--[[--
This module handles generic settings as well as KOReader's global settings system.
]]

local dump = require("dump")
local lfs = require("libs/libkoreader-lfs")
local logger = require("logger")

local LuaSettings = {}

function LuaSettings:new(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    return o
end

--- Opens a settings file.
function LuaSettings:open(file_path)
    local new = {file=file_path}
    local ok, stored

    -- File being absent and returning an empty table is a use case,
    -- so logger.warn() only if there was an existing file
    local existing = lfs.attributes(new.file, "mode") == "file"

    ok, stored = pcall(dofile, new.file)
    if ok and stored then
        new.data = stored
    else
        if existing then logger.warn("Failed reading", new.file, "(probably corrupted).") end
        -- Fallback to .old if it exists
        ok, stored = pcall(dofile, new.file..".old")
        if ok and stored then
            if existing then logger.warn("read from backup file", new.file..".old") end
            new.data = stored
        else
            if existing then logger.warn("no usable backup file for", new.file, "to read from") end
            new.data = {}
        end
    end

    return setmetatable(new, {__index = LuaSettings})
end

-- TODO: DocSettings can return a LuaSettings to use following awesome features.
function LuaSettings:wrap(data)
    local new = {data = type(data) == "table" and data or {}}
    return setmetatable(new, {__index = LuaSettings})
end

--[[--Reads child settings.

@usage

    Settings:saveSetting("key", {
        a = "b",
        c = "true",
        d = false,
    })

    local child = Settings:child("key")

    child:readSetting("a")
    -- result "b"
]]
function LuaSettings:child(key)
    return LuaSettings:wrap(self:readSetting(key))
end

--- Reads a setting.
function LuaSettings:readSetting(key)
    return self.data[key]
end

--- Saves a setting.
function LuaSettings:saveSetting(key, value)
    self.data[key] = value
    return self
end

--- Deletes a setting.
function LuaSettings:delSetting(key)
    self.data[key] = nil
    return self
end

--- Checks if setting exists.
function LuaSettings:has(key)
    return self:readSetting(key) ~= nil
end

--- Checks if setting does not exist.
function LuaSettings:hasNot(key)
    return self:readSetting(key) == nil
end

--- Checks if setting is `true`.
function LuaSettings:isTrue(key)
    return string.lower(tostring(self:readSetting(key))) == "true"
end

--- Checks if setting is `false`.
function LuaSettings:isFalse(key)
    return string.lower(tostring(self:readSetting(key))) == "false"
end

--- Checks if setting is `nil` or `true`.
function LuaSettings:nilOrTrue(key)
    return self:hasNot(key) or self:isTrue(key)
end

--- Checks if setting is `nil` or `false`.
function LuaSettings:nilOrFalse(key)
    return self:hasNot(key) or self:isFalse(key)
end

--- Flips `nil` or `true` to `false`.
function LuaSettings:flipNilOrTrue(key)
    if self:nilOrTrue(key) then
        self:saveSetting(key, false)
    else
        self:delSetting(key)
    end
    return self
end

--- Flips `nil` or `false` to `true`.
function LuaSettings:flipNilOrFalse(key)
    if self:nilOrFalse(key) then
        self:saveSetting(key, true)
    else
        self:delSetting(key)
    end
    return self
end

--- Flips setting to `true`.
function LuaSettings:flipTrue(key)
    if self:isTrue(key) then
        self:delSetting(key)
    else
        self:saveSetting(key, true)
    end
    return self
end

--- Flips setting to `false`.
function LuaSettings:flipFalse(key)
    if self:isFalse(key) then
        self:delSetting(key)
    else
        self:saveSetting(key, true)
    end
    return self
end

--- Adds item to table.
function LuaSettings:addTableItem(key, value)
    local settings_table = self:has(key) and self:readSetting(key) or {}
    table.insert(settings_table, value)
    self:saveSetting(key, settings_table)
    return self
end

--- Removes index from table.
function LuaSettings:removeTableItem(key, index)
    local settings_table = self:has(key) and self:readSetting(key) or {}
    table.remove(settings_table, index)
    self:saveSetting(key, settings_table)
    return self
end

--- Replaces existing settings with table.
function LuaSettings:reset(table)
    self.data = table
    return self
end

--- Writes settings to disk.
function LuaSettings:flush()
    if not self.file then return end
    if lfs.attributes(self.file, "mode") == "file" then
        os.rename(self.file, self.file .. ".old")
    end
    local f_out = io.open(self.file, "w")
    if f_out ~= nil then
        os.setlocale('C', 'numeric')
        f_out:write("-- we can read Lua syntax here!\nreturn ")
        f_out:write(dump(self.data))
        f_out:write("\n")
        f_out:close()
    end
    return self
end

--- Closes settings file.
function LuaSettings:close()
    self:flush()
end

--- Purges settings file.
function LuaSettings:purge()
    if self.file then
        os.remove(self.file)
    end
    return self
end

return LuaSettings
