local Widget = require("ui/widget/widget")
local MultiInputDialog = require("ui/widget/multiinputdialog")
local KeyValuePage = require("ui/widget/keyvaluepage")
local UIManager = require("ui/uimanager")
local Screen = require("device").screen
local TimeVal = require("ui/timeval")
local DataStorage = require("datastorage")
local lfs = require("libs/libkoreader-lfs")
local DEBUG = require("dbg")
local T = require("ffi/util").template
local joinPath = require("ffi/util").joinPath
local _ = require("gettext")
local util = require("util")
local tableutil = require("tableutil")
local ReadHistory = require("readhistory")
local DocSettings = require("docsettings")
local ReaderProgress = require("readerprogress")
local statistics_dir = DataStorage:getDataDir() .. "/statistics/"

-- a copy of page_max_read_sec
local page_max_time

local ReaderStatistics = Widget:extend{
    last_time = nil,
    page_min_read_sec = 5,
    page_max_read_sec = 90,
    current_period = 0,
    pages_current_period = 0,
    is_enabled = nil,
    data = {
        title = "",
        authors = "",
        language = "",
        series = "",
        performance_in_pages = {},
        total_time_in_sec = 0,
        highlights = 0,
        notes = 0,
        pages = 0,
    },
}

local shortDayOfWeekTranslation = {
    ["Mon"] = _("Mon"),
    ["Tue"] = _("Tue"),
    ["Wed"] = _("Wed"),
    ["Thu"] = _("Thu"),
    ["Fri"] = _("Fri"),
    ["Sat"] = _("Sat"),
    ["Sun"] = _("Sun"),
}

local monthTranslation = {
    ["January"] = _("January"),
    ["February"] = _("February"),
    ["March"] = _("March"),
    ["April"] = _("April"),
    ["May"] = _("May"),
    ["June"] = _("June"),
    ["July"] = _("July"),
    ["August"] = _("August"),
    ["September"] = _("September"),
    ["October"] = _("October"),
    ["November"] = _("November"),
    ["December"] = _("December"),
}

function ReaderStatistics:isDocless()
    return self.ui == nil or self.ui.document == nil
end

function ReaderStatistics:init()
    if not self:isDocless() and self.ui.document.is_pic then
        return
    end

    self.ui.menu:registerToMainMenu(self)
    self.current_period = 0
    self.pages_current_period = 0

    local settings = G_reader_settings:readSetting("statistics") or {}
    self.page_min_read_sec = tonumber(settings.min_sec)
    self.page_max_read_sec = tonumber(settings.max_sec)
    -- use later in getDatesFromBook
    page_max_time = self.page_max_read_sec
    self.is_enabled = not (settings.is_enabled == false)
    self.last_time = TimeVal:now()
end

function ReaderStatistics:getBookProperties()
    local props = self.view.document:getProps()
    if props.title == "No document" or props.title == "" then
        -- FIXME: sometimes crengine returns "No document", try one more time
        props = self.view.document:getProps()
    end
    return props
end

function ReaderStatistics:initData(config)
    if self:isDocless() or not self.is_enabled then
        return
    end
    -- first execution
    if not self.data then
        self.data = { performance_in_pages= {} }
        self:inplaceMigration();  -- first time merge data
    end

    local book_properties = self:getBookProperties()
    self.data.title = book_properties.title
    self.data.authors = book_properties.authors
    self.data.language = book_properties.language
    self.data.series = book_properties.series

    self.data.pages = self.view.document:getPageCount()
    return
end

local function generateReadBooksTable(title, dates)
    local result = {}
    for k, v in tableutil.spairs(dates, function(t, a, b) return t[b].date < t[a].date end) do
        table.insert(result, {
            k,
            T(_("Pages (%1) Time: %2"), v.count, util.secondsToClock(v.read, false))
        })
    end
    return result
end

function ReaderStatistics:getStatisticEnabledMenuItem()
    return {
        text = _("Enabled"),
        checked_func = function() return self.is_enabled end,
        callback = function()
            -- if was enabled, have to save data to file
            if self.last_time and self.is_enabled and not self:isDocless() then
                self.ui.doc_settings:saveSetting("stats", self.data)
            end

            self.is_enabled = not self.is_enabled
            -- if was disabled have to get data from file
            if self.is_enabled and not self:isDocless() then
                self:initData(self.ui.doc_settings)
            end
            self:saveSettings()
        end,
    }
