--
-- Surveys
--
-- yl_survey.get_data
--
local function get_data()
    if next(yl_survey.data) then
        return true, yl_survey.data
    else
        return false, {}
    end
end

function yl_survey.get_data() return get_data() end

-- yl_survey.get_survey
--

local function get_record(id)
    local success, data = yl_survey.get_data()
    if (success == false) then return nil end
    if (type(data[id]) == "table") then
        return table.copy(data[id]) -- {data} or nil
    else
        return nil
    end
end

function yl_survey.get_record(id) return get_record(id) end

-- validations
--

local function validate_nil_or_positive(value)
    if (value == nil) then return true end
    if ((type(value) == "number") and (value >= 0)) then return true end
    return false
end

local function validate(value, expect_type, allow_nil)
    if ((allow_nil == true) and (value == nil)) then return true end
    if ((allow_nil == false) and (value == nil)) then return false end
    if (type(value) == expect_type) then return true end
    return false
end

local function validate_questiontype(allowed_types)
    local valid_types = {
        singlechoice = true,
        multiplechoice = true,
        freetext = true
    }

    local sm_count = 0
    local ft_count = 0

    for _, t in ipairs(allowed_types) do

        -- Invalid question type
        if (valid_types[t] == nil) then return false end

        -- Either singlechoice OR multiplechoice
        if ((t == "singlechoice") or (t == "multiplechoice")) then
            sm_count = sm_count + 1
        end

        if sm_count > 1 then return false end

        -- Only one freetext allowed
        if (t == "freetext") then ft_count = ft_count + 1 end

        if ft_count > 1 then return false end

    end

    if ((ft_count + sm_count) == 0) then return false end

    return true
end

local function validate_responses(t_answers, responses)

    -- Defense
    if (validate(t_answers, "table", false) == false) then return false end
    if (validate(responses, "table", false) == false) then return false end

    -- If the responses are not JSON-able, return false
    if (minetest.write_json(responses) == nil) then return false end

    -- If there is a response not matched in t_answers, return false
    for k, v in pairs(responses) do
        local r_id = tonumber(k)
        if r_id and (r_id > 0) then
            local found = false
            for kk, vv in pairs(t_answers) do
                if (v == vv) then found = true end
            end
            if (found == false) then return false end
        end
    end

    return true
end

-- yl_survey.create_survey
--

local function create_survey(owner, title, description, allow_names, allow_anonymous, allow_changes,
                             timestamp_start, timestamp_end)
    -- Defense
    if ((type(owner) ~= "string") or (owner == "")) then
        return false, yl_survey.t("owner must be string")
    end
    if (title == nil) then title = "" end -- set default
    if (description == nil) then description = "" end -- set default
    if (allow_names == nil) then allow_names = true end -- set default
    if (type(allow_names) ~= "boolean") then
        return false, yl_survey.t("allow_names must be boolean")
    end
    if (allow_anonymous == nil) then allow_anonymous = true end -- set default
    if (type(allow_anonymous) ~= "boolean") then
        return false, yl_survey.t("allow_anonymous must be boolean")
    end
    if (allow_changes == nil) then allow_changes = false end -- set default
    if (type(allow_changes) ~= "boolean") then
        return false, yl_survey.t("allow_changes must be boolean")
    end

    if (validate_nil_or_positive(timestamp_start) == false) then
        return false, yl_survey.t(
                   "timestamp_start if given then must be number and not negative")
    end
    if (validate_nil_or_positive(timestamp_end) == false) then
        return false, yl_survey.t(
                   "timestamp_end if given then must be number and not negative")
    end

    -- Specialcase: We don't accept surveys NOONE can take part in
    --[[
    if ((allow_names == false) and (allow_anonymous == false)) then
        return false, yl_survey.t("Noone can take part in that survey. " ..
                                      "Neither named nor anonymous participants are allowed")
    end
    ]] --

    if ((allow_names == false) and (allow_anonymous == false) and
        (allow_changes == false)) then
        return false, yl_survey.t("Noone can take part in that survey. " ..
                                      "Neither named nor anonymous participants are allowed. " ..
                                      " You can't change it later")
    end

    -- Specialcase: We don't accept surveys where the enddate is smaller than the startdate
    if (timestamp_start and timestamp_end and (timestamp_start >= timestamp_end) and
        (allow_changes == false)) then
        return false, yl_survey.t("Noone can take part in that survey. " ..
                                      "It ends before it starts and you can't change it later")
    end

    -- UUID
    local get_uuid_success, UUID = yl_survey.create_uuid()
    if (get_uuid_success == false) then return false, UUID end

    -- Add to list
    local metadata = {
        id = UUID,
        owner = owner,
        title = title,
        description = description,
        allow_names = allow_names,
        allow_anonymous = allow_anonymous,
        allow_changes = allow_changes,
        timestamp_start = timestamp_start,
        timestamp_end = timestamp_end,
        creation_date = os.time(),
        modification_date = os.time()
    }
    local record = {}
    record["metadata"] = metadata

    -- Store
    local success = yl_survey.save(UUID, record)
    if (success == true) then
        yl_survey.data[UUID] = record
        return true, UUID
    else
        return false, yl_survey.t("Could not store")
    end
