--[[--
This is a registry for document providers
]]--

local ConfirmBox = require("ui/widget/confirmbox")
local OpenWithDialog = require("ui/widget/openwithdialog")
local UIManager = require("ui/uimanager")
local gettext = require("gettext")
local logger = require("logger")
local util = require("util")
local T = require("ffi/util").template

local DocumentRegistry = {
    registry = {},
    providers = {},
    filetype_provider = {},
}

function DocumentRegistry:addProvider(extension, mimetype, provider, weight)
    table.insert(self.providers, {
        extension = extension,
        mimetype = mimetype,
        provider = provider,
        weight = weight or 100,
    })
    self.filetype_provider[extension] = true
end

--- Returns true if file has provider.
-- @string file
-- @treturn boolean
function DocumentRegistry:hasProvider(file)
    local filename_suffix = util.getFileNameSuffix(file)

    if self.filetype_provider[filename_suffix] then
        return true
    end
    return false
end

--- Returns the preferred registered document handler.
-- @string file
-- @treturn table provider, or nil
function DocumentRegistry:getProvider(file)
    local providers = self:getProviders(file)

    if providers then
        -- provider for document
        local DocSettings = require("docsettings")
        if DocSettings:hasSidecarFile(file) then
            local doc_settings_provider = DocSettings:open(file):readSetting("provider")
            if doc_settings_provider then
                for _, provider in ipairs(providers) do
                    if provider.provider.provider == doc_settings_provider then
                        return provider.provider
                    end
                end
            end
        end

        -- global provider for filetype
        local filename_suffix = util.getFileNameSuffix(file)
        local g_settings_provider = G_reader_settings:readSetting("provider")

        if g_settings_provider and g_settings_provider[filename_suffix] then
            for _, provider in ipairs(providers) do
                if provider.provider.provider == g_settings_provider[filename_suffix] then
                    return provider.provider
                end
            end
        end

        -- highest weighted provider
        return providers[1].provider
    end
end

--- Returns the registered document handlers.
-- @string file
-- @treturn table providers, or nil
function DocumentRegistry:getProviders(file)
    local providers = {}

    -- TODO: some implementation based on mime types?
    for _, provider in ipairs(self.providers) do
        local suffix = string.sub(file, -string.len(provider.extension) - 1)
        if string.lower(suffix) == "."..provider.extension then
        -- if extension == provider.extension then
            -- stick highest weighted provider at the front
            if #providers >= 1 and provider.weight > providers[1].weight then
                table.insert(providers, 1, provider)
            else
                table.insert(providers, provider)
            end
        end
    end

    if #providers >= 1 then
        return providers
    end
end

--- Sets the preferred registered document handler.
-- @string file
-- @bool all
function DocumentRegistry:setProvider(file, provider, all)
    local _, filename_suffix = util.splitFileNameSuffix(file)

    -- per-document
    if not all then
        local DocSettings = require("docsettings"):open(file)
        DocSettings:saveSetting("provider", provider.provider)
        DocSettings:flush()
    -- global
    else
        local filetype_provider = G_reader_settings:readSetting("provider") or {}
        filetype_provider[filename_suffix] = provider.provider
        G_reader_settings:saveSetting("provider", filetype_provider)
    end
end

function DocumentRegistry:showSetProviderButtons(file, filemanager_instance,  ui, reader_ui)
    local _, filename_pure = util.splitFilePathName(file)
    local filename_suffix = util.getFileNameSuffix(file)

    local buttons = {}
    local radio_buttons = {}
    local providers = self:getProviders(file)

    for _, provider in ipairs(providers) do
        -- we have no need for extension, mimetype, weights, etc. here
        provider = provider.provider
        table.insert(radio_buttons, {
            {
                text = provider.provider_name,
                checked = self:getProvider(file) == provider,
                provider = provider,
            },
        })
    end

    table.insert(buttons, {
        {
            text = gettext("Cancel"),
            callback = function()
                UIManager:close(self.set_provider_dialog)
            end,
        },
        {
            text = gettext("Open"),
            is_enter_default = true,
            callback = function()
                local provider = self.set_provider_dialog.radio_button_table.checked_button.provider

                -- always for this file
                if self.set_provider_dialog._check_file_button.checked then
                    UIManager:show(ConfirmBox:new{
                        text = T(gettext("Always open '%2' with %1?"),
                                         provider.provider_name, filename_pure),
                        ok_text = gettext("Always"),
                        ok_callback = function()
                            self:setProvider(file, provider, false)

                            filemanager_instance:onClose()
                            reader_ui:showReader(file, provider)
                            UIManager:close(self.set_provider_dialog)
                        end,
                    })
                -- always for all files of this file type
                elseif self.set_provider_dialog._check_global_button.checked then
                    UIManager:show(ConfirmBox:new{
                        text = T(gettext("Always open %2 files with %1?"),
                                         provider.provider_name, filename_suffix),
                        ok_text = gettext("Always"),
                        ok_callback = function()
                            self:setProvider(file, provider, true)

                            filemanager_instance:onClose()
                            reader_ui:showReader(file, provider)
                            UIManager:close(self.set_provider_dialog)
                        end,
                    })
                else
                    -- just once
                    filemanager_instance:onClose()
                    reader_ui:showReader(file, provider)
                    UIManager:close(self.set_provider_dialog)
                end
            end,
        },
    })

    self.set_provider_dialog = OpenWithDialog:new{
        title = T(gettext("Open %1 with:"), filename_pure),
        radio_buttons = radio_buttons,
        buttons = buttons,
    }
    UIManager:show(self.set_provider_dialog)
end

function DocumentRegistry:openDocument(file, provider)
    -- force a GC, so that any previous document used memory can be reused
    -- immediately by this new document without having to wait for the
    -- next regular gc. The second call may help reclaming more memory.
    collectgarbage()
    collectgarbage()
    if not self.registry[file] then
        provider = provider or self:getProvider(file)

        if provider ~= nil then
            local ok, doc = pcall(provider.new, provider, {file = file})
            if ok then
                self.registry[file] = {
                    doc = doc,
                    refs = 1,
                }
            else
                logger.warn("cannot open document", file, doc)
            end
        end
    else
        self.registry[file].refs = self.registry[file].refs + 1
    end
    if self.registry[file] then
        return self.registry[file].doc
    end
end

function DocumentRegistry:closeDocument(file)
    if self.registry[file] then
        self.registry[file].refs = self.registry[file].refs - 1
        if self.registry[file].refs == 0 then
            self.registry[file] = nil
            return 0
        else
            return self.registry[file].refs
        end
    else
        error("Try to close unregistered file.")
    end
end

-- load implementations:

require("document/credocument"):register(DocumentRegistry)
require("document/pdfdocument"):register(DocumentRegistry)
require("document/djvudocument"):register(DocumentRegistry)
require("document/picdocument"):register(DocumentRegistry)

return DocumentRegistry
