--[[--
@module koplugin.coverimage

Plugin for saving a cover image to a file and scaling it to fit the screen.
]]

local Device = require("device")

if not (Device.isAndroid() or Device.isEmulator() or Device.isRemarkable() or Device.isPocketBook()) then
    return { disabled = true }
end

local A, android = pcall(require, "android")  -- luacheck: ignore
local Blitbuffer = require("ffi/blitbuffer")
local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage")
local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo")
local InfoMessage = require("ui/widget/infomessage")
local InputDialog = require("ui/widget/inputdialog")
local PathChooser = require("ui/widget/pathchooser")
local UIManager = require("ui/uimanager")
local RenderImage = require("ui/renderimage")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local ffiutil = require("ffi/util")
local lfs = require("libs/libkoreader-lfs")
local logger = require("logger")
local util = require("util")
local _ = require("gettext")
local C_ = _.pgettext
local Screen = require("device").screen
local T = require("ffi/util").template
local md5 = require("ffi/sha2").md5

-- todo: please check the default paths directly on the depending Device:getDefaultCoverPath()


local function isPathAllowed(path)
    -- don't allow a path that interferes with frontend cache-framework; quick and dirty check

    if not Device:isValidPath(path) then -- isValidPath expects a trailing slash
        return false
    elseif not util.pathExists(path:gsub("/$", "")) then -- pathExists expects no trailing slash
        return false
    elseif Device.isAndroid() then
        return path ~= "/sdcard/koreader/cache/"
            and ffiutil.realpath(path) ~= ffiutil.realpath(android.getExternalStoragePath() .. "/koreader/cache/")
    else
        return path ~= "./cache/" and ffiutil.realpath(path) ~= ffiutil.realpath("./cache/")
    end
end

local function isFileOk(filename)
    local path, name = util.splitFilePathName(filename)

    if not isPathAllowed(path) then
        return false
    end

    return name ~="" and lfs.attributes(filename, "mode") ~= "directory"
end

local function getExtension(filename)
    local dummy, name = util.splitFilePathName(filename)
    return util.getFileNameSuffix(name):lower()
end

local CoverImage = WidgetContainer:extend{
    name = "coverimage",
    is_doc_only = true,
}

local default_cache_path = DataStorage:getDataDir() .. "/cache/cover_image.cache/"
local default_fallback_path = DataStorage:getDataDir() .. "/"

function CoverImage:init()
    self.cover_image_path = G_reader_settings:readSetting("cover_image_path") or Device:getDefaultCoverPath()
    self.cover_image_format = G_reader_settings:readSetting("cover_image_format", "auto")
    self.cover_image_quality = G_reader_settings:readSetting("cover_image_quality", 75)
    self.cover_image_grayscale = G_reader_settings:isTrue("cover_image_grayscale")
    self.cover_image_stretch_limit = G_reader_settings:readSetting("cover_image_stretch_limit", 8)
    self.cover_image_background = G_reader_settings:readSetting("cover_image_background", "black")
    self.cover_image_rotate = G_reader_settings:readSetting("cover_image_rotate", true)
    self.cover_image_fallback_path = G_reader_settings:readSetting("cover_image_fallback_path",
        default_fallback_path)
    self.cover_image_cache_path = G_reader_settings:readSetting("cover_image_cache_path",
       default_cache_path)
    self.cover_image_cache_maxfiles = G_reader_settings:readSetting("cover_image_cache_maxfiles", 36)
    self.cover_image_cache_maxsize = G_reader_settings:readSetting("cover_image_cache_maxsize", 5) -- MB
    self.cover_image_cache_prefix = "cover_"
    self.cover = G_reader_settings:isTrue("cover_image_enabled")
    self.fallback = G_reader_settings:isTrue("cover_image_fallback")

    lfs.mkdir(self.cover_image_cache_path)

    self.ui.menu:registerToMainMenu(self)
end

function CoverImage:cleanUpImage()
    if self.cover_image_fallback_path == "" or not self:fallbackEnabled() then
        os.remove(self.cover_image_path)
    elseif lfs.attributes(self.cover_image_fallback_path, "mode") ~= "file" then
        UIManager:show(InfoMessage:new{
            text = T(_("\"%1\"\nis not a valid image file!\nA valid fallback image is required in Cover-Image."), self.cover_image_fallback_path),
            show_icon = true,
            timeout = 10,
        })
        os.remove(self.cover_image_path)
    elseif isFileOk(self.cover_image_path) then
        ffiutil.copyFile(self.cover_image_fallback_path, self.cover_image_path)
    end