end

function yl_survey.create_survey(owner, title, description, allow_names, allow_anonymous,
                                 allow_changes, timestamp_start, timestamp_end)
    return create_survey(owner, title, description, allow_names, allow_anonymous, allow_changes,
                         timestamp_start, timestamp_end)
end

-- yl_survey.edit_survey
--

local function edit_survey(id, owner, title, description, allow_names, allow_anonymous,
                           allow_changes, timestamp_start, timestamp_end,
                           delete_responses)
    -- Defense
    if (validate(id, "string", false) == false) then
        return false, yl_survey.t("id must be string")
    end
    if (validate(owner, "string", true) == false) then
        return false, yl_survey.t("owner must be string")
    end
    if (validate(title, "string", true) == false) then
        return false, yl_survey.t("title must be string")
    end
    if (validate(description, "string", true) == false) then
        return false, yl_survey.t("description must be string")
    end
    if (validate(allow_names, "boolean", true) == false) then
        return false, yl_survey.t("allow_names must be boolean")
    end
    if (validate(allow_anonymous, "boolean", true) == false) then
        return false, yl_survey.t("allow_anonymous must be boolean")
    end
    if (validate(allow_changes, "boolean", true) == false) then
        return false, yl_survey.t("allow_changes must be boolean")
    end
    if (validate(timestamp_start, "number", true) == false) then
        return false, yl_survey.t("timestamp_start must be number")
    end
    if (validate(timestamp_end, "number", true) == false) then
        return false, yl_survey.t("timestamp_end must be number")
    end
    if (validate(delete_responses, "boolean", true) == false) then
        return false, yl_survey.t("delete_responses must be boolean")
    end
    if (validate_nil_or_positive(timestamp_start) == false) then
        return false, yl_survey.t(
                   "timestamp_start if given then must be number and not negative")
    end
    if (validate_nil_or_positive(timestamp_end) == false) then
        return false, yl_survey.t(
                   "timestamp_end if given then must be number and not negative")
    end

    local record = yl_survey.get_record(id)

    if (record == nil) then
        return false, yl_survey.t("record does not exist")
    end
    if (record["metadata"].allow_changes == false) then
        return false, yl_survey.t("no changes allowed")
    end

    local metadata = {
        id = record["metadata"].id, -- This value cannot change
        owner = owner or record["metadata"].owner,
        title = title or record["metadata"].title,
        description = description or record["metadata"].description,
        allow_names = allow_names or record["metadata"].allow_names,
        allow_anonymous = allow_anonymous or record["metadata"].allow_anonymous,
        allow_changes = allow_changes or record["metadata"].allow_changes,
        timestamp_start = timestamp_start or record["metadata"].timestamp_start,
        timestamp_end = timestamp_end or record["metadata"].timestamp_end,
        creation_date = record["metadata"].creation_date, -- This value cannot change
        modification_date = os.time() -- This value must change
    }

    record["metadata"] = metadata

    -- Delete responses
    local no
    if (delete_responses == true) then
        record, no = yl_survey.delete_responses(record)
    end

    -- Store
    local success = yl_survey.save(id, record)
    if (success == true) then
        yl_survey.data[id] = record
        return true, id, no or 0
    else
        return false, yl_survey.t("Could not store")
    end

end

function yl_survey.edit_survey(id, owner, title, description, allow_names, allow_anonymous,
                               allow_changes, timestamp_start, timestamp_end,
                               delete_responses)
    return edit_survey(id, owner, title, description, allow_names, allow_anonymous, allow_changes,
                       timestamp_start, timestamp_end, delete_responses)