end

function ReaderStatistics:updateSettings()
    self.settings_dialog = MultiInputDialog:new {
        title = _("Statistics settings"),
        fields = {
            {
                text = "",
                input_type = "number",
                hint = T(_("Min seconds, default is 5. Current value: %1"),
                         self.page_min_read_sec),
            },
            {
                text = "",
                input_type = "number",
                hint = T(_("Max seconds, default is 90. Current value: %1"),
                         self.page_max_read_sec),
            },
        },
        buttons = {
            {
                {
                    text = _("Cancel"),
                    callback = function()
                        self.settings_dialog:onClose()
                        UIManager:close(self.settings_dialog)
                    end
                },
                {
                    text = _("Apply"),
                    callback = function()
                        self:saveSettings(MultiInputDialog:getFields())
                        self.settings_dialog:onClose()
                        UIManager:close(self.settings_dialog)
                    end
                },
            },
        },
        width = Screen:getWidth() * 0.95,
        height = Screen:getHeight() * 0.2,
        input_type = "number",
    }
    self.settings_dialog:onShowKeyboard()
    UIManager:show(self.settings_dialog)
end

function ReaderStatistics:addToMainMenu(menu_items)
    menu_items.statistics = {
        text = _("Reading statistics"),
        sub_item_table = {
            self:getStatisticEnabledMenuItem(),
            {
                text = _("Settings"),
                callback = function() self:updateSettings() end,
            },
            {
                text = _("Current book"),
                callback = function()
                    UIManager:show(KeyValuePage:new{
                        title = _("Statistics"),
                        kv_pairs = self:getCurrentStat(),
                    })
                end,
                enabled = not self:isDocless()
            },
            {
                text = _("All books"),
                callback = function()
                    local total_msg, kv_pairs = self:getTotalStats()
                    UIManager:show(KeyValuePage:new{
                        title = total_msg,
                        kv_pairs = kv_pairs,
                    })
                end
            },
            {
                text = _("Reading progress"),
                callback = function()
                    UIManager:show(ReaderProgress:new{
                        dates = self:getDatesFromAll(7, "daily_weekday"),
                        current_period = self.current_period,
                        current_pages = self.pages_current_period,
                    })
                end
            },
            {
                text = _("Time range"),
                sub_item_table = {
                    {
                        text = _("Last week"),
                        callback = function()
                            UIManager:show(KeyValuePage:new{
                                title = _("Last week"),
                                kv_pairs = generateReadBooksTable("", self:getDatesFromAll(7, "daily_weekday")),
                            })
                        end,
                    },
                    {
                        text = _("Last month by day"),
                        callback = function()
                            UIManager:show(KeyValuePage:new{
                                title = _("Last month by day"),
                                kv_pairs = generateReadBooksTable("", self:getDatesFromAll(30, "daily_weekday")),
                            })
                        end,
                    },
                    {
                        text = _("Last year by day"),
                        callback = function()
                            UIManager:show(KeyValuePage:new{
                                title = _("Last year by day"),
                                kv_pairs = generateReadBooksTable("", self:getDatesFromAll(365, "daily")),
                            })
                        end,
                    },
                    {
                        text = _("Last year by week"),
                        callback = function()
                            UIManager:show(KeyValuePage:new{
                                title = _("Last year by week"),
                                kv_pairs = generateReadBooksTable("", self:getDatesFromAll(365, "weekly")),
                            })
                        end,
                    },
                    {
                        text = _("Last 10 years by month"),
                        callback = function()
                            UIManager:show(KeyValuePage:new{
                                title = _("Last 10 years by month"),
                                kv_pairs = generateReadBooksTable("", self:getDatesFromAll(3650, "monthly")),
                            })
                        end,
                    },
                }
            },
        },
    }
end

