local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage")
local DateTimeWidget = require("ui/widget/datetimewidget")
local Device = require("device")
local Dispatcher = require("dispatcher")
local Event = require("ui/event")
local InfoMessage = require("ui/widget/infomessage")
local InputDialog = require("ui/widget/inputdialog")
local LuaSettings = require("luasettings")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local ffiUtil = require("ffi/util")
local logger = require("logger")
local util = require("util")
local _ = require("gettext")
local C_ = _.pgettext
local Screen = Device.screen
local T = ffiUtil.template

local autostart_done

local Profiles = WidgetContainer:extend{
    name = "profiles",
    prefix = "profile_exec_",
    profiles_file = DataStorage:getSettingsDir() .. "/profiles.lua",
    profiles = nil,
    data = nil,
    updated = false,
}

function Profiles:init()
    Dispatcher:init()
    self.autoexec = G_reader_settings:readSetting("profiles_autoexec", {})
    self.ui.menu:registerToMainMenu(self)
    self:onDispatcherRegisterActions()
    self:onStart()
end

function Profiles:loadProfiles()
    if self.profiles then
        return
    end
    self.profiles = LuaSettings:open(self.profiles_file)
    self.data = self.profiles.data
    -- ensure profile name
    for k, v in pairs(self.data) do
        if not v.settings then
            v.settings = {}
        end
        if not v.settings.name then
            v.settings.name = k
            self.updated = true
        end
    end
    self:onFlushSettings()
end

function Profiles:onFlushSettings()
    if self.profiles and self.updated then
        self.profiles:flush()
        self.updated = false
    end
end

local function dispatcherRegisterProfile(name)
    Dispatcher:registerAction(Profiles.prefix..name,
        {category="none", event="ProfileExecute", arg=name, title=T(_("Profile %1"), name), general=true})
end

local function dispatcherUnregisterProfile(name)
    Dispatcher:removeAction(Profiles.prefix..name)
end

function Profiles:onDispatcherRegisterActions()
    self:loadProfiles()
    for k, v in pairs(self.data) do
        if v.settings.registered then
            dispatcherRegisterProfile(k)
        end
    end
end

function Profiles:addToMainMenu(menu_items)
    menu_items.profiles = {
        text = _("Profiles"),
        sub_item_table_func = function()
            return self:getSubMenuItems()
        end,
    }
end

