local Cache = require("cache")
local ConfirmBox = require("ui/widget/confirmbox")
local Device = require("device")
local Event = require("ui/event")
local Geom = require("ui/geometry")
local GestureRange = require("ui/gesturerange")
local InfoMessage = require("ui/widget/infomessage")
local InputContainer = require("ui/widget/container/inputcontainer")
local UIManager = require("ui/uimanager")
local logger = require("logger")
local _ = require("gettext")
local Input = Device.input
local Screen = Device.screen
local T = require("ffi/util").template

local ReaderZooming = InputContainer:new{
    zoom = 1.0,
    -- default to nil so we can trigger ZoomModeUpdate events on start up
    zoom_mode = nil,
    DEFAULT_ZOOM_MODE = "pagewidth",
    current_page = 1,
    rotation = 0,
    paged_modes = {
        page = _("Zoom to fit page works best with page view."),
        pageheight = _("Zoom to fit page height works best with page view."),
        contentheight = _("Zoom to fit content height works best with page view."),
        content = _("Zoom to fit content works best with page view."),
    },
}

function ReaderZooming:init()
    if Device:hasKeyboard() then
        self.key_events = {
            ZoomIn = {
                { "Shift", Input.group.PgFwd },
                doc = "zoom in",
                event = "Zoom", args = "in"
            },
            ZoomOut = {
                { "Shift", Input.group.PgBack },
                doc = "zoom out",
                event = "Zoom", args = "out"
            },
            ZoomToFitPage = {
                { "A" },
                doc = "zoom to fit page",
                event = "SetZoomMode", args = "page"
            },
            ZoomToFitContent = {
                { "Shift", "A" },
                doc = "zoom to fit content",
                event = "SetZoomMode", args = "content"
            },
            ZoomToFitPageWidth = {
                { "S" },
                doc = "zoom to fit page width",
                event = "SetZoomMode", args = "pagewidth"
            },
            ZoomToFitContentWidth = {
                { "Shift", "S" },
                doc = "zoom to fit content width",
                event = "SetZoomMode", args = "contentwidth"
            },
            ZoomToFitPageHeight = {
                { "D" },
                doc = "zoom to fit page height",
                event = "SetZoomMode", args = "pageheight"
            },
            ZoomToFitContentHeight = {
                { "Shift", "D" },
                doc = "zoom to fit content height",
                event = "SetZoomMode", args = "contentheight"
            },
            ZoomToFitColumn = {
                { "Shift", "C" },
                doc = "zoom to fit column",
                event = "SetZoomMode", args = "colu"
            },
        }
    end
    self.ui.menu:registerToMainMenu(self)
end

function ReaderZooming:onReadSettings(config)
    local zoom_mode = config:readSetting("zoom_mode") or
                    G_reader_settings:readSetting("zoom_mode") or
                    self.DEFAULT_ZOOM_MODE
    self:setZoomMode(zoom_mode, true) -- avoid informative message on load
end

function ReaderZooming:onSaveSettings()
    self.ui.doc_settings:saveSetting("zoom_mode", self.orig_zoom_mode or self.zoom_mode)
end

function ReaderZooming:onSpread(arg, ges)
    if ges.direction == "horizontal" then
        self:genSetZoomModeCallBack("contentwidth")()
    elseif ges.direction == "vertical" then
        self:genSetZoomModeCallBack("contentheight")()
    elseif ges.direction == "diagonal" then
        self:genSetZoomModeCallBack("content")()
    end
    return true
end

function ReaderZooming:onPinch(arg, ges)
    if ges.direction == "diagonal" then
        self:genSetZoomModeCallBack("page")()
    elseif ges.direction == "horizontal" then
        self:genSetZoomModeCallBack("pagewidth")()
    elseif ges.direction == "vertical" then
        self:genSetZoomModeCallBack("pageheight")()
    end
    return true
end

function ReaderZooming:onToggleFreeZoom(arg, ges)
    if self.zoom_mode ~= "free" then
        self.orig_zoom = self.zoom
        local xpos, ypos
        self.zoom, xpos, ypos = self:getRegionalZoomCenter(self.current_page, ges.pos)
        logger.info("zoom center", self.zoom, xpos, ypos)
        self.ui:handleEvent(Event:new("SetZoomMode", "free"))
        if xpos == nil or ypos == nil then
            xpos = ges.pos.x * self.zoom / self.orig_zoom
            ypos = ges.pos.y * self.zoom / self.orig_zoom
        end
        self.view:SetZoomCenter(xpos, ypos)
    else
        self.ui:handleEvent(Event:new("SetZoomMode", "page"))
    end
end

function ReaderZooming:onSetDimensions(dimensions)
    -- we were resized
    self.dimen = dimensions
    self:setZoom()
end