end

function CoverImage:createCoverImage(doc_settings)
    if not (self:coverEnabled() and doc_settings:nilOrFalse("exclude_cover_image")) then
      return
    end

    local cover_image, custom_cover = FileManagerBookInfo:getCoverImage(self.ui.document)
    if not cover_image then
        return
    end

    local cache_file = self:getCacheFile(custom_cover)
    if lfs.attributes(cache_file, "mode") == "file" then
        logger.dbg("CoverImage: cache file already exists")
        ffiutil.copyFile(cache_file, self.cover_image_path)
        lfs.touch(cache_file) -- update date
        return
    end

    local s_w, s_h = Screen:getWidth(), Screen:getHeight()
    local i_w, i_h = cover_image:getWidth(), cover_image:getHeight()
    local scale_factor = math.min(s_w / i_w, s_h / i_h)

    if self.cover_image_rotate and (
        Screen:getRotationMode() == Screen.DEVICE_ROTATED_UPSIDE_DOWN or
        Screen:getRotationMode() == Screen.DEVICE_ROTATED_CLOCKWISE
    ) then
        local rotated_cover = cover_image:rotatedCopy(180)
        cover_image:free()
        cover_image = rotated_cover
    end

    if self.cover_image_background == "none" or scale_factor == 1 then
        local act_format = self.cover_image_format == "auto" and getExtension(self.cover_image_path) or self.cover_image_format
        if not cover_image:writeToFile(self.cover_image_path, act_format, self.cover_image_quality, self.cover_image_grayscale) then
            UIManager:show(InfoMessage:new{
                text = _("Error writing file") .. "\n" .. self.cover_image_path,
                show_icon = true,
            })
        end
        cover_image:free()
        ffiutil.copyFile(self.cover_image_path, cache_file)
        self:cleanCache()
        return
    end

    local screen_ratio = s_w / s_h
    local image_ratio = i_w / i_h
    local ratio_divergence_percent = math.abs(100 - image_ratio / screen_ratio * 100)

    logger.dbg("CoverImage: geometries screen=" .. screen_ratio .. ", image=" .. image_ratio .. "; ratio=" .. ratio_divergence_percent)

    local image
    if ratio_divergence_percent < self.cover_image_stretch_limit then -- stretch
        logger.dbg("CoverImage: stretch to fullscreen")
        image = RenderImage:scaleBlitBuffer(cover_image, s_w, s_h)
    else -- scale
        local scaled_w, scaled_h = math.floor(i_w * scale_factor), math.floor(i_h * scale_factor)
        logger.dbg("CoverImage: scale to fullscreen, fill background")

        cover_image = RenderImage:scaleBlitBuffer(cover_image, scaled_w, scaled_h)
        -- new buffer with screen dimensions,
        image = Blitbuffer.new(s_w, s_h, cover_image:getType()) -- new buffer, filled with black
        if self.cover_image_background == "white" then
            image:fill(Blitbuffer.COLOR_WHITE)
        elseif self.cover_image_background == "gray" then
            image:fill(Blitbuffer.COLOR_GRAY)
        end
        -- copy scaled image to buffer
        if s_w > scaled_w then -- move right
            image:blitFrom(cover_image, math.floor((s_w - scaled_w) / 2), 0, 0, 0, scaled_w, scaled_h)
        else -- move down
            image:blitFrom(cover_image, 0, math.floor((s_h - scaled_h) / 2), 0, 0, scaled_w, scaled_h)
        end
    end

    cover_image:free()

    local act_format = self.cover_image_format == "auto" and getExtension(self.cover_image_path) or self.cover_image_format
    if not image:writeToFile(self.cover_image_path, act_format, self.cover_image_quality, self.cover_image_grayscale) then
        UIManager:show(InfoMessage:new{
            text = _("Error writing file") .. "\n" .. self.cover_image_path,
            show_icon = true,
            })
    end

    image:free()
    logger.dbg("CoverImage: image written to " .. self.cover_image_path)

    ffiutil.copyFile(self.cover_image_path, cache_file)
    self:cleanCache()