function Profiles:getSubMenuItems()
    self:loadProfiles()
    local sub_item_table = {
        {
            text = _("New"),
            keep_menu_open = true,
            callback = function(touchmenu_instance)
                local function editCallback(new_name)
                    self.data[new_name] = { settings = { name = new_name } }
                    self.updated = true
                    touchmenu_instance.item_table = self:getSubMenuItems()
                    touchmenu_instance.page = 1
                    touchmenu_instance:updateItems()
                end
                self:editProfileName(editCallback)
            end,
        },
        {
            text = _("New with current book settings"),
            enabled = self.document ~= nil,
            keep_menu_open = true,
            callback = function(touchmenu_instance)
                local function editCallback(new_name)
                    self.data[new_name] = self:getProfileFromCurrentBookSettings(new_name)
                    self.updated = true
                    touchmenu_instance.item_table = self:getSubMenuItems()
                    touchmenu_instance.page = 1
                    touchmenu_instance:updateItems()
                end
                self:editProfileName(editCallback)
            end,
            separator = true,
        },
    }
    for k, v in ffiUtil.orderedPairs(self.data) do
        local sub_items = {
            ignored_by_menu_search = true,
            {
                text = _("Execute"),
                callback = function(touchmenu_instance)
                    touchmenu_instance:onClose()
                    self:onProfileExecute(k, { qm_show = false })
                end,
            },
            {
                text = _("Show as QuickMenu"),
                callback = function(touchmenu_instance)
                    touchmenu_instance:onClose()
                    self:onProfileExecute(k, { qm_show = true })
                end,
            },
            {
                text = _("Auto-execute"),
                checked_func = function()
                    for _, profiles in pairs(self.autoexec) do
                        if profiles[k] then
                            return true
                        end
                    end
                end,
                sub_item_table_func = function()
                    return {
                        {
                            text = _("Ask to execute"),
                            checked_func = function()
                                return v.settings.auto_exec_ask
                            end,
                            callback = function()
                                v.settings.auto_exec_ask = not v.settings.auto_exec_ask or nil
                                self.updated = true
                            end,
                        },
                        {
                            text_func = function()
                                local txt
                                if v.settings.auto_exec_promptly then
                                    txt = _("promptly")
                                elseif v.settings.auto_exec_delay then
                                    txt = string.format("%0.1f", v.settings.auto_exec_delay) .. " " .. C_("Time", "s")
                                else
                                    txt = _("default")
                                end
                                return T(_("Excecuting delay: %1"), txt)
                            end,
                            checked_func = function()
                                return v.settings.auto_exec_promptly or v.settings.auto_exec_delay ~= nil
                            end,
                            sub_item_table_func = function()
                                return {
                                    {
                                        text = _("promptly"),
                                        help_text =
_([[Enable this option to execute the profile before some other operations triggered by the event.
For example, with a trigger "on document closing" the profile will be executed before the document is closed.]]),
                                        checked_func = function()
                                            return v.settings.auto_exec_promptly
                                        end,
                                        radio = true,
                                        callback = function()
                                            if not v.settings.auto_exec_promptly then
                                                v.settings.auto_exec_promptly = true
                                                v.settings.auto_exec_delay = nil
                                                self.updated = true
                                            end
                                        end,
                                    },
                                    {
                                        text = _("default"),
                                        checked_func = function()
                                            return not (v.settings.auto_exec_promptly or v.settings.auto_exec_delay)
                                        end,
                                        radio = true,
                                        callback = function()
                                            if v.settings.auto_exec_promptly or v.settings.auto_exec_delay then
                                                v.settings.auto_exec_promptly = nil
                                                v.settings.auto_exec_delay = nil
                                                self.updated = true
                                            end
                                        end,
                                    },
                                    {
                                        text_func = function()
                                            return v.settings.auto_exec_delay
                                                and string.format("%0.1f", v.settings.auto_exec_delay) .. " " .. C_("Time", "s")
                                                 or _("custom")
                                        end,
                                        checked_func = function()
                                            return v.settings.auto_exec_delay ~= nil
                                        end,
                                        radio = true,
                                        callback = function(touchmenu_instance)
                                            local SpinWidget = require("ui/widget/spinwidget")
                                            UIManager:show(SpinWidget:new{
                                                title_text = _("Executing delay"),
                                                value = v.settings.auto_exec_delay or 3,
                                                value_min = 1,
                                                value_max = 10,
                                                value_hold_step = 0.1,
                                                precision = "%0.1f",
                                                unit = C_("Time", "s"),
                                                ok_always_enabled = true,
                                                callback = function(spin)
                                                    v.settings.auto_exec_promptly = nil
                                                    v.settings.auto_exec_delay = spin.value
                                                    self.updated = true
                                                    touchmenu_instance:updateItems()
                                                end,
                                            })
                                        end,
                                    },
                                }
                            end,
                            hold_callback = function(touchmenu_instance)
                                v.settings.auto_exec_promptly = nil
                                v.settings.auto_exec_delay = nil
                                self.updated = true
                                touchmenu_instance:updateItems()
                            end,
                        },
                        {
                            text_func = function()
                                local interval = v.settings.auto_exec_time_interval
                                return _("Execute within time interval") ..
                                    (interval and ": " .. interval[1] .. " - " .. interval[2] or "")
                            end,
                            checked_func = function()
                                return v.settings.auto_exec_time_interval and true
                            end,
                            sub_item_table_func = function()
                                local sub_sub_item_table = {}
                                local points = { _("start: "), _("end: ") }
                                local titles = { _("Set start time"), _("Set end time") }
                                for i, point in ipairs(points) do
                                    sub_sub_item_table[i] = {
                                        text_func = function()
                                            local interval = v.settings.auto_exec_time_interval
                                            return point .. (interval and interval[i] or "--:--")
                                        end,
                                        keep_menu_open = true,
                                        callback = function(touchmenu_instance)
                                            local interval = v.settings.auto_exec_time_interval
                                            local time_str = interval and interval[i] or os.date("%H:%M")
                                            local h, m = time_str:match("(%d+):(%d+)")
                                            UIManager:show(DateTimeWidget:new{
                                                title_text = titles[i],
                                                info_text = _("Enter time in hours and minutes."),
                                                hour = tonumber(h),
                                                min = tonumber(m),
                                                ok_text = _("Set time"),
                                                callback = function(new_time)
                                                    local str = string.format("%02d:%02d", new_time.hour, new_time.min)
                                                    if interval then
                                                        v.settings.auto_exec_time_interval[i] = str
                                                    else
                                                        v.settings.auto_exec_time_interval = { str, str }
                                                    end
                                                    self.updated = true
                                                    touchmenu_instance:updateItems()
                                                end,
                                            })
                                        end,
                                    }
                                end
                                return sub_sub_item_table
                            end,
                            hold_callback = function(touchmenu_instance)
                                v.settings.auto_exec_time_interval = nil
                                self.updated = true
                                touchmenu_instance:updateItems()
                            end,
                            separator = true,
                        },
                        self:genAutoExecMenuItem(_("on KOReader start"), "Start", k),
                        self:genAutoExecMenuItem(_("on wake-up"), "Resume", k),
                        self:genAutoExecMenuItem(_("on exiting sleep screen"), "OutOfScreenSaver", k),
                        self:genAutoExecMenuItem(_("on read timer expiry"), "ReadTimerExpired", k),
                        self:genAutoExecMenuItem(_("on rotation"), "SetRotationMode", k),
                        self:genAutoExecMenuItem(_("on showing folder"), "PathChanged", k, true),
                        -- separator
                        self:genAutoExecMenuItem(_("on book opening"), "ReaderReadyAll", k),
                        self:genAutoExecMenuItem(_("on book closing"), "CloseDocumentAll", k),
                        max_per_page = 11,
                    }
                end,
                hold_callback = function(touchmenu_instance)
                    for event, profiles in pairs(self.autoexec) do
                        if profiles[k] then
                            util.tableRemoveValue(self.autoexec, event, k)
                        end
                    end
                    touchmenu_instance:updateItems()
                end,
            },
            {
                text = _("Show notification on executing"),
                checked_func = function()
                    return v.settings.notify
                end,
                callback = function()
                    v.settings.notify = not v.settings.notify or nil
                    self.updated = true
                end,
                separator = true,
            },
            {
                text = _("Show in action list"),
                checked_func = function()
                    return v.settings.registered
                end,
                callback = function()
                    if v.settings.registered then
                        dispatcherUnregisterProfile(k)
                        UIManager:broadcastEvent(Event:new("DispatcherActionNameChanged",
                            { old_name = self.prefix..k, new_name = nil }))
                        v.settings.registered = nil
                    else
                        dispatcherRegisterProfile(k)
                        v.settings.registered = true
                    end
                    self.updated = true
                end,
            },
            {
                text_func = function()
                    return T(_("Edit actions: (%1)"), Dispatcher:menuTextFunc(v))
                end,
                sub_item_table_func = function()
                    local edit_actions_sub_items = {}
                    Dispatcher:addSubMenu(self, edit_actions_sub_items, self.data, k)
                    return edit_actions_sub_items
                end,
                separator = true,
            },
            {
                text = T(_("Rename: %1"), k),
                keep_menu_open = true,
                callback = function(touchmenu_instance)
                    local function editCallback(new_name)
                        self.data[new_name] = util.tableDeepCopy(v)
                        self.data[new_name].settings.name = new_name
                        self:updateAutoExec(k, new_name)
                        if v.settings.registered then
                            dispatcherUnregisterProfile(k)
                            dispatcherRegisterProfile(new_name)
                            UIManager:broadcastEvent(Event:new("DispatcherActionNameChanged",
                                { old_name = self.prefix..k, new_name = self.prefix..new_name }))
                        end
                        self.data[k] = nil
                        self.updated = true
                        touchmenu_instance.item_table = self:getSubMenuItems()
                        touchmenu_instance:updateItems()
                        table.remove(touchmenu_instance.item_table_stack)
                      end
                    self:editProfileName(editCallback, k)
                end,
            },
            {
                text = _("Duplicate"),
                keep_menu_open = true,
                callback = function(touchmenu_instance)
                    local function editCallback(new_name)
                        self.data[new_name] = util.tableDeepCopy(v)
                        self.data[new_name].settings.name = new_name
                        if v.settings.registered then
                            dispatcherRegisterProfile(new_name)
                        end
                        self.updated = true
                        touchmenu_instance.item_table = self:getSubMenuItems()
                        touchmenu_instance:updateItems()
                        table.remove(touchmenu_instance.item_table_stack)
                      end
                    self:editProfileName(editCallback, k)
                end,
            },
            {
                text = _("Delete"),
                keep_menu_open = true,
                callback = function(touchmenu_instance)
                    UIManager:show(ConfirmBox:new{
                        text = _("Do you want to delete this profile?"),
                        ok_text = _("Delete"),
                        ok_callback = function()
                            self:updateAutoExec(k)
                            if v.settings.registered then
                                dispatcherUnregisterProfile(k)
                                UIManager:broadcastEvent(Event:new("DispatcherActionNameChanged",
                                    { old_name = self.prefix..k, new_name = nil }))
                            end
                            self.data[k] = nil
                            self.updated = true
                            touchmenu_instance.item_table = self:getSubMenuItems()
                            touchmenu_instance:updateItems()
                            table.remove(touchmenu_instance.item_table_stack)
                        end,
                    })
                end,
                separator = true,
            },
        }
        table.insert(sub_item_table, {
            text_func = function()
                return (v.settings.show_as_quickmenu and "\u{F0CA} " or "\u{F144} ") .. k
            end,
            sub_item_table = sub_items,
        })
    end
    return sub_item_table
