-- import addon read namespace from global env
local _G = _G
local Grichelde = _G.Grichelde or {}

local type, print, pairs, tSize, select, unpack, find, cGray, cDarkgray, cRed, cPrefix, format, rep, toString
    = Grichelde.F.type, Grichelde.F.print, Grichelde.F.pairs, Grichelde.F.tSize, Grichelde.F.select, Grichelde.F.unpack, Grichelde.F.find, Grichelde.F.cGray, Grichelde.F.cDarkgray, Grichelde.F.cRed, Grichelde.F.cPrefix, Grichelde.F.format, Grichelde.F.rep, Grichelde.F.toString

-- show strings differently to distinguish them from numbers
function Grichelde:plainValue(val)
    if (val == nil) then
        return "<nil>"
    elseif (type(val) == "string") then
        return '"' .. val .. '"'
    elseif (type(val) == "table") then
        if (tSize(val) > 0) then
            return toString(val)
        else
            return "{}"
        end
    else
        return toString(val)
    end
end

--- Prints any value to default channel, do NOT return a string.
function Grichelde:tPrint(val, indent, known, printFunc)
    local printF = printFunc or print
    indent = indent or 0
    known = known or {}

    if (val == nil) then
        printF(rep(" ", indent) .. "<nil>")
    elseif (type(val) == "string") then
        printF(rep(" ", indent) .. "\"" .. val .. "\"")
    elseif (type(val) == "table") then
        if (tSize(val) > 0) then
            for key, value in pairs(val) do
                if (value == nil) then
                    printF(rep(" ", indent) .. self:plainValue(key) .. " = <nil>")
                elseif (type(value) == "table") then
                    printF(rep(" ", indent) .. self:plainValue(key) .. " = {")
                    if (tSize(value) > 0) then
                        if not known[value] then
                            self:tPrint(value, indent + 4, known, printF)
                            known[value] = true
                        else
                            printF("<known table> " .. self:plainValue(value))
                        end
                    end
                    printF(rep(" ", indent) .. "}")
                else
                    local k = self:plainValue(key)
                    local v = self:plainValue(value)
                    --print( "k: " .. k .. ", v: " ..v)
                    printF(rep(" ", indent) .. k .. " = " .. v)
                end
            end
        else
            printF(rep(" ", indent) .. "{}")
        end
    else
        printF(rep(" ", indent) .. toString(val))
    end
end

--- Splits at first word of a text line
function Grichelde:SplitOnFirstMatch(text, delimPattern, start)
    if (text == nil) then return nil end
    local pattern = "^(.-)" .. (delimPattern or " " ) .."(.*)"
    local pos = start or 1
    self:TracePrint("SplitOnFirstMatch : text: %s, pattern: %s, start: %d", text, pattern, start)
    local _, _, left, right = find(text, pattern, pos)
    self:TracePrint("SplitOnFirstMatch : left: %s, right: %s", left, right)
    return left or text, right
end

--- Splits at last word of a text line
function Grichelde:SplitOnLastMatch(text, delimPattern, start)
    local pattern = "(.*)" .. (delimPattern or " ") .. "(.-)$"
    local pos = start or 1
    self:TracePrint("SplitOnLastMatch : text: %s, pattern: %s, start: %d", text, pattern, start)
    local _, _, left, right = find(text, pattern, pos)
    self:TracePrint("SplitOnLastMatch : left: %s, right: %s", left, right)
    return left, right or text
end

function Grichelde:Format(message, ...)
    if (message == nil) then
        return "<nil>"
    elseif (type(message) == "string") then
        if (find(message, "%%") == nil) then
            --print("message: ", message)
            --print("...: ", ...)
            return message, ...
        else
            local l = select("#", ...)
            if (l > 0) then
                -- sanitize nil values in vararg
                local packed = { ... }
                for i = 1, l do
                    packed[i] = toString(packed[i]) or "nil"
                    --print("packed[i] = ", packed[i])
                    --packed[i] = gsub(packed[i], "%%(%d)", "%%%%%1")
                end