function ReaderZooming:onRestoreDimensions(dimensions)
    -- we were resized
    self.dimen = dimensions
    self:setZoom()
end

function ReaderZooming:onRotationUpdate(rotation)
    self.rotation = rotation
    self:setZoom()
end

function ReaderZooming:onZoom(direction)
    logger.info("zoom", direction)
    if direction == "in" then
        self.zoom = self.zoom * 1.333333
    elseif direction == "out" then
        self.zoom = self.zoom * 0.75
    end
    logger.info("zoom is now at", self.zoom)
    self:onSetZoomMode("free")
    self.view:onZoomUpdate(self.zoom)
    return true
end

function ReaderZooming:onSetZoomMode(new_mode)
    self.view.zoom_mode = new_mode
    if self.zoom_mode ~= new_mode then
        logger.info("setting zoom mode to", new_mode)
        self.ui:handleEvent(Event:new("ZoomModeUpdate", new_mode))
        self.zoom_mode = new_mode
        self:setZoom()
        self.ui:handleEvent(Event:new("InitScrollPageStates", new_mode))
    end
end

function ReaderZooming:onPageUpdate(new_page_no)
    self.current_page = new_page_no
    self:setZoom()
end

function ReaderZooming:onReZoom(font_size)
    if self.document.is_reflowable then
        local reflowable_font_size = self.document:convertKoptToReflowableFontSize(font_size)
        self.document:layoutDocument(reflowable_font_size)
    end
    self:setZoom()
    self.ui:handleEvent(Event:new("InitScrollPageStates"))
    return true
end

function ReaderZooming:onEnterFlippingMode(zoom_mode)
    if Device:isTouchDevice() then
        self.ges_events = {
            Spread = {
                GestureRange:new{
                    ges = "spread",
                    range = Geom:new{
                        x = 0, y = 0,
                        w = Screen:getWidth(),
                        h = Screen:getHeight(),
                    }
                }
            },
            Pinch = {
                GestureRange:new{
                    ges = "pinch",
                    range = Geom:new{
                        x = 0, y = 0,
                        w = Screen:getWidth(),
                        h = Screen:getHeight(),
                    }
                }
            },
            ToggleFreeZoom = {
                GestureRange:new{
                    ges = "double_tap",
                    range = Geom:new{
                        x = 0, y = 0,
                        w = Screen:getWidth(),
                        h = Screen:getHeight(),
                    }
                }
            },
        }
    end

    self.orig_zoom_mode = self.zoom_mode
    if zoom_mode == "free" then
        self.ui:handleEvent(Event:new("SetZoomMode", "page"))
    else
        self.ui:handleEvent(Event:new("SetZoomMode", zoom_mode))
    end
end

function ReaderZooming:onExitFlippingMode(zoom_mode)
    if Device:isTouchDevice() then
        self.ges_events = {}
    end
    self.orig_zoom_mode = nil
    self.ui:handleEvent(Event:new("SetZoomMode", zoom_mode))
end

function ReaderZooming:getZoom(pageno)
    -- check if we're in bbox mode and work on bbox if that's the case
    local zoom = nil
    local page_size = self.ui.document:getNativePageDimensions(pageno)
    if self.zoom_mode == "content"
    or self.zoom_mode == "contentwidth"
    or self.zoom_mode == "contentheight"
    or self.zoom_mode == "column" then
        local ubbox_dimen = self.ui.document:getUsedBBoxDimensions(pageno, 1)
        -- if bbox is larger than the native page dimension render the full page
        -- See discussion in koreader/koreader#970.
        if ubbox_dimen.w <= page_size.w and ubbox_dimen.h <= page_size.h then
            page_size = ubbox_dimen
            self.view:onBBoxUpdate(ubbox_dimen)
        else
            self.view:onBBoxUpdate(nil)
        end
    else
        -- otherwise, operate on full page
        self.view:onBBoxUpdate(nil)
    end
    -- calculate zoom value:
    local zoom_w = self.dimen.w
    local zoom_h = self.dimen.h
    if self.ui.view.footer_visible then
        zoom_h = zoom_h - self.ui.view.footer:getHeight()
    end
    if self.rotation % 180 == 0 then
        -- No rotation or rotated by 180 degrees
        zoom_w = zoom_w / page_size.w
        zoom_h = zoom_h / page_size.h
    else
        -- rotated by 90 or 270 degrees
        zoom_w = zoom_w / page_size.h
        zoom_h = zoom_h / page_size.w
    end
    if self.zoom_mode == "content" or self.zoom_mode == "page" then
        if zoom_w < zoom_h then
            zoom = zoom_w
        else
            zoom = zoom_h
        end
    elseif self.zoom_mode == "contentwidth" or self.zoom_mode == "pagewidth" then
        zoom = zoom_w
    elseif self.zoom_mode == "column" then
        zoom = zoom_w * 2
    elseif self.zoom_mode == "contentheight" or self.zoom_mode == "pageheight" then
        zoom = zoom_h
    elseif self.zoom_mode == "free" then
        zoom = self.zoom
    end
    if zoom and zoom > 10 and not Cache:willAccept(zoom * (self.dimen.w * self.dimen.h + 64)) then
        logger.dbg("zoom too large, adjusting")
        while not Cache:willAccept(zoom * (self.dimen.w * self.dimen.h + 64)) do
            if zoom > 100 then
                zoom = zoom - 50
            elseif zoom > 10 then
                zoom = zoom - 5
            elseif zoom > 1 then
                zoom = zoom - 0.5
            elseif zoom > 0.1 then
                zoom = zoom - 0.05
            else
                zoom = zoom - 0.005
            end
            logger.dbg("new zoom: "..zoom)

            if zoom < 0 then return 0 end
        end
    end
    return zoom