end

function CoverImage:onCloseDocument()
    logger.dbg("CoverImage: onCloseDocument")
    if self:fallbackEnabled() then
        self:cleanUpImage()
    end
end

function CoverImage:onReaderReady(doc_settings)
    logger.dbg("CoverImage: onReaderReady")
    self:createCoverImage(doc_settings)
end

function CoverImage:onSetRotationMode(rotation)
    logger.dbg("CoverImage: onSetRotationMode", rotation)
    self:createCoverImage(self.ui.doc_settings)
end

function CoverImage:fallbackEnabled()
    return self.fallback and isFileOk(self.cover_image_fallback_path)
end

function CoverImage:coverEnabled()
    return self.cover and isFileOk(self.cover_image_path)
end

---------------------------
-- cache handling functions
---------------------------

function CoverImage:getCacheFile(custom_cover)
    local custom_cover_mtime = custom_cover and lfs.attributes(custom_cover, "modification") or ""
    local dummy, document_name = util.splitFilePathName(self.ui.document.file)
    local rotated = ""
    if self.cover_image_rotate then
        rotated = "_rotated_"
    end

    -- use document_name here. Title may contain characters not allowed on every filesystem (esp. vfat on /sdcard)
    local key = document_name .. custom_cover_mtime .. self.cover_image_quality .. self.cover_image_stretch_limit
        .. self.cover_image_background .. self.cover_image_format .. tostring(self.cover_image_grayscale)
        .. Screen:getRotationMode() .. rotated

    return self.cover_image_cache_path .. self.cover_image_cache_prefix .. md5(key) .. "." .. getExtension(self.cover_image_path)
end

function CoverImage:emptyCache()
    for entry in lfs.dir(self.cover_image_cache_path) do
        if entry ~= "." and entry ~= ".." then
            local file = self.cover_image_cache_path .. entry
            if entry:sub(1, self.cover_image_cache_prefix:len()) == self.cover_image_cache_prefix
                and lfs.attributes(file, "mode") == "file" then
                    os.remove(file)
            end
        end
    end
end