end

function Profiles:onProfileExecute(name, exec_props)
    Dispatcher:execute(self.data[name], exec_props)
end

function Profiles:editProfileName(editCallback, old_name)
    local name_input
    name_input = InputDialog:new{
        title =  _("Enter profile name"),
        input = old_name,
        buttons = {{
            {
                text = _("Cancel"),
                id = "close",
                callback = function()
                    UIManager:close(name_input)
                end,
            },
            {
                text = _("Save"),
                callback = function()
                    local new_name = name_input:getInputText()
                    if new_name == "" or new_name == old_name then return end
                    UIManager:close(name_input)
                    if self.data[new_name] then
                        UIManager:show(InfoMessage:new{
                            text = T(_("Profile already exists: %1"), new_name),
                        })
                    else
                        editCallback(new_name)
                    end
                end,
            },
        }},
    }
    UIManager:show(name_input)
    name_input:onShowKeyboard()
end

function Profiles:getProfileFromCurrentBookSettings(new_name)
    local document_settings
    if self.ui.rolling then
        document_settings = {
            "rotation_mode",
            "set_font",
            "font_size",
            "font_gamma",
            "font_base_weight",
            "font_hinting",
            "font_kerning",
            "word_spacing",
            "word_expansion",
            "visible_pages",
            "h_page_margins",
            "t_page_margin",
            "b_page_margin",
            "sync_t_b_page_margins",
            "view_mode",
            "block_rendering_mode",
            "render_dpi",
            "line_spacing",
            "embedded_css",
            "embedded_fonts",
            "smooth_scaling",
            "nightmode_images",
            "status_line",
        }
    else
        document_settings = {
            "rotation_mode",
            "kopt_text_wrap",
            "kopt_trim_page",
            "kopt_page_margin",
            "kopt_zoom_overlap_h",
            "kopt_zoom_overlap_v",
            "kopt_zoom_mode_type",
            "kopt_zoom_mode_genus",
            "kopt_zoom_range_number",
            "kopt_zoom_factor",
            "kopt_zoom_direction",
            "kopt_page_scroll",
            "kopt_line_spacing",
            "kopt_font_size",
            "kopt_contrast",
            "kopt_quality",
            "kopt_max_columns",
        }
    end
    local setting_needs_arg = {
        ["sync_t_b_page_margins"] = true,
        ["view_mode"]             = true,
        ["embedded_css"]          = true,
        ["embedded_fonts"]        = true,
        ["smooth_scaling"]        = true,
        ["nightmode_images"]      = true,
        ["kopt_trim_page"]        = true,
        ["kopt_zoom_mode_genus"]  = true,
        ["kopt_zoom_mode_type"]   = true,
        ["kopt_page_scroll"]      = true,
    }

    local profile = { settings = { name = new_name, order = document_settings } }
    for _, v in ipairs(document_settings) do
        -- document configurable settings do not have prefixes
        local value = self.document.configurable[v:gsub("^kopt_", "")]
        if setting_needs_arg[v] then
            value = Dispatcher:getArgFromValue(v, value)
        end
        profile[v] = value
    end
    if self.ui.rolling then
        profile["set_font"] = self.ui.font.font_face -- not in configurable settings
    end
    return profile
