local eolian = require("eolian")
local eomap = require("docgen.mappings")

local is_verbose = false

local M = {}

local stats = {}

local stats_pd = function(n)
    local ret = 0
    if n == 0 then
        return 1
    end
    while (n ~= 0) do
        n = math.floor(n / 10)
        ret = ret + 1
    end
    return ret
end

local fcol = 30
local ncol = 0

local get_percent = function(sv, svu)
    return (sv == 0) and 100 or math.floor(((sv - svu) / sv) * 100 + 0.5)
end

local print_stat = function(printname, statname)
    local sv = stats[statname] or 0
    local svu = stats[statname .. "_undoc"] or 0
    local percent = get_percent(sv, svu)
    local tb = (" "):rep(math.max(0, fcol - #printname - 1) + ncol - stats_pd(sv))
    local dtb = (" "):rep(ncol - stats_pd(sv - svu))
    local ptb = (" "):rep(3 - stats_pd(percent))
    print(("%s:%s%d (documented: %s%d or %s%d%%)")
        :format(printname, tb, sv, dtb, sv - svu, ptb, percent))
end

local get_secstats = function(...)
    local sv, svu = 0, 0
    for i, v in ipairs({ ... }) do
        sv = sv + (stats[v] or 0)
        svu = svu + (stats[v .. "_undoc"] or 0)
    end
    return sv - svu, sv, get_percent(sv, svu)
end

M.print = function()
    for k, v in pairs(stats) do
        ncol = math.max(ncol, stats_pd(v))
    end

    print(("=== CLASS SECTION: %d out of %d (%d%%) ===\n")
        :format(get_secstats("class", "interface", "mixin", "event")))
    print_stat("Classes", "class")
    print_stat("Interfaces", "interface")
    print_stat("Mixins", "mixin")
    print_stat("Events", "event")

    print(("\n=== FUNCTION SECTION: %d out of %d (%d%%) ===\n")
        :format(get_secstats("method", "param", "mret",
                             "getter", "gret", "gkey", "gvalue",
                             "setter", "sret", "skey", "svalue")))
    print_stat("Methods", "method")
    print_stat("  Method params", "param")
    print_stat("  Method returns", "mret")
    print_stat("Getters", "getter")
    print_stat("  Getter returns", "gret")
    print_stat("  Getter keys", "gkey")
    print_stat("  Getter values", "gvalue")
    print_stat("Setters", "setter")
    print_stat("  Setter returns", "sret")
    print_stat("  Setter keys", "skey")
    print_stat("  Setter values", "svalue")

    print(("\n=== TYPE SECTION: %d out of %d (%d%%) ===\n")
        :format(get_secstats("alias", "struct", "sfield", "enum", "efield")))
    print_stat("Aliases", "alias")
    print_stat("Structs", "struct")
    print_stat("Struct fields", "sfield")
    print_stat("Enums", "enum")
    print_stat("Enum fields", "efield")

    print(("\n=== VARIABLE SECTION: %d out of %d (%d%%) ===\n")
        :format(get_secstats("constant", "global")))
    print_stat("Constants", "constant")
    print_stat("Globals", "global")

    local sv, svu = 0, 0
    for k, v in pairs(stats) do
        if k:match(".*_undoc$") then
            svu = svu + v
        else
            sv = sv + v
        end
    end
    print(("\n=== TOTAL: %d out of %d (%d%%) ===")
        :format(sv - svu, sv, get_percent(sv, svu)))
end

local stat_incr = function(name, missing)
    if not stats[name] then
        stats[name], stats[name .. "_undoc"] = 0, 0
    end
    stats[name] = stats[name] + 1
    if missing then
        stats[name .. "_undoc"] = stats[name .. "_undoc"] + 1
    end
end

local print_missing = function(name, tp)
    if not is_verbose then
        return
    end
    print(tp .. " '" .. name .. "'" .. " missing documentation")
end

M.check_class = function(cl)
    local ct = eomap.classt_to_str[cl:type_get()]
    if not ct then
        return
    end
    if not cl:doc_get():exists() then
        print_missing(cl:full_name_get(), ct)
        stat_incr(ct, true)
    else
        stat_incr(ct, false)
    end

    for i, ev in ipairs(cl:events_get()) do
        if not ev:documentation_get() then
            print_missing(cl:full_name_get() .. "." .. ev:name_get(), "event")
            stat_incr("event", true)
        else
            stat_incr("event", false)
        end
    end
end

M.check_method = function(fn, cl)
    local fts = eolian.function_type
    local fulln = cl:full_name_get() .. "." .. fn:name_get()
    if fn:return_type_get(fts.METHOD) then
        if not fn:return_documentation_get(fts.METHOD) then
            print_missing(fulln, "method return")
            stat_incr("mret", true)
        else
            stat_incr("mret", false)
        end
    end
    if not fn:documentation_get(fts.METHOD) then
        print_missing(fulln, "method")
        stat_incr("method", true)
    else
        stat_incr("method", false)
    end
    for p in fn:parameters_get() do
        if not p:documentation_get() then
            print_missing(fulln .. "." .. p:name_get(), "method parameter")
            stat_incr("param", true)
        else
            stat_incr("param", false)
        end
    end
end

M.check_property = function(fn, cl, ft)
    local fts = eolian.function_type

    local pfxs = {
        [fts.PROP_GET] = "g",
        [fts.PROP_SET] = "s",
    }
    local pfx = pfxs[ft]

    local fulln = cl:full_name_get() .. "." .. fn:name_get()
    if fn:return_type_get(ft) then
        if not fn:return_documentation_get(ft) then
            print_missing(fulln, pfx .. "etter return")
            stat_incr(pfx .. "ret", true)
        else
            stat_incr(pfx .. "ret", false)
        end
    end

    if not fn:documentation_get(fts.PROPERTY) and not fn:documentation_get(ft) then
        print_missing(fulln, pfx .. "etter")
        stat_incr(pfx .. "etter", true)
    else
        stat_incr(pfx .. "etter", false)
    end

    for p in fn:property_keys_get(ft) do
        if not p:documentation_get() then
            print_missing(fulln .. "." .. p:name_get(), pfx .. "etter key")
            stat_incr(pfx .. "key", true)
        else
            stat_incr(pfx .. "key", false)
        end
    end

    for p in fn:property_values_get(ft) do
        if not p:documentation_get() then
            print_missing(fulln .. "." .. p:name_get(), pfx .. "etter value")
            stat_incr(pfx .. "value", true)
        else
            stat_incr(pfx .. "value", false)
        end
    end
end

M.check_alias = function(v)
    if not v:documentation_get() then
        print_missing(v:full_name_get(), "alias")
        stat_incr("alias", true)
    else
        stat_incr("alias", false)
    end
end

M.check_struct = function(v)
    if not v:documentation_get() then
        print_missing(v:full_name_get(), "struct")
        stat_incr("struct", true)
    else
        stat_incr("struct", false)
    end
    for fl in v:struct_fields_get() do
        if not fl:documentation_get() then
            print_missing(v:full_name_get() .. "." .. fl:name_get(), "struct field")
            stat_incr("sfield", true)
        else
            stat_incr("sfield", false)
        end
    end
end

M.check_enum = function(v)
    if not v:documentation_get() then
        print_missing(v:full_name_get(), "enum")
        stat_incr("enum", true)
    else
        stat_incr("enum", false)
    end
    for fl in v:enum_fields_get() do
        if not fl:documentation_get() then
            print_missing(v:full_name_get() .. "." .. fl:name_get(), "enum field")
            stat_incr("efield", true)
        else
            stat_incr("efield", false)
        end
    end
end

M.check_constant = function(v)
    if not v:documentation_get() then
        print_missing(v:full_name_get(), "constant")
        stat_incr("constant", true)
    else
        stat_incr("constant", false)
    end
end

M.check_global = function(v)
    if not v:documentation_get() then
        print_missing(v:full_name_get(), "global")
        stat_incr("global", true)
    else
        stat_incr("global", false)
    end
end

M.init = function(verbose)
    is_verbose = verbose
end

return M