--              print("packed = ", packed)
--              self:tPrint(packed)
                -- cannot assign unpacked to a vararg variable and print it for debug
                -- Manually set count as unpack() stops on nil (bug with #table)
                return format(message, unpack(packed, 1, l))
            else
                return message
            end
        end
    end
end

function Grichelde:PrefixedPrint(...)
    print(cPrefix(self.L.AddonName) .. ":", self:Format(...))
end

function Grichelde:ErrorPrint(...)
    print(cPrefix(self.L.AddonName) .. ": " .. cRed(self:Format(...)))
end

function Grichelde:DebugPrint(obj, ...)
    if (self.logLevel >= Grichelde.LOG_LEVEL.DEBUG) then
        self:LogPrint(function(...)
            print(cGray(self.L.AddonName) .. ":", self:Format(...))
        end, obj, ...)
    end
end

function Grichelde:TracePrint(obj, ...)
    if (self.logLevel >= Grichelde.LOG_LEVEL.TRACE) then
        self:LogPrint(function(...)
            print(cDarkgray(self.L.AddonName) .. ":", self:Format(...))
        end, obj, ...)
    end
end

function Grichelde:LogPrint(printFunc, obj, ...)
    local printF = printFunc or print
    if obj == nil then
        printF("<nil>")
    else
        if type(obj) == "string" then
            local l = select("#", ...)
            if (l == 0) or (find(obj, "%%") == nil) then
                printF(obj, ...)
            else
                -- sanitize nil values in vararg
                local packed = { ... }
                for i = 1, l do
                    packed[i] = toString(packed[i]) or "nil"
                end

--              print("packed = ", packed)
--              self:tPrint(packed)
                -- cannot assign unpacked to a vararg variable and print it for debug
                local fmtMsg = format(obj, unpack(packed, 1, l)) -- manually set count as unpack() stops on nil (bug with #table)
                printF(fmtMsg)
            end
        elseif (type(obj) == "table") then
            self:tPrint(obj, 0, {}, printF)
        else
            printF(self:plainValue(obj))
        end
    end
end

--- Print UI options to chat frame
function Grichelde:PrintOptions()
    self:PrefixedPrint(cPrefix(self.L.Debug_Options))
    self:LogPrint(-1, function(...)
        print(cPrefix(self.L.AddonName) .. ":", self:Format(...))
    end, self.options.args.replacements.args)
end

--- Print current DB profile
function Grichelde:PrintProfile()
    self:PrefixedPrint(cPrefix(self.L.Debug_Profile))
    self:LogPrint(-1, function(...)
        print(cPrefix(self.L.AddonName) .. ":", self:Format(...))
    end, self.db.profile)
end

--- Print DB replacements to chat frame
function Grichelde:PrintMappings()
    self:PrefixedPrint(cPrefix(self.L.Debug_Mappings))
    self:LogPrint(-1, function(...)
        print(cPrefix(self.L.AddonName) .. ":", self:Format(...))
    end, self.db.profile.replacements)
end

--- Open window with DB replacements in it
function Grichelde:ToogleMappings()
    local AceGUI = LibStub("AceGUI-3.0")

    if (self.debugFrame ~= nil) then
        AceGUI:Release(self.debugFrame)
        self.debugFrame = nil
    else
        local replacements = self.db.profile.replacements or {}
        local repls = 0
        for k, _ in pairs(replacements) do
            if k and find(k, "^replacement_") then
                repls = repls + 1
            end
        end

        local frame = AceGUI:Create("Frame");
        frame:SetTitle(self.L.Debug_Mappings);
        frame:SetStatusText(self:Format(self.L.Debug_Mappings_Found, repls))
        frame:SetWidth(400);
        frame:SetAutoAdjustHeight(true)
        --frame:SetFullHeight(true)
        frame:SetLayout("Flow");
        local function closeFrame(widget) AceGUI:Release(widget); self.debugFrame = nil end
        frame:SetCallback("OnClose", closeFrame)

        local hint = AceGUI:Create("Label");
        hint:SetText(self.L.Debug_Mappings_Hint)
        hint:SetFullWidth(true)
        frame:AddChild(hint);

        local scroll = AceGUI:Create("ScrollFrame");
        scroll:SetFullWidth(true)
        scroll:SetFullHeight(true)
        scroll:SetLayout("Fill");

        local configBox = AceGUI:Create("MultiLineEditBox");
        configBox:SetLabel("")
        local text = ""
        self:tPrint(replacements, 0, {}, function(s) text = text .. s .. "|n" end)
        configBox:SetText(text)
        configBox:SetFullWidth(true)
        --configBox:SetFullHeight(true)
        configBox:DisableButton(true)
        --configBox:SetDisabled(true)
        configBox:SetNumLines(50);
        configBox:SetFocus()
        scroll:AddChild(configBox);

        frame:AddChild(scroll);
        self.debugFrame = frame
    end
end