function ReaderStatistics:getCurrentStat()
    local dates = {}
    local now_stamp = os.time()
    local now_t = os.date("*t")
    local from_begin_day = now_t.hour * 3600 + now_t.min * 60 + now_t.sec
    local start_today_time = now_stamp - from_begin_day
    local pages_today_period = 0
    local time_today_period = 0
    local last_period = 0
    local sorted_today_performance = {}
    local diff
    for k, v in pairs(self.data.performance_in_pages) do
        dates[os.date("%Y-%m-%d", k)] = true
        if k >= start_today_time then
            pages_today_period = pages_today_period + 1
            table.insert(sorted_today_performance, k)
        end
    end
    local total_days = util.tableSize(dates)
    local read_pages = util.tableSize(self.data.performance_in_pages)
    local current_page = self.view.state.page -- get current page from the view
    local avg_time_per_page = self.data.total_time_in_sec / read_pages

    table.sort(sorted_today_performance)
    for _, n in pairs(sorted_today_performance) do
        if last_period == 0 then
            last_period = n
            time_today_period = avg_time_per_page
        else
            diff = n - last_period
            if (diff <= page_max_time and diff > 0) then
                time_today_period = time_today_period + diff
            else
                time_today_period = time_today_period + avg_time_per_page
            end
            last_period = n
        end

    end
    return {
        { _("Current period"), util.secondsToClock(self.current_period, false) },
        { _("Current pages"), self.pages_current_period },
        { _("Today period"), util.secondsToClock(time_today_period, false) },
        { _("Today pages"), pages_today_period },
        { _("Time to read"), util.secondsToClock((self.data.pages - current_page) * avg_time_per_page, false) },
        { _("Total time"), util.secondsToClock(self.data.total_time_in_sec, false) },
        { _("Total highlights"), self.data.highlights },
        { _("Total notes"), self.data.notes },
        { _("Total days"), total_days },
        { _("Average time per page"), util.secondsToClock(avg_time_per_page, false) },
        { _("Read pages/Total pages"), read_pages .. "/" .. self.data.pages },
        { _("Percentage completed"), math.floor(read_pages / self.data.pages * 100 + 0.5) .. "%" }, -- adding 0.5 rounds to nearest integer with math.floor
    }
end

-- For backward compatibility
local function getDatesForBookOldFormat(book)
    local dates = {}

    for k, v in pairs(book.details) do
        local date_text = os.date("%Y-%m-%d", v.time)
        if not dates[date_text] then
            dates[date_text] = {
                date = v.time,
                read = v.read,
                count = 1
            }
        else
            dates[date_text] = {
                read = dates[date_text].read + v.read,
                count = dates[date_text].count + 1,
                date = dates[date_text].date
            }
        end
    end

    return generateReadBooksTable(book.title, dates)
end

-- sdays -> number of days to show
-- ptype -> daily - show daily without weekday name
--          daily_weekday - show daily with weekday name
--          weekly - show weekly
--          monthly - show monthly
function ReaderStatistics:getDatesFromAll(sdays, ptype)
    local dates = {}
    local sorted_performance_in_pages
    local diff
    local now_t = os.date("*t")
    local from_begin_day = now_t.hour *3600 + now_t.min*60 + now_t.sec
    local now_stamp = os.time()
    local one_day = 24 * 3600 -- one day in seconds
    local avg_time_per_page
    local period = now_stamp - ((sdays -1) * one_day) - from_begin_day
    for __, v in pairs(ReadHistory.hist) do
        local book_stats = DocSettings:open(v.file):readSetting('stats')
        if book_stats ~= nil then
            -- if current reading book
            if book_stats.title == self.data.title then
                book_stats = self.data
                local read_pages = util.tableSize(self.data.performance_in_pages)
                avg_time_per_page = self.data.total_time_in_sec / read_pages
            else
                avg_time_per_page = book_stats.total_time_in_sec / book_stats.pages
            end
            --zeros table sorted_performance_in_pages
            sorted_performance_in_pages = {}
            for k1, v1 in pairs(book_stats.performance_in_pages) do
                if k1 >= period then
                    table.insert(sorted_performance_in_pages, k1)
                end  --if period
            end --  for book_performance
            -- sort table by time (unix timestamp)
            local date_text
            table.sort(sorted_performance_in_pages)
            for i, n in pairs(sorted_performance_in_pages) do
                if ptype == "daily_weekday" then
                    date_text = string.format("%s (%s)",
                        os.date("%Y-%m-%d", n),
                        shortDayOfWeekTranslation[os.date("%a", n)])
                elseif ptype == "daily" then
                    date_text = os.date("%Y-%m-%d", n)
                elseif ptype == "weekly" then
                    date_text = T(_("%1 Week %2"), os.date("%Y "), os.date(" %W", n))
                elseif ptype == "monthly" then
                    date_text = monthTranslation[os.date("%B", n)] .. os.date(" %Y", n)
                else
                    date_text = os.date("%Y-%m-%d", n)
                end  --if ptype
                if not dates[date_text] then
                    dates[date_text] = {
                        -- first pages of day is set to average of all pages
                        read = avg_time_per_page,
                        date = n,
                        count = 1
                    }
                else
                    local entry = dates[date_text]
                    diff = n - entry.date
                    -- page_max_time
                    if (diff <= page_max_time and diff > 0) then
                        entry.read = entry.read + n - entry.date
                    else
                        --add average time if time > page_max_time
                        entry.read = avg_time_per_page + entry.read
                    end  --if diff
                    if diff < 0 then
                        entry.read = avg_time_per_page + entry.read
                    end
                    entry.date = n
                    entry.count = entry.count + 1
                end  --if not dates[]
            end  -- for sorted_performance_in_pages
        end  -- if book_status
    end  --for pairs(ReadHistory.hist)
    return dates