end

function ReaderZooming:getRegionalZoomCenter(pageno, pos)
    local p_pos = self.view:getSinglePagePosition(pos)
    local page_size = self.ui.document:getNativePageDimensions(pageno)
    local pos_x = p_pos.x / page_size.w
    local pos_y = p_pos.y / page_size.h
    local block = self.ui.document:getPageBlock(pageno, pos_x, pos_y)
    local margin = self.ui.document.configurable.page_margin * Screen:getDPI()
    if block then
        local zoom = self.dimen.w / page_size.w / (block.x1 - block.x0)
        zoom = zoom/(1 + 3*margin/zoom/page_size.w)
        local xpos = (block.x0 + block.x1)/2 * zoom * page_size.w
        local ypos = p_pos.y / p_pos.zoom * zoom
        return zoom, xpos, ypos
    end
    local zoom = 2*self.dimen.w / page_size.w
    return zoom/(1 + 3*margin/zoom/page_size.w)
end

function ReaderZooming:setZoom()
    if not self.dimen then
        self.dimen = self.ui.dimen
    end
    self.zoom = self:getZoom(self.current_page)
    self.ui:handleEvent(Event:new("ZoomUpdate", self.zoom))
end

function ReaderZooming:genSetZoomModeCallBack(mode)
    return function()
        self:setZoomMode(mode)
    end
end

function ReaderZooming:setZoomMode(mode, no_warning)
    if not no_warning and self.ui.view.page_scroll and self.paged_modes[mode] then
        UIManager:show(InfoMessage:new{
            text = T(_([[
%1

In combination with continuous view (scroll mode), this can cause unexpected vertical shifts when turning pages.]]), self.paged_modes[mode]),
            timeout = 5,
        })
    end

    self.ui:handleEvent(Event:new("SetZoomMode", mode))
    self.ui:handleEvent(Event:new("InitScrollPageStates"))
end

function ReaderZooming:addToMainMenu(menu_items)
    if self.ui.document.info.has_pages then
        local function getZoomModeMenuItem(text, mode, separator)
            return {
                text_func = function()
                    local default_zoom_mode = G_reader_settings:readSetting("zoom_mode") or self.DEFAULT_ZOOM_MODE
                    return text .. (mode == default_zoom_mode and "   ★" or "")
                end,
                checked_func = function()
                    return self.zoom_mode == mode
                end,
                callback = self:genSetZoomModeCallBack(mode),
                hold_callback = function(touchmenu_instance)
                    self:makeDefault(mode, touchmenu_instance)
                end,
                separator = separator,
            }
        end
        menu_items.switch_zoom_mode = {
            text = _("Switch zoom mode"),
            enabled_func = function()
                return self.ui.document.configurable.text_wrap ~= 1
            end,
            sub_item_table = {
                getZoomModeMenuItem(_("Zoom to fit content width"), "contentwidth"),
                getZoomModeMenuItem(_("Zoom to fit content height"), "contentheight", true),
                getZoomModeMenuItem(_("Zoom to fit page width"), "pagewidth"),
                getZoomModeMenuItem(_("Zoom to fit page height"), "pageheight", true),
                getZoomModeMenuItem(_("Zoom to fit column"), "column"),
                getZoomModeMenuItem(_("Zoom to fit content"), "content"),
                getZoomModeMenuItem(_("Zoom to fit page"), "page"),
            }
        }
    end
end

function ReaderZooming:makeDefault(zoom_mode, touchmenu_instance)
    UIManager:show(ConfirmBox:new{
        text = T(
            _("Set default zoom mode to %1?"),
            zoom_mode
        ),
        ok_callback = function()
            G_reader_settings:saveSetting("zoom_mode", zoom_mode)
            if touchmenu_instance then touchmenu_instance:updateItems() end
        end,
    })
end

return ReaderZooming