end

function Profiles:onDispatcherActionNameChanged(action)
    for _, profile in pairs(self.data) do
        if profile[action.old_name] ~= nil then
            if profile.settings and profile.settings.order then
                for i, action_in_order in ipairs(profile.settings.order) do
                    if action_in_order == action.old_name then
                        if action.new_name then
                            profile.settings.order[i] = action.new_name
                        else
                            table.remove(profile.settings.order, i)
                            if #profile.settings.order < 2 then
                                profile.settings.order = nil
                            end
                        end
                        break
                    end
                end
            end
            profile[action.old_name] = nil
            if action.new_name then
                profile[action.new_name] = true
            end
            self.updated = true
        end
    end
end

function Profiles:onDispatcherActionValueChanged(action)
    for _, profile in pairs(self.data) do
        if profile[action.name] == action.old_value then
            profile[action.name] = action.new_value
            if action.new_value == nil then
                if profile.settings and profile.settings.order then
                    for i, action_in_order in ipairs(profile.settings.order) do
                        if action_in_order == action.name then
                            table.remove(profile.settings.order, i)
                            if #profile.settings.order < 2 then
                                profile.settings.order = nil
                            end
                            break
                        end
                    end
                end
            end
            self.updated = true
        end
    end
end

-- AutoExec

function Profiles:updateAutoExec(old_name, new_name)
    for event, profiles in pairs(self.autoexec) do
        local old_value
        for profile_name in pairs(profiles) do
            if profile_name == old_name then
                old_value = profiles[old_name]
                profiles[old_name] = nil
                break
            end
        end
        if old_value then
            if new_name then
                profiles[new_name] = old_value
            else
                if next(profiles) == nil then
                    self.autoexec[event] = nil
                end
            end
        end
    end
end

function Profiles:genAutoExecMenuItem(text, event, profile_name, separator)
    if event == "SetRotationMode" then
        return self:genAutoExecSetRotationModeMenuItem(text, event, profile_name, separator)
    elseif event == "PathChanged" then
        return self:genAutoExecPathChangedMenuItem(text, event, profile_name, separator)
    elseif event == "ReaderReadyAll" or event == "CloseDocumentAll" then
        return self:genAutoExecDocConditionalMenuItem(text, event, profile_name, separator)
    end
    return {
        text = text,
        enabled_func = function()
            if event == "Resume" then
                local screensaver_delay = G_reader_settings:readSetting("screensaver_delay")
                return screensaver_delay == nil or screensaver_delay == "disable"
            end
            return true
        end,
        checked_func = function()
            return util.tableGetValue(self.autoexec, event, profile_name)
        end,
        callback = function()
            if util.tableGetValue(self.autoexec, event, profile_name) then
                util.tableRemoveValue(self.autoexec, event, profile_name)
            else
                util.tableSetValue(self.autoexec, true, event, profile_name)
                if event == "ReaderReady" or event == "CloseDocument" then
                    -- "always" is checked, clear all conditional triggers
                    util.tableRemoveValue(self.autoexec, event .. "All", profile_name)
                end
            end
        end,
        separator = separator,
    }