end

local function getDatesForBook(book)
    local dates = {}
    local sorted_performance_in_pages = {}
    local diff
    local read_pages = util.tableSize(book.performance_in_pages)
    for k, v in pairs(book.performance_in_pages) do
        table.insert(sorted_performance_in_pages, k)
    end
    -- sort table by time (unix timestamp)
    table.sort(sorted_performance_in_pages)
    for i, n in pairs(sorted_performance_in_pages) do
        local date_text = os.date("%Y-%m-%d", n)
        if not dates[date_text] then
            dates[date_text] = {
                -- first pages of day is set to average of all pages
                read = book.total_time_in_sec / read_pages,
                date = n,
                count = 1
            }
        else
            local entry = dates[date_text]
            diff = n - entry.date
            if diff <= page_max_time then
                entry.read = entry.read + n - entry.date
            else
                --add average time if time > page_max_time e.g longer break while reading
                entry.read = book.total_time_in_sec / read_pages + entry.read
            end
            entry.date = n
            entry.count = entry.count + 1
        end
    end
    return generateReadBooksTable(book.title, dates)
end

function ReaderStatistics:getTotalStats()
    local total_stats = {}
    if not self:isDocless() then
        -- empty title
        if self.data.title == "" then
            self.data.title = self.document.file:match("^.+/(.+)$")
        end
        total_stats = {
            {
                self.data.title,
                util.secondsToClock(self.data.total_time_in_sec, false),
                callback = function()
                    UIManager:show(KeyValuePage:new{
                        title = self.data.title,
                        kv_pairs = getDatesForBook(self.data),
                    })
                end,
            }
        }
    end
    -- find stats for all other books in history
    local proceded_titles, total_books_time = self:getStatisticsFromHistory(total_stats)
    total_books_time = total_books_time + self:getOldStatisticsFromDirectory(proceded_titles, total_stats)
    total_books_time = total_books_time + tonumber(self.data.total_time_in_sec)

    return T(_("Total hours read %1"),
             util.secondsToClock(total_books_time, false)),
           total_stats
end

function ReaderStatistics:getStatisticsFromHistory(total_stats)
    local titles = {}
    local total_books_time = 0
    for _, v in pairs(ReadHistory.hist) do
        local book_stats = DocSettings:open(v.file):readSetting('stats')
        -- empty title
        if book_stats and book_stats.title == "" then
            book_stats.title = v.file:match("^.+/(.+)$")
        end
        if book_stats and book_stats.total_time_in_sec > 0
            and book_stats.title ~= self.data.title then
            titles[book_stats.title] = true
            table.insert(total_stats, {
                book_stats.title,
                util.secondsToClock(book_stats.total_time_in_sec, false),
                callback = function()
                    UIManager:show(KeyValuePage:new{
                        title = book_stats.title,
                        kv_pairs = getDatesForBook(book_stats),
                    })
                end,
            })
            total_books_time = total_books_time + tonumber(book_stats.total_time_in_sec)
        end  --if book_stats
    end  --for pairs(ReadHistory.hist)
    return titles, total_books_time
