local Device = require("device")
local Dispatcher = require("dispatcher")
local KeyValuePage = require("ui/widget/keyvaluepage")
local Math = require("optmath")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local datetime = require("datetime")
local time = require("ui/time")
local util = require("util")
local _ = require("gettext")

local SystemStat = {
    start_time = time.realtime(),
    start_monotonic_time = time.boottime_or_realtime_coarse(),
    suspend_time = nil,
    resume_time = nil,
    wakeup_count = 0,
    sleep_count = 0,
    charge_count = 0,
    discharge_count = 0,
}

function SystemStat:init()
    if Device:isCervantes() or Device:isPocketBook() then
        self.storage_filter = "mmcblk"
    elseif Device:isKobo() then
        self.storage_filter = " /mnt/"
    elseif Device:isKindle() then
        self.storage_filter = "' /mnt/us$'"
    elseif Device:isSDL() then
        self.storage_filter = "/dev/sd"
    end

    -- Account for a start-up mid-charge
    local powerd = Device:getPowerDevice()
    if Device:hasAuxBattery() and powerd:isAuxBatteryConnected() then
        if powerd:isAuxCharging() and not powerd:isAuxCharged() then
            self.charge_count = self.charge_count + 1
        end
    else
        if powerd:isCharging() and not powerd:isCharged() then
            self.charge_count = self.charge_count + 1
        end
    end
end

function SystemStat:put(p)
    table.insert(self.kv_pairs, p)
end