end

function Profiles:genAutoExecSetRotationModeMenuItem(text, event, profile_name, separator)
    return {
        text = text,
        checked_func = function()
            return util.tableGetValue(self.autoexec, event, profile_name) and true
        end,
        sub_item_table_func = function()
            local sub_item_table = {}
            local optionsutil = require("ui/data/optionsutil")
            for i, mode in ipairs(optionsutil.rotation_modes) do
                sub_item_table[i] = {
                    text = optionsutil.rotation_labels[i],
                    checked_func = function()
                        return util.tableGetValue(self.autoexec, event, profile_name, mode)
                    end,
                    callback = function()
                        if util.tableGetValue(self.autoexec, event, profile_name, mode) then
                            util.tableRemoveValue(self.autoexec, event, profile_name, mode)
                        else
                            util.tableSetValue(self.autoexec, true, event, profile_name, mode)
                        end
                    end,
                }
            end
            return sub_item_table
        end,
        hold_callback = function(touchmenu_instance)
            util.tableRemoveValue(self.autoexec, event, profile_name)
            touchmenu_instance:updateItems()
        end,
        separator = separator,
    }
end

function Profiles:genAutoExecPathChangedMenuItem(text, event, profile_name, separator)
    return {
        text = text,
        checked_func = function()
            return util.tableGetValue(self.autoexec, event, profile_name) and true
        end,
        sub_item_table_func = function()
            local conditions = {
                { _("if folder path contains"), "has" },
                { _("if folder path does not contain"), "has_not" },
                { _("if folder path is equal"), "is_equal" },
                { _("if folder path is not equal"), "is_not_equal" },
            }
            local sub_item_table = {}
            for i, mode in ipairs(conditions) do
                local condition = mode[2]
                sub_item_table[i] = {
                    text_func = function()
                        local txt = mode[1]
                        local value = util.tableGetValue(self.autoexec, event, profile_name, condition)
                        return value and txt .. ": " .. value or txt
                    end,
                    checked_func = function()
                        return util.tableGetValue(self.autoexec, event, profile_name, condition)
                    end,
                    check_callback_updates_menu = true,
                    callback = function(touchmenu_instance)
                        local dialog
                        local buttons = {{
                            {
                                text = _("Current folder"),
                                callback = function()
                                    local curr_path = self.ui.file_chooser and self.ui.file_chooser.path or self.ui:getLastDirFile()
                                    dialog:addTextToInput(curr_path)
                                end,
                            },
                        }}
                        table.insert(buttons, {
                            {
                                text = _("Cancel"),
                                id = "close",
                                callback = function()
                                    UIManager:close(dialog)
                                end,
                            },
                            {
                                text = _("Save"),
                                callback = function()
                                    local txt = dialog:getInputText()
                                    if txt == "" then
                                        util.tableRemoveValue(self.autoexec, event, profile_name, condition)
                                    else
                                        util.tableSetValue(self.autoexec, txt, event, profile_name, condition)
                                    end
                                    UIManager:close(dialog)
                                    touchmenu_instance:updateItems()
                                end,
                            },
                        })
                        dialog = InputDialog:new{
                            title =  _("Enter text contained in folder path"),
                            input = util.tableGetValue(self.autoexec, event, profile_name, condition),
                            buttons = buttons,
                        }
                        UIManager:show(dialog)
                        dialog:onShowKeyboard()
                    end,
                    hold_callback = function(touchmenu_instance)
                        util.tableRemoveValue(self.autoexec, event, profile_name, condition)
                        touchmenu_instance:updateItems()
                    end,
                }
            end
            return sub_item_table
        end,
        hold_callback = function(touchmenu_instance)
            util.tableRemoveValue(self.autoexec, event, profile_name)
            touchmenu_instance:updateItems()
        end,
        separator = separator,
    }
end