function CoverImage:getCacheFiles(cache_path, cache_prefix)
    local cache_size = 0
    local files = {}
    for entry in lfs.dir(self.cover_image_cache_path) do
        if entry ~= "." and entry ~= ".." then
            local file = cache_path .. entry
            if entry:sub(1, self.cover_image_cache_prefix:len()) == cache_prefix
                and lfs.attributes(file, "mode") == "file" then
                local blocksize = lfs.attributes(file).blksize or 4096
                local size = math.floor(((lfs.attributes(file).size) + blocksize - 1) / blocksize) * blocksize
                table.insert(files, {
                    name = file,
                    size = size,
                    mod = lfs.attributes(file).modification,
                })
                cache_size = cache_size + size
            end
        end
    end
    logger.dbg("CoverImage: start - cache size: ".. util.getFriendlySize(cache_size) ..
        ", cached files: " .. #files)
    return #files, cache_size, files
end

function CoverImage:cleanCache()
    if not self:isCacheEnabled() then
        self:emptyCache()
        return
    end

    local cache_count, cache_size, files = self:getCacheFiles(self.cover_image_cache_path, self.cover_image_cache_prefix)

    -- delete the oldest files first
    table.sort(files, function(a, b) return a.mod < b.mod end)
    local index = 1
    while (cache_count > self.cover_image_cache_maxfiles and self.cover_image_cache_maxfiles ~= 0)
        or (cache_size > self.cover_image_cache_maxsize * 1000 * 1000 and self.cover_image_cache_maxsize ~= 0)
        and index <= #files do
        os.remove(files[index].name)
        cache_count = cache_count - 1
        cache_size = cache_size - files[index].size
        index = index + 1
    end
    logger.dbg("CoverImage: clean - cache size: ".. util.getFriendlySize(cache_size) ..
        ", cached files: " .. cache_count)
end

function CoverImage:isCacheEnabled(path)
    if not path then
        path = self.cover_image_cache_path
    end

    return self.cover_image_cache_maxfiles >= 0 and self.cover_image_cache_maxsize >= 0
        and lfs.attributes(path, "mode") == "directory" and isPathAllowed(path)
end

-- callback for choosePathFile()
function CoverImage:migrateCache(old_path, new_path)
    if old_path == new_path or not self:isCacheEnabled(new_path) then
        return
    end
    for entry in lfs.dir(old_path) do
        if entry ~= "." and entry ~= ".."   then
            local old_file = old_path .. entry
            if lfs.attributes(old_file, "mode") == "file" and entry:sub(1, self.cover_image_cache_prefix:len()) == self.cover_image_cache_prefix then
                local old_access_time = lfs.attributes(old_file, "access")
                local new_file = new_path .. entry
                os.rename(old_file, new_file)
                lfs.touch(new_file, old_access_time) -- restore original time
            end
        end
    end
end

-- callback for choosePathFile()
function CoverImage:migrateCover(old_file, new_file)
    if old_file ~= new_file then
        os.rename(old_file, new_file)
    end
end

--[[--
chooses a path or (an existing) file

@touchmenu_instance for updating of the menu
@string key is the G_reader_setting key which is used and changed
@boolean folder_only just selects a path, no file handling
@boolean new_file allows to enter a new filename, or use just an existing file
@function migrate(a,b) callback to a function to mangle old folder/file with new folder/file.
    Can be used for migrating the contents of the old path to the new one
]]
function CoverImage:choosePathFile(touchmenu_instance, key, folder_only, new_file, migrate)
    local old_path, dummy = util.splitFilePathName(self[key])
    UIManager:show(PathChooser:new{
        select_directory = folder_only or new_file,
        select_file = not folder_only,
        height = Screen:getHeight(),
        path = old_path,
        onConfirm = function(dir_path)
            local mode = lfs.attributes(dir_path, "mode")
            if folder_only then -- just select a folder
                if not dir_path:find("/$") then
                    dir_path = dir_path .. "/"
                end
                if migrate then
                    migrate(self, self[key], dir_path)
                end
                self[key] = dir_path
                G_reader_settings:saveSetting(key, dir_path)
                if touchmenu_instance then
                    touchmenu_instance:updateItems()
                end
            elseif new_file and mode == "directory" then -- new filename should be entered or a file could be selected
                local file_input
                file_input = InputDialog:new{
                    title =  _("Append filename"),
                    input = dir_path .. "/",
                    buttons = {{
                        {
                            text = _("Cancel"),
                            id = "close",
                            callback = function()
                                UIManager:close(file_input)
                            end,
                        },
                        {
                            text = _("Save"),
                            callback = function()
                                local file = file_input:getInputText()
                                if migrate and self[key] and self[key] ~= "" then
                                    migrate(self, self[key], file)
                                end
                                self[key] = file
                                G_reader_settings:saveSetting(key, file)
                                if touchmenu_instance then
                                    touchmenu_instance:updateItems()
                                end
                                UIManager:close(file_input)
                            end,
                        },
                    }},
                }
                UIManager:show(file_input)
                file_input:onShowKeyboard()
            elseif mode == "file" then   -- just select an existing file
                if migrate then
                    migrate(self, self[key], dir_path)
                end
                self[key] = dir_path
                G_reader_settings:saveSetting(key, dir_path)
                if touchmenu_instance then
                    touchmenu_instance:updateItems()
                end
            end
        end,
    })
end

--[[--
Update a specific G_reader_setting's value via a Spinner

@touchmenu_instance used for updating the menu
@string setting is the G_reader_setting key which is used and changed
@string title shown in the spinner
@int min minimum value of the spinner
@int max maximum value of the spinner
@int default default value of the spinner
@function callback to call, when spinner changed the value
]]
function CoverImage:sizeSpinner(touchmenu_instance, setting, title, min, max, default, callback, unit)
    local SpinWidget = require("ui/widget/spinwidget")
    UIManager:show(SpinWidget:new{
        value = self[setting],
        value_min = min,
        value_max = max,
        unit = unit,
        default_value = default,
        title_text = title,
        ok_text = _("Set"),
        callback = function(spin)
            self[setting] = spin.value
            G_reader_settings:saveSetting(setting, self[setting])
            if callback then
                callback(self)
            end
            if touchmenu_instance then touchmenu_instance:updateItems() end
        end
    })
end

-------------- menus and longer texts -----------


local about_text = _([[
This plugin saves a book cover to a file. That file can then be used as a screensaver on certain devices.

If enabled, the cover image of the current file is stored in the set path on book opening. Books can be excluded if desired.

If disabled, the cover file will be deleted.

If fallback is enabled, the fallback file will be copied to the screensaver file on book closing.
If the filename is empty or the file doesn't exist, the cover file will be deleted.

If fallback is disabled, the screensaver image will stay in place after closing a book.]])

local set_image_text = _([[
You can either choose an existing file:
- Choose a file

or specify a new file:
- First choose a folder
- Then add the name of the new file

or delete the path:
- First choose a folder
- Clear the name of the file]])

-- menu entry: Cache settings
function CoverImage:menuEntryCache()
    return {
        text = _("Cache settings"),
        checked_func = function()
            return self:isCacheEnabled()
        end,
        sub_item_table = {
            {
                text_func = function()
                    local number
                    if self.cover_image_cache_maxfiles > 0 then
                        number = self.cover_image_cache_maxfiles
                    elseif self.cover_image_cache_maxfiles == 0 then
                        number = _("unlimited")
                    else
                        number = _("off")
                    end
                    return T(_("Maximum number of cached covers: %1"), number)
                end,
                help_text = string.format("%s\n\n%s",
                    _("If set to zero the number of cache files is unlimited.\nIf set to -1 the cache is disabled."),
                    _("Each screen orientation requires its own cache file.")),
                checked_func = function()
                    return self.cover_image_cache_maxfiles >= 0
                end,
                check_callback_updates_menu = true,
                callback = function(touchmenu_instance)
                    self:sizeSpinner(touchmenu_instance, "cover_image_cache_maxfiles", _("Number of covers"), -1, 100, 36, self.cleanCache)
                end,
            },
            {
                text_func = function()
                    local number
                    if self.cover_image_cache_maxsize > 0 then
                        number = util.getFriendlySize(self.cover_image_cache_maxsize * 1e6)
                    elseif self.cover_image_cache_maxsize == 0 then
                        number = _("unlimited")
                    else
                        number = _("off")
                    end
                    return T(_("Maximum size of cached covers: %1"), number)
                end,
                help_text = _("If set to zero the cache size is unlimited.\nIf set to -1 the cache is disabled."),
                checked_func = function()
                    return self.cover_image_cache_maxsize >= 0
                end,
                check_callback_updates_menu = true,
                callback = function(touchmenu_instance)
                    self:sizeSpinner(touchmenu_instance, "cover_image_cache_maxsize", _("Cache size"), -1, 100, 5, self.cleanCache, C_("Data storage size", "MB"))
                end,
            },
            self:menuEntrySetPath("cover_image_cache_path", _("Cover cache folder"), _("Current cache path:\n%1"),
                _("Choose a cache folder. The contents of the old folder will be migrated."),
                default_cache_path, true, false, self.migrateCache),
            {
                text = _("Clear cached covers"),
                help_text_func = function()
                    local cache_count, cache_size
                        = self:getCacheFiles(self.cover_image_cache_path, self.cover_image_cache_prefix)
                    return T(_("The cache contains %1 files and uses %2."), cache_count, util.getFriendlySize(cache_size))
                end,
                callback = function()
                    UIManager:show(ConfirmBox:new{
                        text =  _("Clear the cover image cache?"),
                        ok_text = _("Clear"),
                        ok_callback = function()
                            self:emptyCache()
                        end,
                    })
                end,
                keep_menu_open = true,
            },
        },
    }
end

--[[--
Menu entry for setting an specific G_reader_setting key for a path/file

@string key is the G_reader_setting key which is used and changed
@string title shown in the menu
@string help shown in the menu
@string info shown in the menu (if containing %1, the value of the key is shown)
@string the default value
@bool folder_only sets if only folders can be selected
@bool new_file sets if a new filename can be entered
@function migrate a callback for example moving the folder contents
]]
function CoverImage:menuEntrySetPath(key, title, help, info, default, folder_only, new_file, migrate)
    return {
        text = title,
        help_text_func = function()
            local text = self[key]
            text = text ~= "" and text or _("not set")
            return T(help, text)
        end,
        checked_func = function()
            return isFileOk(self[key]) or (isPathAllowed(self[key]) and folder_only)
        end,
        check_callback_updates_menu = true,
        callback = function(touchmenu_instance)
            UIManager:show(ConfirmBox:new{
                text = info,
                ok_callback = function()
                    self:choosePathFile(touchmenu_instance, key, folder_only, new_file, migrate)
                end,
                other_buttons = {{
                {
                    text = _("Default"),
                    callback = function()
                        if migrate then
                            migrate(self, self[key],default)
                        end
                        self[key] = default
                        G_reader_settings:saveSetting(key, default)
                        if touchmenu_instance then
                            touchmenu_instance:updateItems()
                        end
                    end

                }
            }},
            })
        end,
    }
end

function CoverImage:menuEntryFormat(title, format, grayscale)
    return {
        text = title,
        checked_func = function()
            return self.cover_image_format == format and self.cover_image_grayscale == grayscale
        end,
        callback = function()
            local old_cover_image_format = self.cover_image_format
            local old_cover_image_grayscale = self.cover_image_grayscale
            self.cover_image_format = format
            G_reader_settings:saveSetting("cover_image_format", format)
            self.cover_image_grayscale = grayscale
            G_reader_settings:saveSetting("cover_image_grayscale", grayscale)
            if self:coverEnabled() and (old_cover_image_format ~= format or old_cover_image_grayscale ~= grayscale) then
                self:createCoverImage(self.ui.doc_settings)
            end
        end,
    }
end

function CoverImage:menuEntryBackground(color, color_translatable)
    return {
        text = T(_("Fit to screen, %1 background"), _(color_translatable)),
        checked_func = function()
            return self.cover_image_background == color
        end,
        callback = function()
            local old_background = self.cover_image_background
            self.cover_image_background = color
            G_reader_settings:saveSetting("cover_image_background", self.cover_image_background)
            if self:coverEnabled() and old_background ~= self.cover_image_background then
                self:createCoverImage(self.ui.doc_settings)
            end
        end,
    }
end

-- menu entry: scale, background, format
function CoverImage:menuEntrySBF()
    return {
        text = _("Size, background and format"),
        enabled_func = function()
            return self:coverEnabled()
        end,
        sub_item_table = {
            {
                text_func = function()
                    return T(_("Aspect ratio stretch threshold: %1"),
                        self.cover_image_stretch_limit ~= 0 and self.cover_image_stretch_limit .. " %" or _("off"))
                end,
                keep_menu_open = true,
                help_text_func = function()
                    return T(_("If the image and the screen have a similar aspect ratio (±%1 %), stretch the image instead of keeping its aspect ratio."), self.cover_image_stretch_limit)
                end,
                callback = function(touchmenu_instance)
                    local function createCover()
                        self:createCoverImage(self.ui.doc_settings)
                    end
                    self:sizeSpinner(touchmenu_instance, "cover_image_stretch_limit", _("Set stretch threshold"), 0, 20, 8, createCover, "%")
                end,
            },
            self:menuEntryBackground("black", _("black")),
            self:menuEntryBackground("white", _("white")),
            self:menuEntryBackground("gray", _("gray")),
            {
                text = _("Rotate image"),
                keep_menu_open = true,
                help_text_func = function()
                    return T(_("Rotate the generated image to follow the device orientation. Enable this when the native system does not rotate the background image to follow system orientation. Disable this if your device correctly handles screen saver rotation."), self.cover_image_stretch_limit)
                end,
                checked_func = function()
                    return self.cover_image_rotate
                end,
                callback = function()
                    self.cover_image_rotate =  not self.cover_image_rotate
                    G_reader_settings:saveSetting("cover_image_rotate", self.cover_image_rotate)
                    if self:coverEnabled() then
                        self:createCoverImage(self.ui.doc_settings)
                    end
                end,
            },
            {
                text = _("Original image"),
                checked_func = function()
                    return self.cover_image_background == "none"
                end,
                callback = function()
                    local old_background = self.cover_image_background
                    self.cover_image_background = "none"
                    G_reader_settings:saveSetting("cover_image_background", self.cover_image_background)
                    if self:coverEnabled() and old_background ~= self.cover_image_background then
                        self:createCoverImage(self.ui.doc_settings)
                    end
                end,
                separator = true,
            },
            -- menu entries: File format
            {
                text = _("File format derived from filename"),
                help_text = _("If the file format is not supported, then JPG will be used."),
                checked_func = function()
                    return self.cover_image_format == "auto"
                end,
                callback = function()
                    local old_cover_image_format = self.cover_image_format
                    self.cover_image_format = "auto"
                    G_reader_settings:saveSetting("cover_image_format", self.cover_image_format)
                    if self:coverEnabled() and old_cover_image_format ~= self.cover_image_format then
                        self:createCoverImage(self.ui.doc_settings)
                    end
                end,
            },
            self:menuEntryFormat(_("JPG file format"), "jpg"),
            self:menuEntryFormat(_("PNG file format"), "png"),
            self:menuEntryFormat(_("BMP file format (color)"), "bmp"),
            self:menuEntryFormat(_("BMP file format (grayscale)"), "bmp", true),
        },
    }
end

-- CoverImage main menu
function CoverImage:addToMainMenu(menu_items)
    menu_items.coverimage = {
        sorting_hint = "screen",
        text = _("Cover image"),
        checked_func = function()
            return self:coverEnabled() or self:fallbackEnabled()
        end,
        sub_item_table = {
            -- menu entry: about cover image
            {
                text = _("About cover image"),
                callback = function()
                    UIManager:show(InfoMessage:new{
                        text = about_text,
                    })
                end,
                keep_menu_open = true,
                separator = true,
            },
            -- menu entry: filename dialog
            self:menuEntrySetPath("cover_image_path", _("Set image path"), _("Current Cover image path:\n%1"), set_image_text,
                 Device:getDefaultCoverPath(), false, true, self.migrateCover),
            -- menu entry: enable
            {
                text = _("Save cover image"),
                checked_func = function()
                    return self:coverEnabled()
                end,
                enabled_func = function()
                    return self.cover_image_path ~= "" and isFileOk(self.cover_image_path)
                end,
                callback = function()
                    if self.cover_image_path ~= "" then
                        self.cover = not self.cover
                        self.cover = self.cover and self:coverEnabled()
                        G_reader_settings:saveSetting("cover_image_enabled", self.cover)
                        if self:coverEnabled() then
                            self:createCoverImage(self.ui.doc_settings)
                        else
                            self:cleanUpImage()
                        end
                    end
                end,
            },
            -- menu entry: scale, background, format
            self:menuEntrySBF(),
            -- menu entry: exclude this cover
            {
                text = _("Exclude this book cover"),
                checked_func = function()
                    return self.ui and self.ui.doc_settings and self.ui.doc_settings:isTrue("exclude_cover_image")
                end,
                callback = function()
                    if self.ui.doc_settings:isTrue("exclude_cover_image") then
                        self.ui.doc_settings:makeFalse("exclude_cover_image")
                        self:createCoverImage(self.ui.doc_settings)
                    else
                        self.ui.doc_settings:makeTrue("exclude_cover_image")
                        self:cleanUpImage()
                    end
                    self.ui:saveSettings()
                end,
                separator = true,
            },
            -- menu entry: set fallback image
            self:menuEntrySetPath("cover_image_fallback_path", _("Set fallback path"),
                _("The fallback image used on document close is:\n%1"), _("You can choose a fallback image."), default_fallback_path, false, false),
            -- menu entry: fallback
            {
                text = _("Turn on fallback image"),
                checked_func = function()
                    return self:fallbackEnabled()
                end,
                enabled_func = function()
                    return lfs.attributes(self.cover_image_fallback_path, "mode") == "file"
                end,
                callback = function()
                    self.fallback = not self.fallback
                    self.fallback = self.fallback and self:fallbackEnabled()
                    G_reader_settings:saveSetting("cover_image_fallback", self.fallback)
                    if not self:coverEnabled() then
                        self:cleanUpImage()
                    end
                end,
                separator = true,
            },
            -- menu entry: Cache settings
            self:menuEntryCache(),
        },
    }
end

return CoverImage