end

-- yl_survey.remove_record
--

local function remove_record(id)
    local data_success, data = yl_survey.get_data()
    if (data_success ~= true) then return false, "Cannot get data" end

    local remove_success = yl_survey.remove_file(id)
    if (remove_success ~= true) then
        return false, yl_survey.t("Could not remove")
    end

    local record = yl_survey.get_record(id)
    if (record == nil) then
        return false, yl_survey.t("record not found, cannot remove")
    end

    data[id] = nil
    return true, record
end

function yl_survey.remove_record(id) return remove_record(id) end

--
-- Questions
--

-- create_question
--

local function create_question(id, question, category, sort, allowed_types,
                               answers, enabled)
    -- Defense
    if (validate(id, "string", false) == false) then
        return false, yl_survey.t("id must be string")
    end
    if (validate(question, "string", false) == false) then
        return false, yl_survey.t("question must be string")
    end
    if (category == nil) then category = "" end
    if (validate(category, "string", false) == false) then
        return false, yl_survey.t("category must be string")
    end
    if (validate_nil_or_positive(sort) == false) then
        return false, yl_survey.t("sort must be positive number")
    end
    if (validate(allowed_types, "table", false) == false) then
        return false, yl_survey.t("allowed_types must be table")
    end
    if (validate(answers, "table", true) == false) then
        return false, yl_survey.t("answers must be table")
    end
    if (enabled == nil) then enabled = true end
    if (validate(enabled, "boolean", false) == false) then
        return false, yl_survey.t("enabled must be boolean")
    end

    -- Specialcase : allowed_types must be a combination of
    -- "singlechoice" or "multiplechoice" or "freetext"
    if (validate_questiontype(allowed_types) == false) then
        return false, yl_survey.t("wrong allowed_types")
    end

    -- Specialcase : answers cannot be nil or empty if allowedtypes has either multiplechoice or singlechoice
    if (((answers == nil) or (next(answers) == nil)) and
        ((yl_survey.table_contains(allowed_types, "multiplechoice") == true) or
            (yl_survey.table_contains(allowed_types, "singlechoice") == true))) then
        return false, yl_survey.t(
                   "answers cannot be nil or empty if allowedtypes has either multiplechoice or singlechoice")
    end

    local record = yl_survey.get_record(id)

    if (record == nil) then
        return false, yl_survey.t("record does not exist")
    end

    -- Specialcase : given sort is a duplicate
    if ((type(sort) == "number") and
        (yl_survey.is_duplicate(record, sort) == true)) then
        return false, yl_survey.t("sort is duplicate")
    end

    if (record["metadata"].allow_changes == false) then
        return false, yl_survey.t("no changes allowed")
    end

    -- Payload
    -- if sort is nil, then find next sort
    local next_sort = sort or yl_survey.find_next_free_number(record, "sort")

    -- Find next free q_id
    local next_q_id = yl_survey.find_next_free_number(record, "id")

    local t_question = {
        id = next_q_id,
        category = category,
        sort = next_sort,
        question = question,
        enabled = true,
        allowed_types = allowed_types,
        answers = answers,
        responses = {}
    }

    record[tostring(next_q_id)] = t_question

    -- Store
    local success = yl_survey.save(id, record)
    if (success == true) then
        yl_survey.data[id] = record
        return true, next_q_id
    else
        return false, yl_survey.t("Could not store")
    end
end

function yl_survey.create_question(id, question, category, sort, allowed_types,
                                   answers, enabled)
    return create_question(id, question, category, sort, allowed_types, answers,
                           enabled)
end

-- edit_question
--