function Profiles:genAutoExecDocConditionalMenuItem(text, event, profile_name, separator)
    local event_always = event:gsub("All", "")
    return {
        text = text,
        checked_func = function()
            return (util.tableGetValue(self.autoexec, event_always, profile_name) or util.tableGetValue(self.autoexec, event, profile_name)) and true
        end,
        sub_item_table_func = function()
            local conditions = {
                { _("if device orientation is"), "orientation" },
                { _("if book metadata contains"), "doc_props" },
                { _("if book file path contains"), "filepath" },
                { _("if book is in collections"), "collections" },
                { _("and if book is new"), "is_new" },
            }
            local sub_item_table = {
                self:genAutoExecMenuItem(_("always"), event_always, profile_name, true),
                -- separator
                {
                    text = conditions[1][1], -- orientation
                    enabled_func = function()
                        return not util.tableGetValue(self.autoexec, event_always, profile_name)
                    end,
                    checked_func = function()
                        return util.tableGetValue(self.autoexec, event, profile_name, conditions[1][2]) and true
                    end,
                    sub_item_table_func = function()
                        local condition = conditions[1][2]
                        local sub_item_table = {}
                        local optionsutil = require("ui/data/optionsutil")
                        for i, mode in ipairs(optionsutil.rotation_modes) do
                            sub_item_table[i] = {
                                text = optionsutil.rotation_labels[i],
                                checked_func = function()
                                    return util.tableGetValue(self.autoexec, event, profile_name, condition, mode)
                                end,
                                callback = function()
                                    if util.tableGetValue(self.autoexec, event, profile_name, condition, mode) then
                                        util.tableRemoveValue(self.autoexec, event, profile_name, condition, mode)
                                    else
                                        util.tableSetValue(self.autoexec, true, event, profile_name, condition, mode)
                                    end
                                end,
                            }
                        end
                        return sub_item_table
                    end,
                    hold_callback = function(touchmenu_instance)
                        util.tableRemoveValue(self.autoexec, event, profile_name, conditions[1][2])
                        touchmenu_instance:updateItems()
                    end,
                },
                {
                    text = conditions[2][1], -- doc_props
                    enabled_func = function()
                        return not util.tableGetValue(self.autoexec, event_always, profile_name)
                    end,
                    checked_func = function()
                        return util.tableGetValue(self.autoexec, event, profile_name, conditions[2][2]) and true
                    end,
                    sub_item_table_func = function()
                        local condition = conditions[2][2]
                        local sub_item_table = {}
                        for i, prop in ipairs(self.ui.bookinfo.props) do
                            sub_item_table[i] = {
                                text_func = function()
                                    local title = self.ui.bookinfo.prop_text[prop]:lower()
                                    local txt = util.tableGetValue(self.autoexec, event, profile_name, condition, prop)
                                    return txt and title .. " " .. txt or title:sub(1, -2)
                                end,
                                checked_func = function()
                                    return util.tableGetValue(self.autoexec, event, profile_name, condition, prop) and true
                                end,
                                check_callback_updates_menu = true,
                                callback = function(touchmenu_instance)
                                    local dialog
                                    local buttons = self.document == nil and {} or {{
                                        {
                                            text = _("Current book"),
                                            enabled_func = function()
                                                return prop == "title" or self.ui.doc_props[prop] ~= nil
                                            end,
                                            callback = function()
                                                local txt = self.ui.doc_props[prop == "title" and "display_title" or prop]
                                                dialog:addTextToInput(txt)
                                            end,
                                        },
                                    }}
                                    table.insert(buttons, {
                                        {
                                            text = _("Cancel"),
                                            id = "close",
                                            callback = function()
                                                UIManager:close(dialog)
                                            end,
                                        },
                                        {
                                            text = _("Save"),
                                            callback = function()
                                                local txt = dialog:getInputText()
                                                if txt == "" then
                                                    util.tableRemoveValue(self.autoexec, event, profile_name, condition, prop)
                                                else
                                                    util.tableSetValue(self.autoexec, txt, event, profile_name, condition, prop)
                                                end
                                                UIManager:close(dialog)
                                                touchmenu_instance:updateItems()
                                            end,
                                        },
                                    })
                                    dialog = InputDialog:new{
                                        title =  _("Enter text contained in:") .. " " .. self.ui.bookinfo.prop_text[prop]:sub(1, -2),
                                        input = util.tableGetValue(self.autoexec, event, profile_name, condition, prop),
                                        buttons = buttons,
                                    }
                                    UIManager:show(dialog)
                                    dialog:onShowKeyboard()
                                end,
                                hold_callback = function(touchmenu_instance)
                                    util.tableRemoveValue(self.autoexec, event, profile_name, condition, prop)
                                    touchmenu_instance:updateItems()
                                end,
                            }
                        end
                        return sub_item_table
                    end,
                    hold_callback = function(touchmenu_instance)
                        util.tableRemoveValue(self.autoexec, event, profile_name, conditions[2][2])
                        touchmenu_instance:updateItems()
                    end,
                },
                {
                    text_func = function() -- filepath
                        local txt = conditions[3][1]
                        local value = util.tableGetValue(self.autoexec, event, profile_name, conditions[3][2])
                        return value and txt .. ": " .. value or txt
                    end,
                    enabled_func = function()
                        return not util.tableGetValue(self.autoexec, event_always, profile_name)
                    end,
                    checked_func = function()
                        return util.tableGetValue(self.autoexec, event, profile_name, conditions[3][2]) and true
                    end,
                    check_callback_updates_menu = true,
                    callback = function(touchmenu_instance)
                        local condition = conditions[3][2]
                        local dialog
                        local buttons = self.document == nil and {} or {{
                            {
                                text = _("Current book"),
                                callback = function()
                                    dialog:addTextToInput(self.document.file)
                                end,
                            },
                        }}
                        table.insert(buttons, {
                            {
                                text = _("Cancel"),
                                id = "close",
                                callback = function()
                                    UIManager:close(dialog)
                                end,
                            },
                            {
                                text = _("Save"),
                                callback = function()
                                    local txt = dialog:getInputText()
                                    if txt == "" then
                                        util.tableRemoveValue(self.autoexec, event, profile_name, condition)
                                    else
                                        util.tableSetValue(self.autoexec, txt, event, profile_name, condition)
                                    end
                                    UIManager:close(dialog)
                                    touchmenu_instance:updateItems()
                                end,
                            },
                        })
                        dialog = InputDialog:new{
                            title =  _("Enter text contained in file path"),
                            input = util.tableGetValue(self.autoexec, event, profile_name, condition),
                            buttons = buttons,
                        }
                        UIManager:show(dialog)
                        dialog:onShowKeyboard()
                    end,
                    hold_callback = function(touchmenu_instance)
                        util.tableRemoveValue(self.autoexec, event, profile_name, conditions[3][2])
                        touchmenu_instance:updateItems()
                    end,
                },
                {
                    text_func = function() -- collections
                        local txt = conditions[4][1]
                        local collections = util.tableGetValue(self.autoexec, event, profile_name, conditions[4][2])
                        if collections then
                            local collections_nb = util.tableSize(collections)
                            return txt .. ": " ..
                                (collections_nb == 1 and self.ui.collections:getCollectionTitle(next(collections))
                                                      or "(" .. collections_nb .. ")")
                        end
                        return txt
                    end,
                    enabled_func = function()
                        return not util.tableGetValue(self.autoexec, event_always, profile_name)
                    end,
                    checked_func = function()
                        return util.tableGetValue(self.autoexec, event, profile_name, conditions[4][2]) and true
                    end,
                    check_callback_updates_menu = true,
                    callback = function(touchmenu_instance)
                        local condition = conditions[4][2]
                        local collections = util.tableGetValue(self.autoexec, event, profile_name, condition)
                        local caller_callback = function(selected_collections)
                            if next(selected_collections) == nil then
                                util.tableRemoveValue(self.autoexec, event, profile_name, condition)
                            else
                                util.tableSetValue(self.autoexec, selected_collections, event, profile_name, condition)
                            end
                            touchmenu_instance:updateItems()
                        end
                        self.ui.collections:onShowCollList(collections or {}, caller_callback, true)
                    end,
                    hold_callback = function(touchmenu_instance)
                        util.tableRemoveValue(self.autoexec, event, profile_name, conditions[4][2])
                        touchmenu_instance:updateItems()
                    end,
                    separator = true,
                },
                event == "ReaderReadyAll" and {
                    text = conditions[5][1], -- new
                    enabled_func = function()
                        return not util.tableGetValue(self.autoexec, event_always, profile_name)
                    end,
                    checked_func = function()
                        return util.tableGetValue(self.autoexec, event, profile_name, conditions[5][2]) and true
                    end,
                    callback = function(touchmenu_instance)
                        local condition = conditions[5][2]
                        if util.tableGetValue(self.autoexec, event, profile_name, condition) then
                            util.tableRemoveValue(self.autoexec, event, profile_name, condition)
                        else
                            util.tableSetValue(self.autoexec, true, event, profile_name, condition)
                        end
                    end,
                    hold_callback = function(touchmenu_instance)
                        util.tableRemoveValue(self.autoexec, event, profile_name, conditions[5][2])
                        touchmenu_instance:updateItems()
                    end,
                } or nil,
            }
            return sub_item_table
        end,
        hold_callback = function(touchmenu_instance)
            util.tableRemoveValue(self.autoexec, event_always, profile_name)
            util.tableRemoveValue(self.autoexec, event, profile_name)
            touchmenu_instance:updateItems()
        end,
        separator = separator,
    }
