--
-- {{{ Digit Functions
--- Get sorted base-10 digits of a number
---@param n integer
---@return integer[]
local function get_sorted_digits(n)
    local digits = {}
    for d in tostring(math.abs(n)):gmatch("%d") do
        table.insert(digits, tonumber(d))
    end
    table.sort(digits)
    return digits
end
-- }}}

--{{{ print_r
local function print_r(t)
    local print_r_cache = {}
    local function sub_print_r(t, indent)
        if (print_r_cache[tostring(t)]) then
            print(indent .. "*" .. tostring(t))
        else
            print_r_cache[tostring(t)] = true
            if (type(t) == "table") then
                local tLen = #t
                for i = 1, tLen do
                    local val = t[i]
                    if (type(val) == "table") then
                        print(indent .. "#[" .. i .. "] => " .. tostring(t) .. " {")
                        sub_print_r(val, indent .. string.rep(" ", string.len(i) + 8))
                        print(indent .. string.rep(" ", string.len(i) + 6) .. "}")
                    elseif (type(val) == "string") then
                        print(indent .. "#[" .. i .. '] => "' .. val .. '"')
                    else
                        print(indent .. "#[" .. i .. "] => " .. tostring(val))
                    end
                end
                for pos, val in pairs(t) do
                    if type(pos) ~= "number" or math.floor(pos) ~= pos or (pos < 1 or pos > tLen) then
                        if (type(val) == "table") then
                            print(indent .. "[" .. pos .. "] => " .. tostring(t) .. " {")
                            sub_print_r(val, indent .. string.rep(" ", string.len(pos) + 8))
                            print(indent .. string.rep(" ", string.len(pos) + 6) .. "}")
                        elseif (type(val) == "string") then
                            print(indent .. "[" .. pos .. '] => "' .. val .. '"')
                        else
                            print(indent .. "[" .. pos .. "] => " .. tostring(val))
                        end
                    end
                end
            else
                print(indent .. tostring(t))
            end
        end
    end

    if (type(t) == "table") then
        print(tostring(t) .. " {")
        sub_print_r(t, "  ")
        print("}")
    else
        sub_print_r(t, "  ")
    end

    print()
end
--}}}

--{{{ table_count
function table_count(t)
    local count = 0
    for _ in pairs(t) do count = count + 1 end
    return count
end

--}}}

--{{{ range(low, high, n)
---Create a table with integers in a given range
---@param low integer Lowest number
---@param high integer Highest number
---@param n integer Increment - the array will contain every nth number between [low..high]
---@return table
local function range(low, high, n)
    local t = {}
    for i = low, high, n do
        table.insert(t, i)
    end

    return t
end
---}}}

--{{{ sortMapByValueDesc(m)
---Sort the table m by value.
---@param m table
---@return table
local function sortMapByValueDesc(m)
    local totalCount = 0
    local tmp = {}

    -- create a temporary table that is easier to sort
    for damage, occurrences in pairs(m) do
        local entry = {
            damage = damage,
            count = occurrences,
        }
        totalCount = totalCount + occurrences
        table.insert(tmp, entry)
    end

    table.sort(tmp, function(a, b)
        return a.count > b.count
    end)

    return tmp

    --
    -- local result = {}
    -- for _, v in pairs(tmp) do
    --     result[v.damage] = v.count
    -- end
    --
    --
    -- if table_count(result) ~= table_count(m) then
    --     error("I couldn't sort!!")
    -- end
    --
    -- return result
end
--}}}

--{{{table_sum(t)
---numeric sum of table values
---@param t integer[]
---@return integer
local function table_sum(t)
    local result = 0
    for _, v in pairs(t) do
        result = result + v
    end

    return result
end
--}}}

--{{{mostCommonDamageValues(instances, ratio)
--- Calculate the most likely values
---@param instances table A map of [damage score] => [occurrences]
---@param ratio number Include most common damage until the sum of the probabilities of the damage scores are at least this number.
---@return table
local function mostCommonDamageValues(instances, ratio)
    if ratio == nil then
        ratio = 0.5
    end

    local sortedInstances = sortMapByValueDesc(instances)
    local totalOccurrences = table_sum(instances)

    local result = {}
    local occurrenceAccumulator = 0

    for _, instance in pairs(sortedInstances) do
        occurrenceAccumulator = occurrenceAccumulator + instance.count
        table.insert(result, instance.damage)

        if occurrenceAccumulator / totalOccurrences >= ratio then
            return result
        end
    end

    return result
end
--}}}

--{{{calculateDamageD100(skill, base_damage)
---Calculate the average of the d100 attack method
---@param skill integer The skill level of the attacker (range of 1 to 100, but will be capped to 20)
---@param base_damage integer The base damage of the attacker's weapon.
---@return number
---@return table mostRolled the damage values that are most likely to be rolled
local function calculateDamageD100(skill, base_damage)
    local sum = 0
    local n = 0
    local tn = math.min(skill, 80)
    local damages = {}

    for rolled = 1, 100, 1 do
        local digits = get_sorted_digits(rolled)

        local damage = 0

        if rolled > tn then
            -- glancing blow: damage equal to weapon's base damage.
            damage = base_damage
        end
        if rolled < tn then
            -- normal hit: damage equal digit sum of what is rolled plus base damage.
            damage = table_sum(digits) + base_damage
        end
        if rolled == tn then
            -- Critical Hit: damage equal to what you rolled.
            damage = rolled
        end

        local key = tostring(damage)
        n = n + 1
        sum = sum + damage
        if not damages[key] then
            damages[key] = 1
        else
            damages[key] = damages[key] + 1
        end
    end

    return sum / n, mostCommonDamageValues(damages, 0.5)
end
--}}}

local skill_min = 30
local skill_max = 80
local skill_inc = 5
local skills_table = range(skill_min, skill_max, skill_inc)

local damage_min = 0
local damage_max = 10
local damage_inc = 3
local damages_table = range(damage_min, damage_max, damage_inc)

io.write("skill\\damage", ";")
for i, dmg in pairs(damages_table) do
    io.write(string.format("%d (avg);%d (typical)", dmg, dmg))
    if i < #damages_table then
        io.write(";")
    end
end
print("")


for _, skill in pairs(skills_table) do
    io.write(skill, ";")

    local tmp = {}
    for key, damage in ipairs(damages_table) do
        local avg, dmg = calculateDamageD100(skill, damage)
        tmp[key] = string.format("%.2f;%s", avg, table.concat(dmg, ", "))
    end
    print(table.concat(tmp, ";"))
end