local function edit_question(id, q_id, question, sort, category, allowed_types,
                             answers, enabled, delete_responses)
    -- Defense
    if (validate(id, "string", false) == false) then
        return false, yl_survey.t("id must be string")
    end
    if (validate(q_id, "number", false) == false) then
        return false, "q_id must be number"
    end
    if (q_id <= 0) then return false, "q_id must be positive number" end

    if (validate(question, "string", true) == false) then
        return false, yl_survey.t("question must be string")
    end
    if (validate(category, "string", true) == false) then
        return false, yl_survey.t("category must be string")
    end
    if (validate_nil_or_positive(sort) == false) then
        return false, yl_survey.t("sort must be positive number")
    end
    if (validate(allowed_types, "table", true) == false) then
        return false, yl_survey.t("allowed_types must be table")
    end
    if (validate(answers, "table", true) == false) then
        return false, yl_survey.t("answers must be table")
    end
    if (validate(enabled, "boolean", true) == false) then
        return false, yl_survey.t("enabled must be boolean")
    end
    if (validate(delete_responses, "boolean", true) == false) then
        return false, yl_survey.t("delete_responses must be boolean")
    end

    -- Specialcase : allowed_types must be a combination of
    -- "singlechoice" or "multiplechoice" or "freetext"
    if ((allowed_types ~= nil) and
        (validate_questiontype(allowed_types) == false)) then
        return false, yl_survey.t("wrong allowed_types")
    end

    -- Specialcase : answers cannot be nil or empty if allowedtypes has either multiplechoice or singlechoice
    if ((answers ~= nil) and (next(answers) ~= nil) and
        ((yl_survey.table_contains(allowed_types, "multiplechoice") == true) or
            (yl_survey.table_contains(allowed_types, "singlechoice") == true))) then
        return false, yl_survey.t(
                   "answers cannot be nil or empty if allowedtypes has either multiplechoice or singlechoice")
    end

    local record = yl_survey.get_record(id)

    if (record == nil) then
        return false, yl_survey.t("record does not exist")
    end

    -- Specialcase : given sort is a duplicate
    if ((type(sort) == "number") and
        (yl_survey.is_duplicate(record, sort) == true)) then
        return false, yl_survey.t("sort is duplicate")
    end

    if (record["metadata"].allow_changes == false) then
        return false, yl_survey.t("no changes allowed")
    end

    if (record[tostring(q_id)] == nil) then
        return false, yl_survey.t("question does not exist")
    end

    local t_question = table.copy(record[tostring(q_id)])

    -- Delete responses
    local no = 0
    local n_responses = t_question["responses"] or {}
    if (delete_responses == true) then
        for _, _ in pairs(n_responses) do no = no + 1 end
        n_responses = {}
    end

    -- Payload

    local s_question = {
        id = t_question["id"],
        category = category or t_question["category"],
        sort = sort or t_question["sort"],
        question = question or t_question["question"],
        enabled = enabled or t_question["enabled"],
        allowed_types = allowed_types or t_question["allowed_types"],
        answers = answers or t_question["answers"],
        responses = n_responses
    }

    record[tostring(q_id)] = s_question

    -- Store
    local success = yl_survey.save(id, record)
    if (success == true) then
        yl_survey.data[id] = record
        return true, q_id
    else
        return false, yl_survey.t("Could not store")
    end

end

function yl_survey.edit_question(s_id, q_id, question, sort, category,
                                 allowed_types, answers, enabled,
                                 delete_responses)
    return edit_question(s_id, q_id, question, sort, category, allowed_types,
                         answers, enabled, delete_responses)
end

-- remove_question
--

local function remove_question(id, q_id, delete_responses)
    -- Defense
    if (validate(id, "string", false) == false) then
        return false, yl_survey.t("id must be string")
    end
    if (validate(q_id, "number", false) == false) then
        return false, "q_id must be number"
    end
    if (q_id <= 0) then return false, "q_id must be positive number" end
    if (validate(delete_responses, "boolean", true) == false) then
        return false, yl_survey.t("delete_responses must be boolean")
    end

    local record = yl_survey.get_record(id)

    if (record == nil) then
        return false, yl_survey.t("record does not exist")
    end
    if (record["metadata"].allow_changes == false) then
        return false, yl_survey.t("no changes allowed")
    end

    if (record[tostring(q_id)] == nil) then
        return false, yl_survey.t("question does not exist")
    end

    local t_question = table.copy(record[tostring(q_id)])

    -- Delete responses
    local no = 0
    local n_responses = t_question["responses"] or {}
    if (delete_responses == true) then
        for _, _ in pairs(n_responses) do no = no + 1 end
        record[tostring(q_id)] = nil
    else
        record[tostring(q_id)] = {responses = n_responses}
    end

    -- Store
    local success = yl_survey.save(id, record)
    if (success == true) then
        yl_survey.data[id] = record
        return true, t_question, no
    else
        return false, yl_survey.t("Could not store")
    end