end

function Profiles:onStart() -- local event
    if not autostart_done then
        self:executeAutoExecEvent("Start")
        autostart_done = true
    end
end

function Profiles:onResume() -- global
    self:executeAutoExecEvent("Resume")
end

function Profiles:onOutOfScreenSaver() -- global
    self:executeAutoExecEvent("OutOfScreenSaver")
end

function Profiles:onReadTimerExpired() -- global by ReadTimer plugin
    self:executeAutoExecEvent("ReadTimerExpired")
end

function Profiles:onSetRotationMode(mode) -- global
    local event = "SetRotationMode"
    if self.autoexec[event] == nil then return end
    for profile_name, modes in pairs(self.autoexec[event]) do
        if modes[mode] then
            if self.ui.config then -- close bottom menu to let Dispatcher execute profile
                self.ui.config:onCloseConfigMenu()
            end
            self:executeAutoExec(profile_name)
        end
    end
end

function Profiles:onPathChanged(path) -- global
    local event = "PathChanged"
    if self.autoexec[event] == nil then return end
    local function is_match(txt, pattern)
        for str in util.gsplit(pattern, ",") do -- comma separated patterns are allowed
            if util.stringSearch(txt, util.trim(str)) ~= 0 then
                return true
            end
        end
    end
    for profile_name, conditions in pairs(self.autoexec[event]) do
        local do_execute
        for condition, trigger in pairs(conditions) do
            if condition == "has" then
                do_execute = is_match(path, trigger)
            elseif condition == "has_not" then
                do_execute = not is_match(path, trigger)
            elseif condition == "is_equal" then
                do_execute = path == trigger
            elseif condition == "is_not_equal" then
                do_execute = path ~= trigger
            end
            if do_execute then
                break
            end
        end
        if do_execute then
            self:executeAutoExec(profile_name)
        end
    end