end

-- For backward compatibility
function ReaderStatistics:getOldStatisticsFromDirectory(exlude_titles, total_stats)
    if lfs.attributes(statistics_dir, "mode") ~= "directory" then
        return 0
    end
    local total_books_time = 0
    for curr_file in lfs.dir(statistics_dir) do
        local path = statistics_dir .. curr_file
        if lfs.attributes(path, "mode") == "file" then
            local book_result = self:importFromFile(statistics_dir, curr_file)
            if book_result and book_result.total_time > 0
                    and book_result.title ~= self.data.title
                    and not exlude_titles[book_result.title] then
                table.insert(total_stats, {
                    book_result.title,
                    util.secondsToClock(book_result.total_time, false),
                    callback = function()
                        UIManager:show(KeyValuePage:new{
                            title = book_result.title,
                            kv_pairs = getDatesForBookOldFormat(book_result),
                        })
                    end,
                })
                total_books_time = total_books_time + tonumber(book_result.total_time)
            end
        end
    end
    return total_books_time
end

function ReaderStatistics:onPageUpdate(pageno)
    if self:isDocless() or not self.is_enabled then
        return
    end
    local curr_time = TimeVal:now()
    local diff_time = curr_time.sec - self.last_time.sec

    -- if last update was more then 10 minutes then current period set to 0
    if (diff_time > 600) then
        self.current_period = 0
        self.pages_current_period = 0
    end

    if diff_time >= self.page_min_read_sec and diff_time <= self.page_max_read_sec then
        self.current_period = self.current_period + diff_time
        self.pages_current_period = self.pages_current_period + 1
        self.data.total_time_in_sec = self.data.total_time_in_sec + diff_time
        self.data.performance_in_pages[curr_time.sec] = pageno
        -- we cannot save stats each time this is a page update event,
        -- because the self.data may not even be initialized when such a event
        -- comes, which will render a blank stats written into doc settings
        -- and all previous stats are totally wiped out.
        self.ui.doc_settings:saveSetting("stats", self.data)
    end

    self.last_time = curr_time
end

-- For backward compatibility
function ReaderStatistics:inplaceMigration()
    local oldData = self:importFromFile(statistics_dir, self.data.title .. ".stat")
    if oldData then
        for k, v in pairs(oldData.details) do
            self.data.performance_in_pages[v.time] = v.page
        end
    end
end

-- For backward compatibility
function ReaderStatistics:importFromFile(base_path, item)
    item = string.gsub(item, "^%s*(.-)%s*$", "%1") -- trim
    if item ~= ".stat" then
        local statistic_file = joinPath(base_path, item)
        if lfs.attributes(statistic_file, "mode") == "directory" then
            return
        end
        local ok, stored = pcall(dofile, statistic_file)
        if ok then
            return stored
        else
            DEBUG(stored)
        end
    end
end

function ReaderStatistics:onCloseDocument()
    if not self:isDocless() and self.last_time and self.is_enabled then
        self.ui.doc_settings:saveSetting("stats", self.data)
    end
end

function ReaderStatistics:onAddHighlight()
    self.data.highlights = self.data.highlights + 1
end

function ReaderStatistics:onAddNote()
    self.data.notes = self.data.notes + 1
end

-- in case when screensaver starts
function ReaderStatistics:onSaveSettings()
    self:saveSettings()
    if not self:isDocless() then
        self.ui.doc_settings:saveSetting("stats", self.data)
        self.current_period = 0
        self.pages_current_period = 0
    end
end

-- screensaver off
function ReaderStatistics:onResume()
    self.current_period = 0
    self.pages_current_period = 0

end

function ReaderStatistics:saveSettings(fields)
    if fields then
        self.page_min_read_sec = tonumber(fields[1])
        self.page_max_read_sec = tonumber(fields[2])
    end

    local settings = {
        min_sec = self.page_min_read_sec,
        max_sec = self.page_max_read_sec,
        is_enabled = self.is_enabled,
    }
    G_reader_settings:saveSetting("statistics", settings)
end

function ReaderStatistics:onReadSettings(config)
    self.data = config.data.stats
end

function ReaderStatistics:onReaderReady()
    -- we have correct page count now, do the actual initialization work
    self:initData()
end

return ReaderStatistics
