-- The functions and variables in this file are only for use in the mod itself.
-- Those that do real work should be local and wrapped in public functions
local function log(text)
    local logmessage = yl_survey.t("log_prefix", yl_survey.modname, text)
    if yl_survey.settings.debug then minetest.log("action", logmessage) end
    return logmessage
end

function yl_survey.log(text) return log(text) end

-- Helpers

local function filename2uuid(filename) return filename:sub(1, -6) end

local function is_visible(filename) return (string.sub(filename, 1, 1) ~= ".") end

local function is_json(filename) return (filename:match("%.json$")) end

local function ends_with(str, suffix) return str:sub(-suffix:len()) == suffix end

local function split(str)
    local parts = {}
    for part in str:gmatch("[^,%s]+") do table.insert(parts, part) end
    return parts
end

local function priv_exists(priv)
    return (minetest.registered_privileges[priv] ~= nil) or false
end

function yl_survey.priv_exists(priv) return priv_exists(priv) end

-- Hash name

local function hash_name(name)
    if ((type(name) ~= "string") or (name == "")) then
        return nil
    end
    return minetest.sha256(name, false)
end

function yl_survey.hash_name(name) return hash_name(name) end

-- UUID

local function generate_uuid()
    local template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
    return string.gsub(template, '[xy]', function(c)
        local v = (c == 'x') and math.random(0, 15) or math.random(8, 11)
        return string.format('%x', v)
    end)
end

local function is_uuid_duplicate(UUID)
    for i, record in pairs(yl_survey.data) do
        local message = "is_uuid_duplicate record.id=%s, UUID=%s"
        local formatted = string.format(message, dump(record.id), dump(UUID))
        log(formatted)
        if record.id == UUID then return true end
    end
    return false
end

local function create_uuid()
    local max_attempts = 10

    local UUID

    repeat
        UUID = generate_uuid()
        max_attempts = max_attempts - 1
        if max_attempts < 0 then
            return false, "Cannot find non-duplicate UUID"
        end
    until (is_uuid_duplicate(UUID) == false)

    if UUID == "" then return false, "UUID empty" end

    return true, UUID

end

function yl_survey.create_uuid() return create_uuid() end

-- delete responses

local function delete_responses(record)
    local no = 0
    for key, question in pairs(record) do
        if (tonumber(key) ~= nil) then
            if question.responses ~= nil then
                question.responses = nil
                no = no + 1
            end
        end
    end
    return record, no
end

function yl_survey.delete_responses(record)
    return delete_responses(record)
end

-- yl_survey.find_next_free_sort_number(record)

local function find_next_free_number(record, target)
    local highest_no = 1
    local duplicates = {}
    for key, question in pairs(record) do
        if (tonumber(key) ~= nil) then
            table.insert(duplicates, question[target])
            if (question[target] >= highest_no) then
                highest_no = question[target] + 1
            end
        end
    end
    local is_duplicate = false
    for _, dupe in ipairs(duplicates) do
        if (highest_no == dupe) then
            is_duplicate = true
        end
    end
    return highest_no, is_duplicate
end

function yl_survey.find_next_free_number(record, target)
    return find_next_free_number(record, target)
end

-- yl_survey.is_duplicate(record, sort)

local function is_duplicate(record, sort)
    for key, question in pairs(record) do
        if (tonumber(key) ~= nil) then
            if (question[sort] == sort) then
                return true
            end
        end
    end
    return false
end

function yl_survey.is_duplicate(record, sort)
    return is_duplicate(record, sort)
end

-- Load and save

local function get_savepath()
    local savepath = yl_survey.worldpath .. yl_survey.settings.save_path
    log(savepath or "")
    return savepath
end

local function get_filepath(filename)
    local path_to_file = get_savepath() .. DIR_DELIM .. filename .. ".json"
    log(yl_survey.t("get_filepath", dump(path_to_file)))
    return path_to_file
end

local function save_json(filename, content)
    if ((type(filename) ~= "string") or (type(content) ~= "table")) then
        return false
    end
    local savepath = get_filepath(filename)
    local savecontent = minetest.write_json(content)
    return minetest.safe_file_write(savepath, savecontent)
end

local function load_json(path)
    local file = io.open(path, "r")
    if not file then
        return false, yl_survey.t("error_cannot_open_file", dump(path))
    end

    local content = file:read("*all")
    file:close()

    if not content then
        return false, yl_survey.t("error_cannot_read_file", dump(path))
    end

    return true, minetest.parse_json(content)
end

-- Public functions wrap the private ones, so they can be exchanged easily

function yl_survey.load(filename, ...) return load_json(filename, ...) end

function yl_survey.save(filename, content, ...)
    return save_json(filename, content, ...)
end

-- Remove file

local function remove_file(UUID)
    local path = get_filepath(UUID)
    return os.remove(path)
end

function yl_survey.remove_file(UUID) return remove_file(UUID) end

-- Load all data
--

local function load_all_data()
    -- Get all json files from savepath
    -- Excluding invisible
    -- Excluding non-json files
    local save_path = get_savepath()
    local files = minetest.get_dir_list(save_path, false) or {}
    local data = {}
    local total = 0
    local good = 0
    local bad = 0
    for key, filename in ipairs(files) do
        if is_visible(filename) and is_json(filename) then
            total = total + 1
            local UUID = filename2uuid(filename)
            local filepath = get_filepath(UUID)
            local success, content = load_json(filepath)

            if ((success == true) and (content ~= nil) and (content["metadata"].id == UUID)) then
                good = good + 1
                data[UUID] = content
            else
                bad = bad + 1
            end

        end
    end

    yl_survey.data = data

    local message = "[MOD] %s : bad = %s, good = %s, total = %s"
    local formatted = string.format(message, yl_survey.information.name, tostring(bad), tostring(good), tostring(total))
    if bad == 0 then
        minetest.log("action", formatted)
        return true, good, bad
    else
        minetest.log("warning", formatted)
        return false, good, bad
    end
end

function yl_survey.load_all_data() return load_all_data() end

-- Check privs
--

local function check_privs(settings)
    for key, value in pairs(settings) do
        if ends_with(key, "_privs") then
            local parts = split(value)
            for _, part in ipairs(parts) do
                local message = "[MOD] %s : configured priv %s does not exist."
                local formatted = string.format(message, yl_survey.information.name, dump(part))
                assert(priv_exists(part), formatted)
            end
        end
    end
    log("PASS priv check")
end

function yl_survey.check_privs(settings) return check_privs(settings) end