end

function Profiles:onReaderReady() -- global
    if not self.ui.reloading then
        self:executeAutoExecEvent("ReaderReady")
        self:executeAutoExecDocConditional("ReaderReadyAll")
    end
end

function Profiles:onCloseDocument() -- global
    if not self.ui.reloading then
        self:executeAutoExecEvent("CloseDocument")
        self:executeAutoExecDocConditional("CloseDocumentAll")
    end
end

function Profiles:executeAutoExecEvent(event)
    if self.autoexec[event] == nil then return end
    for profile_name in pairs(self.autoexec[event]) do
        self:executeAutoExec(profile_name, event)
    end
end

function Profiles:executeAutoExec(profile_name, event)
    local profile = self.data[profile_name]
    if profile == nil then return end
    if profile.settings.auto_exec_time_interval then
        local now = os.date("%H:%M")
        local start_time, end_time = unpack(profile.settings.auto_exec_time_interval)
        local do_execute
        if start_time < end_time then
            do_execute = start_time <= now and now <= end_time
        else
            do_execute = (start_time <= now and now <= "23:59") or ("00:00" <= now and now <= end_time)
        end
        if not do_execute then return end
    end
    local function execute_profile()
        if profile.settings.auto_exec_promptly then
            logger.dbg("Profiles - auto executing promptly:", profile_name)
            Dispatcher:execute(profile)
        elseif profile.settings.auto_exec_delay then
            logger.dbg("Profiles - auto executing delayed:", profile_name, profile.settings.auto_exec_delay)
            UIManager:scheduleIn(profile.settings.auto_exec_delay, function()
                Dispatcher:execute(profile)
            end)
        else
            logger.dbg("Profiles - auto executing:", profile_name)
            if event == "CloseDocument" or event == "CloseDocumentAll" then
                UIManager:tickAfterNext(function()
                    Dispatcher:execute(profile)
                end)
            else
                UIManager:nextTick(function()
                    Dispatcher:execute(profile)
                end)
            end
        end
    end
    if profile.settings.auto_exec_ask then
        UIManager:show(ConfirmBox:new{
            text = _("Do you want to execute profile?") .. "\n\n" .. profile_name .. "\n",
            ok_text = _("Execute"),
            ok_callback = function()
                execute_profile()
            end,
        })
    else
        execute_profile()
    end
end

function Profiles:executeAutoExecDocConditional(event)
    if self.autoexec[event] == nil then return end
    local function is_match(txt, pattern)
        for str in util.gsplit(pattern, ",") do
            if util.stringSearch(txt, util.trim(str)) ~= 0 then
                return true
            end
        end
    end
    for profile_name, conditions in pairs(self.autoexec[event]) do
        if self.data[profile_name] then
            local do_execute
            if not conditions.is_new or self.document.is_new then
                for condition, trigger in pairs(conditions) do
                    if condition == "orientation" then
                        local mode = Screen:getRotationMode()
                        do_execute = trigger[mode]
                    elseif condition == "doc_props" then
                        if self.document then
                            for prop_name, pattern in pairs(trigger) do
                                local prop = self.ui.doc_props[prop_name == "title" and "display_title" or prop_name]
                                do_execute = is_match(prop, pattern)
                                if do_execute then
                                    break -- any prop match is enough
                                end
                            end
                        end
                    elseif condition == "filepath" then
                        if self.document then
                            do_execute = is_match(self.document.file, trigger)
                        end
                    elseif condition == "collections" then
                        if self.document then
                            local ReadCollection = require("readcollection")
                            for collection_name in pairs(trigger) do
                                if ReadCollection:isFileInCollection(self.document.file, collection_name) then
                                    do_execute = true
                                    break -- any collection is enough
                                end
                            end
                        end
                    end
                    if do_execute then
                        break -- execute profile only once
                    end
                end
            end
            if do_execute then
                self:executeAutoExec(profile_name, event)
            end
        end
    end
end

return Profiles