function SystemStat:putSeparator()
    self.kv_pairs[#self.kv_pairs].separator = true
end

function SystemStat:appendCounters()
    local use_twelve_hour_clock = G_reader_settings:isTrue("twelve_hour_clock")
    self:put({
        _("KOReader started at"),
        datetime.secondsToDateTime(time.to_s(self.start_time), use_twelve_hour_clock, true)
    })
    if self.suspend_time then
       self:put({
           "  " .. _("Last suspend time"),
           datetime.secondsToDateTime(time.to_s(self.suspend_time), use_twelve_hour_clock, true)
        })
    end
    if self.resume_time then
        self:put({
            "  " .. _("Last resume time"),
           datetime.secondsToDateTime(time.to_s(self.resume_time), use_twelve_hour_clock, true)
        })
    end
    local uptime = time.boottime_or_realtime_coarse() - self.start_monotonic_time
    local suspend = 0
    if Device:canSuspend() then
        suspend = Device.total_suspend_time
    end
    local standby = 0
    if Device:canStandby() then
        standby = Device.total_standby_time
    end
    self:put({"  " .. _("Up time"),
            datetime.secondsToClockDuration("", time.to_s(uptime), false, true, true)})
    if Device:canSuspend() or Device:canStandby() then
        local awake = uptime - suspend - standby
        self:put({"  " .. _("Time spent awake"),
            datetime.secondsToClockDuration("", time.to_s(awake), false, true, true)
            .. " (" .. Math.round((awake / uptime) * 100) .. "%)"})
    end
    if Device:canSuspend() then
        self:put({"  " .. _("Time in suspend"),
            datetime.secondsToClockDuration("", time.to_s(suspend), false, true, true)
            .. " (" .. Math.round((suspend / uptime) * 100) .. "%)"})
    end
    if Device:canStandby() then
        self:put({"  " .. _("Time in standby"),
            datetime.secondsToClockDuration("", time.to_s(standby), false, true, true)
            .. " (" .. Math.round((standby / uptime) * 100) .. "%)"})
    end
    self:put({_("Counters"), ""})
    self:put({_("  wake-ups"), self.wakeup_count})
    -- @translators The number of "sleeps", that is the number of times the device has entered standby. This could also be translated as a rendition of a phrase like "entered sleep".
    self:put({_("  sleeps"), self.sleep_count})
    self:put({_("  charge cycles"), self.charge_count})
    self:put({_("  discharge cycles"), self.discharge_count})
end

local function systemInfo()
    local result = {}
    do
        local stat = io.open("/proc/stat", "r")
        if stat ~= nil then
            for line in stat:lines() do
                local t = util.splitToArray(line, " ")
                if #t >= 5 and string.lower(t[1]) == "cpu" then
                    local n1, n2, n3, n4
                    n1 = tonumber(t[2])
                    n2 = tonumber(t[3])
                    n3 = tonumber(t[4])
                    n4 = tonumber(t[5])
                    if n1 ~= nil and n2 ~= nil and n3 ~= nil and n4 ~= nil then
                      result.cpu = {
                        user = n1,
                        nice = n2,
                        system = n3,
                        idle = n4,
                        total = n1 + n2 + n3 + n4
                      }
                      break
                    end
                end
            end
            stat:close()
        end
    end

    do
        local meminfo = io.open("/proc/meminfo", "r")
        if meminfo ~= nil then
            result.memory = {}
            for line in meminfo:lines() do
                local t = util.splitToArray(line, " ")
                if #t >= 2 then
                    if string.lower(t[1]) == "memtotal:" then
                        local n = tonumber(t[2])
                        if n ~= nil then
                            result.memory.total = n
                        end
                    elseif string.lower(t[1]) == "memfree:" then
                        local n = tonumber(t[2])
                        if n ~= nil then
                            result.memory.free = n
                        end
                    elseif string.lower(t[1]) == "memavailable:" then
                        local n = tonumber(t[2])
                        if n ~= nil then
                            result.memory.available = n
                        end
                    end
                end
            end
            meminfo:close()
        end
    end
    return result
end

function SystemStat:appendSystemInfo()
    local stat = systemInfo()
    if stat.cpu ~= nil then
        self:put({_("System information"), ""})
        -- @translators Ticks is a highly technical term. See https://superuser.com/a/101202 The correct translation is likely to simply be "ticks".
        self:put({_("  Total ticks (million)"),
                 string.format("%.2f", stat.cpu.total * (1/1000000))})
        -- @translators Ticks is a highly technical term. See https://superuser.com/a/101202 The correct translation is likely to simply be "ticks".
        self:put({_("  Idle ticks (million)"),
                 string.format("%.2f", stat.cpu.idle * (1/1000000))})
        self:put({_("  Processor usage %"),
                 string.format("%.2f", (1 - stat.cpu.idle / stat.cpu.total) * 100)})
    end
    if stat.memory ~= nil then
        if stat.memory.total ~= nil then
            self:put({_("  Total memory (MB)"),
                     string.format("%.2f", stat.memory.total / 1024)})
        end
        if stat.memory.free ~= nil then
            self:put({_("  Free memory (MB)"),
                     string.format("%.2f", stat.memory.free / 1024)})
        end
        if stat.memory.available ~= nil then
            self:put({_("  Available memory (MB)"),
                     string.format("%.2f", stat.memory.available / 1024)})
        end
    end
end

function SystemStat:appendProcessInfo()
    local stat = io.open("/proc/self/stat", "r")
    if stat == nil then return end

    local t = util.splitToArray(stat:read("*line"), " ")
    stat:close()

    local n1, n2

    if #t == 0 then return end
    self:put({_("Process"), ""})

    self:put({_("  ID"), t[1]})

    if #t < 14 then return end
    n1 = tonumber(t[14])
    n2 = tonumber(t[15])
    if n1 ~= nil then
        if n2 ~= nil then
            n1 = n1 + n2
        end
        local sys_stat = systemInfo()
        if sys_stat.cpu ~= nil and sys_stat.cpu.total ~= nil then
            self:put({_("  Processor usage %"),
                     string.format("%.2f", n1 / sys_stat.cpu.total * 100)})
        else
            self:put({_("  Processor usage ticks (million)"), n1 * (1/1000000)})
        end
    end

    if #t < 20 then return end
    n1 = tonumber(t[20])
    if n1 ~= nil then
        self:put({_("  Threads"), tostring(n1)})
    end

    if #t < 23 then return end
    n1 = tonumber(t[23])
    if n1 ~= nil then
        self:put({_("  Virtual memory (MB)"), string.format("%.2f", n1 / 1024 / 1024)})
    end

    if #t < 24 then return end
    n1 = tonumber(t[24])
    if n1 ~= nil then
        self:put({_("  RAM usage (MB)"), string.format("%.2f", n1 / 256)})
    end
end

function SystemStat:appendStorageInfo()
    if self.storage_filter == nil then return end

    local std_out = io.popen(
        "df -h | sed -r 's/ +/ /g' | grep " .. self.storage_filter ..
        " | sed 's/ /\\t/g' | cut -f 2,4,5,6"
    )
    if not std_out then return end

    self:put({_("Storage information"), ""})
    for line in std_out:lines() do
        local t = util.splitToArray(line, "\t")
        if #t ~= 4 then
            self:put({_("  Unexpected"), line})
        else
            self:put({_("  Mount point"), t[4]})
            self:put({_("    Available"), t[2]})
            self:put({_("    Total"), t[1]})
            self:put({_("    Used percentage"), t[3]})
        end
    end
    std_out:close()
end

function SystemStat:onSuspend()
    self.suspend_time = time.realtime()
    self.sleep_count = self.sleep_count + 1
end

function SystemStat:onResume()
    self.resume_time = time.realtime()
    self.wakeup_count = self.wakeup_count + 1
end

function SystemStat:onCharging()
    self.charge_count = self.charge_count + 1
end

function SystemStat:onNotCharging()
    self.discharge_count = self.discharge_count + 1
end

function SystemStat:showStatistics()
    self.kv_pairs = {}
    self:appendCounters()
    self:putSeparator()
    self:appendProcessInfo()
    self:putSeparator()
    self:appendStorageInfo()
    self:putSeparator()
    self:appendSystemInfo()
    UIManager:show(KeyValuePage:new{
        title = _("System statistics"),
        kv_pairs = self.kv_pairs,
    })
end

SystemStat:init()

local SystemStatWidget = WidgetContainer:extend{
    name = "systemstat",
}

function SystemStatWidget:onDispatcherRegisterActions()
    Dispatcher:registerAction("system_statistics", {category="none", event="ShowSysStatistics", title=_("System statistics"), device=true, separator=true})
end

function SystemStatWidget:init()
    self:onDispatcherRegisterActions()
    self.ui.menu:registerToMainMenu(self)
end

function SystemStatWidget:addToMainMenu(menu_items)
    menu_items.system_statistics = {
        text = _("System statistics"),
        keep_menu_open = true,
        callback = function()
            SystemStat:showStatistics()
        end,
    }
end

function SystemStatWidget:onShowSysStatistics()
    SystemStat:showStatistics()
end

function SystemStatWidget:onSuspend()
    SystemStat:onSuspend()
end

function SystemStatWidget:onResume()
    SystemStat:onResume()
end

function SystemStatWidget:onCharging()
    SystemStat:onCharging()
end

function SystemStatWidget:onNotCharging()
    SystemStat:onNotCharging()
end

return SystemStatWidget