end

function yl_survey.remove_question(s_id, q_id, delete_responses)
    return remove_question(s_id, q_id, delete_responses)
end

-- list_questions
--

local function list_questions(id, include_responses)
    -- Defense
    if (validate(id, "string", false) == false) then
        return false, yl_survey.t("id must be string")
    end
    if (validate(include_responses, "boolean", true) == false) then
        return false, yl_survey.t("include_responses must be boolean")
    end

    local record = yl_survey.get_record(id)

    if (record == nil) then
        return false, yl_survey.t("record does not exist")
    end

    -- Payload
    local questions = {}

    for key, value in pairs(record) do
        if (tonumber(key) ~= nil) then
            if (include_responses ~= true) then
                value["responses"] = nil
            end
            questions[key] = value
        end
    end

    if (next(questions) ~= nil) then
        return true, questions
    else
        return false, {}
    end

end

function yl_survey.list_questions(s_id, include_responses)
    return list_questions(s_id, include_responses)
end

-- yl_survey.answer_question
--

local function answer_question(id, q_id, playername, responses, overwrite)
    -- Defense
    if (validate(id, "string", false) == false) then
        return false, yl_survey.t("id must be string")
    end
    if (validate(q_id, "number", false) == false) then
        return false, "q_id must be number"
    end
    if (q_id <= 0) then return false, "q_id must be positive number" end
    if (validate(playername, "string", false) == false) then
        return false, yl_survey.t("playername must be string")
    end
    if (validate(responses, "table", false) == false) then
        return false, yl_survey.t("responses must be table")
    end
    if (validate(overwrite, "boolean", true) == false) then
        return false, yl_survey.t("overwrite must be boolean")
    end

    -- Get survey

    local record = yl_survey.get_record(id)

    if (record == nil) then
        return false, yl_survey.t("record does not exist")
    end

    -- Get question

    if (record[tostring(q_id)] == nil) then
        return false, yl_survey.t("question does not exist")
    end

    local t_question = table.copy(record[tostring(q_id)])

    -- Get responses

    local all_responses = t_question["responses"]

    local overwritten_responses = {}

    if (all_responses and next(all_responses[playername]) ~= nil) then
        overwritten_responses = all_responses[playername] or {}
    end

    if ((overwrite ~= true) and (overwritten_responses ~= nil)) then
        return false,
               "previous responses cannot be written over without overwrite true"
    end

    responses["q"] = t_question["question"]

    if (record[tostring(q_id)]["responses"] == nil) then
        record[tostring(q_id)]["responses"] = {}
    end

    if (validate_responses(t_question["answers"], responses) == false) then
        return false, "responses do not fit the questions"
    end

    record[tostring(q_id)]["responses"][playername] = responses

    -- Store
    local success = yl_survey.save(id, record)
    if (success == true) then
        yl_survey.data[id] = record
        return true, overwritten_responses or {}
    else
        return false, yl_survey.t("Could not store")
    end

end

function yl_survey.answer_question(id, q_id, playername, responses, overwrite)
    return answer_question(id, q_id, playername, responses, overwrite)
end

-- get_question
--

local function get_question(id, q_id, include_responses, playername)
    -- Defense
    if (validate(id, "string", false) == false) then
        return false, yl_survey.t("id must be string")
    end
    if (validate(q_id, "number", false) == false) then
        return false, yl_survey.t("q_id must be number")
    end
    if (q_id <= 0) then return false, "q_id must be positive number" end
    if (validate(playername, "string", true) == false) then
        return false, yl_survey.t("playername must be string")
    end
    if (validate(include_responses, "boolean", true) == false) then
        return false, yl_survey.t("include_responses must be boolean")
    end

    local record = yl_survey.get_record(id)

    if (record == nil) then
        return false, yl_survey.t("record does not exist")
    end

    -- Payload
    local question = record[tostring(q_id)] or {}

    if (playername ~= nil) then
        local responses = {}
        responses[playername] = question["responses"] and
                                    question["responses"][playername] or {}
        question["responses"] = responses
    end

    if (include_responses ~= true) then question["responses"] = nil end

    if (next(question) ~= nil) then
        return true, question
    else
        return false, {}
    end
end

function yl_survey.get_question(s_id, q_id, include_responses, playername)
    return get_question(s_id, q_id, include_responses, playername)
end
