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

local type, print, pairs, tSize, select, unpack, find, format, rep, toString
    = Grichelde.functions.type, Grichelde.functions.print, Grichelde.functions.pairs, Grichelde.functions.tSize, Grichelde.functions.select, Grichelde.functions.unpack, Grichelde.functions.find, Grichelde.functions.format, Grichelde.functions.rep, Grichelde.functions.toString

-- show strings differently to distinguish them from numbers
local function 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.
local function 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) .. plainValue(key) .. " = <nil>")
                elseif type(value) == "table" then
                    printF(rep(" ", indent) .. plainValue(key) .. " = {")
                    if tSize(value) > 0 then
                        if not known[value] then
                            tPrint(value, indent + 4, known, printF)
                            known[value] = true
                        else
                            printF("<known table> " .. plainValue(value))
                        end
                    end
                    printF(rep(" ", indent) .. "}")
                else
                    printF(rep(" ", indent) .. plainValue(key) .. " = " .. plainValue(value))
                end
            end
        else
            printF(rep(" ", indent) .. "{}")
        end
    else
        printF(rep(" ", indent) .. toString(val))
    end
end

-- split 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

-- split 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

-- split at last word of a text line
function Grichelde:TestMatch(text, pattern)
    local _, _, left, right = find(text, pattern, 1)
    self:DebugPrint("TestMatch : left: %s, right: %s", left, right)
end

function Grichelde:Format(message, ...)
    if ( not message ) then
        return "<nil>"
    elseif type(message) == "string" then
        if ( not find(message, "%%")) then
            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"
                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))
            end
        end
    end
end

--- deprecated
function Grichelde:Print(...)
    print(self:Format(...))
end

function Grichelde:PrefixedPrint(...)
    print(self.COLOR_CODES.PREFIX .. self.L.AddonName .. self.COLOR_CODES.CLOSE .. ":", self:Format(...))
end

function Grichelde:DebugPrint(obj, ...)
    self:LogPrint(Grichelde.LOG_LEVEL.DEBUG, function(...)
        print(self.COLOR_CODES.GRAY .. self.L.AddonName .. self.COLOR_CODES.CLOSE .. ":", self:Format(...))
    end, obj, ...)
end

function Grichelde:TracePrint(obj, ...)
    self:LogPrint(Grichelde.LOG_LEVEL.TRACE, function(...)
        print(self.COLOR_CODES.DARKGRAY .. self.L.AddonName .. self.COLOR_CODES.CLOSE .. ":", self:Format(...))
    end, obj, ...)
end

function Grichelde:LogPrint(logLevel, printFunc, obj, ...)
    if (self.logLevel >= logLevel) then
        local printF = printFunc or print
        if obj == nil then
            printF("<nil>")
        else
            if type(obj) == "string" then
                local l = select("#", ...)
                if ( l == 0 or not find(obj, "%%")) 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
                tPrint(obj, 0, {}, printF)
            else
                printF(plainValue(obj))
            end
        end
    end
end

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

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

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

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

    if self.debugFrame 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 = ""
        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