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

-- constants and upvalues
Grichelde.LOG_LEVEL = { DEBUG = 1, TRACE = 2 }

Grichelde.INPUT_LIMIT = 255
Grichelde.ENDLESS_LOOP_LIMIT = 10000
Grichelde.MAPPING_OFFSET = 10
Grichelde.MINIMAP_ENABLED = 1.0
Grichelde.MINIMAP_DARKENDED = 0.5

Grichelde.ICONS = {
    MOVE_UP = "Interface\\MainMenuBar\\UI-MainMenu-ScrollUpButton-Up",
    MOVE_UP_DISABLED = "Interface\\MainMenuBar\\UI-MainMenu-ScrollUpButton-Disabled",
    MOVE_DOWN = "Interface\\MainMenuBar\\UI-MainMenu-ScrollDownButton-Up",
    MOVE_DOWN_DISABLED = "Interface\\MainMenuBar\\UI-MainMenu-ScrollDownButton-Disabled",
    DELETE = "Interface\\Buttons\\UI-Panel-MinimizeButton-Up",
    DELETE_DISABLED = "Interface\\Buttons\\UI-Panel-MinimizeButton-Disabled",
}

-- colors:
Grichelde.COLORS = {
    NORMAL    = _G.NORMAL_FONT_COLOR,
    HIGHLIGHT = _G.HIGHLIGHT_FONT_COLOR,
    RED       = _G.RED_FONT_COLOR,
    GREEN     = _G.GREEN_FONT_COLOR,
}

Grichelde.COLOR_CODES = {
    PREFIX      = "|c00FFAA00",
    -- https://github.com/stoneharry/Misc-WoW-Stuff/blob/master/EoC%20Interface/FrameXML/Constants.lua
    NORMAL      = _G.NORMAL_FONT_COLOR_CODE      or "|cffffd200",
    HIGHLIGHT   = _G.HIGHLIGHT_FONT_COLOR_CODE   or "|cffffffff",
    RED         = _G.RED_FONT_COLOR_CODE         or "|cffff2020",
    GREEN       = _G.GREEN_FONT_COLOR_CODE       or "|cff20ff20",
    LIGHTGRAY   = "|cffC0C0C0",
    GRAY        = _G.GRAY_FONT_COLOR_CODE        or "|cff808080",
    DARKGRAY    = "|cff404040",
    YELLOW      = _G.YELLOW_FONT_COLOR_CODE      or "|cffffff00",
    LIGHTYELLOW = _G.LIGHTYELLOW_FONT_COLOR_CODE or "|cffffff9a",
    ORANGE      = _G.ORANGE_FONT_COLOR_CODE      or "|cffff7f3f",
    BLUE        = "|cff0000ff",
    HYPERLINK   = "|cff4040ff",
    CLOSE       = _G.FONT_COLOR_CODE_CLOSE       or "|r",
}

Grichelde.SLASH_COMMANDS = { "gri", "grichelde" }

Grichelde.SUPPORTED_CHAT_COMMANDS = {
    ["/s"]               = "SAY",
    ["/say"]             = "SAY",
    ["/sprechen"]        = "SAY",
    ["/c"]               = "CHANNEL",
    ["/csay"]            = "CHANNEL",
    ["/e"]               = "EMOTE",
    ["/em"]              = "EMOTE",
    ["/me"]              = "EMOTE",
    ["/emote"]           = "EMOTE",
    ["/y"]               = "YELL",
    ["/yell"]            = "YELL",
    ["/sh"]              = "YELL",
    ["/shout"]           = "YELL",
    ["/schreien"]        = "YELL",
    ["/sch"]             = "YELL",
    ["/p"]               = "PARTY",
    ["/party"]           = "PARTY",
    ["/gruppe"]          = "PARTY",
    ["/pl"]              = "PARTY",
    ["/partyleader"]     = "PARTY",
    ["/g"]               = "GUILD",
    ["/gc"]              = "GUILD",
    ["/guild"]           = "GUILD",
    ["/gilde"]           = "GUILD",
    ["/o"]               = "OFFICER",
    ["/osay"]            = "OFFICER",
    ["/officer"]         = "OFFICER",
    ["/offizier"]        = "OFFICER",
    ["/raid"]            = "RAID",
    ["/rsay"]            = "RAID",
    ["/rl"]              = "RAID",
    ["/rsay"]            = "RAID",
    ["/raidleader"]      = "RAID",
    ["/schlachtzug"]     = "RAID",
    ["/rw"]              = "RAID_WARNING",
    ["/raidwarning"]     = "RAID_WARNING",
    ["/i"]               = "INSTANCE_CHAT",
    ["/instance"]        = "INSTANCE_CHAT",
    ["/instanz"]         = "INSTANCE_CHAT",
    ["/bg"]              = "BATTLEGROUND",
    ["/battleground"]    = "BATTLEGROUND",
    ["/schlachfeld"]     = "BATTLEGROUND",
    ["/w"]               = "WHISPER",
    ["/whisper"]         = "WHISPER",
    ["/t"]               = "WHISPER",
    ["/tell"]            = "WHISPER",
    ["/send"]            = "WHISPER",
    ["/tt"]              = "WHISPER",
    ["/r"]               = "WHISPER",
    ["/reply"]           = "WHISPER",
    ["/fl\195\188stern"] = "WHISPER",
    ["/antworten"]       = "WHISPER",
}

Grichelde.BLIZZ_TYPE_TO_OPTIONS = {
    ["SAY"]          = "say",
    ["EMOTE"]        = "emote",
    ["YELL"]         = "yell",
    ["PARTY"]        = "party",
    ["GUILD"]        = "guild",
    ["OFFICER"]      = "officer",
    ["RAID"]         = "raid",
    ["RAID_WARNING"] = "raidWarning",
    ["INSTANCE"]     = "instance",
    ["BATTLEGROUND"] = "battleground",
    ["WHISPER"]      = "whisper",
}

-- do not replace these patterns
-- combined item links in the chat will look like this: |cff9d9d9d|Hitem:3299::::::::20:257::::::|h[Fractured Canine]|h|r
Grichelde.IGNORE_PATTERNS = {
    LINKS = {
        "|[Cc]%x%x%x%x%x%x%x%x.-|r", -- colored items (or links)
        "|H.-|h",     -- item links (http://www.wowwiki.com/ItemLink)
        "|T.-|t",     -- textures
        "|K.-|k",     -- Battle.net
        "|n",         -- newline
    },
    EMOTES = {
        "%*.-%*",     -- emotes *
        "%*%*.-%*%*", -- emotes **
        "%<.-%>",     -- emotes < >
    },
    SUBSTITUTES = {
        "%%n",   -- player's name
        "%%z",   -- player's currnt zone
        "%%sz",  -- player's current sub-zone
        "%%loc", -- player's map coordinates
        "%%t",   -- name of target
        "%%f",   -- name of focus target
        "%%m",   -- name of mouseover unit
        "%%p",   -- name of player pet
        "%%tt",  -- name of player's target's target
    },
    RAID_TARGETS = {
        "{rt[1-8]}", -- rumbered raid target icons, localized raid targets are handled differently
    },
    LOCALIZED_RAID_TARGETS = {
        "Star",
        "Circle",
        "Diamond",
        "Triangle",
        "Moon",
        "Square",
        "Cross",
        "Skull",
    },
    OOC_BRACKETS = {
        "%(%(.-%)%)",           -- (( ... ))
        "%(%s*ooc[%:%s].-%)",   -- ( ooc )
    },
    OOC_NO_BRACKETS = {
        "ooc[%:%s]",
    },
}

local function nilOrEmpty(s)
    return s == nil or s:trim() == ""
end

local function spairs(t, orderFunc)
    -- collect the keys
    local sortedKeys = {}
    -- for every non-nil value
    for key, _ in Grichelde.F.pairs(t) do
        Grichelde.F.tInsert(sortedKeys, key)
    end

    if (orderFunc ~= nil) then
        Grichelde.F.tSort(sortedKeys, function(a, b) return orderFunc(sortedKeys, a, b) end)
    else
        -- lexicographical order
        Grichelde.F.tSort(sortedKeys)
    end

    -- return the iterator function
    local it = 0
    return function()
        it = it + 1
        if (sortedKeys[it] ~= nil) then
            return sortedKeys[it], t[sortedKeys[it]]
        else
            return nil
        end
    end
end

local function tFilter(t, condition, extract)
    local filtered = {}
    for key, value in Grichelde.F.pairs(t) do
        local cond = false
        if (condition) then
            local t = Grichelde.F.type(condition)
            if (t == "function") then
                cond = condition(t, key, value)
            elseif (t == "string") or (t == "number") then
                cond = (value == condition)
            end
        end

        if (cond) then
            local val = value
            if (extract and (Grichelde.F.type(extract) == "function")) then
                val = extract(t, key, value)
            end
            Grichelde.F.tInsert(filtered, val)
        end
    end
    return filtered
end

local function tSize(t)
    local size = 0
    if (t ~= nil) then
        -- for every non-nil value
        for _, _ in Grichelde.F.pairs(t) do
            size = size + 1
        end
    end
    return size
end

local function tIsEmpty(t)
    if (not t) then return true end
    return Grichelde.F.tNext(t) == nil
end

local function tClone(orig)
    local orig_type = Grichelde.F.type(orig)
    local copy
    if (orig_type == 'table') then
        copy = {}
        -- for every non-nil value
        for orig_key, orig_value in Grichelde.F.tNext, orig, nil do
            copy[tClone(orig_key)] = tClone(orig_value)
        end
        Grichelde.F.setmetatable(copy, tClone(Grichelde.F.getmetatable(orig)))
    else -- number, string, boolean, etc
        copy = orig
    end
    return copy
end

local function getNextCharUtf8(word)
    if ((word == nil) or (Grichelde.F.type(word) ~= "string") or (Grichelde.F.length(word) < 1)) then
        return nil, nil
    end

    local wordLength = Grichelde.F.length(word)
    local c1 = Grichelde.F.toByte(word, 1)
    if (c1 > 0) and (c1 <= 127) then
        -- UTF8-1
        return Grichelde.F.bytes2Char(c1), Grichelde.F.sub(word, 2)
    elseif (c1 >= 194) and (c1 <= 223) then
        -- UTF8-2
        Grichelde.F.assert(wordLength >= 2, "broken UTF-8 character")
        local c2 = Grichelde.F.toByte(word, 2)
        return Grichelde.F.bytes2Char(c1, c2), Grichelde.F.sub(word, 3)
    elseif (c1 >= 224) and (c1 <= 239) then
        -- UTF8-3
        Grichelde.F.assert(wordLength >= 3, "broken UTF-8 character")
        local c2 = Grichelde.F.toByte(word, 2)
        local c3 = Grichelde.F.toByte(word, 3)
        return Grichelde.F.bytes2Char(c1, c2, c3), Grichelde.F.sub(word, 4)
    elseif (c1 >= 240) and (c1 <= 244) then
        -- UTF8-4
        Grichelde.F.assert(wordLength >= 4, "broken UTF-8 character")
        local c2 = Grichelde.F.toByte(word, 2)
        local c3 = Grichelde.F.toByte(word, 3)
        local c4 = Grichelde.F.toByte(word, 4)
        return Grichelde.F.bytes2Char(c1, c2, c3, c4), Grichelde.F.sub(word, 5)
    else
        return nil, nil
    end
end

local function isLetter(word)
    local char = Grichelde.F.getNextCharUtf8(word)
    return (char ~= nil) and (Grichelde.F.toUpper(char) ~= Grichelde.F.toLower(char))
end

local function isNumber(digit)
    if ((digit == nil) or (Grichelde.F.type(digit) ~= "string") or (Grichelde.F.length(digit) < 1)) then
        return false
    else
        return Grichelde.F.find(digit, "%d+") ~= nil
    end
end

local function isUpper(word)
    if ((word == nil) or (Grichelde.F.type(word) ~= "string") or (Grichelde.F.length(word) < 1)) then
        return false
    elseif (Grichelde.F.toUpper(word) == Grichelde.F.toLower(word)) then
        return false
    else
        return word == Grichelde.F.toUpper(word)
    end
end

local function isLower(word)
    if ((word == nil) or (Grichelde.F.type(word) ~= "string") or (Grichelde.F.length(word) < 1)) then
        return false
    elseif (Grichelde.F.toUpper(word) == Grichelde.F.toLower(word)) then
        return false
    else
        return word == Grichelde.F.toLower(word)
    end
end

local function isCapital(word)
    if ((word == nil) or (Grichelde.F.type(word) ~= "string") or (Grichelde.F.length(word) < 1)) then
        return false
    else
        local first, rest = Grichelde.F.getNextCharUtf8(word)
        local isCapital = Grichelde.F.isUpper(first)
        if (rest ~= nil) then
            return isCapital and Grichelde.F.isLower(rest)
        else
           return isCapital
        end
    end
end

local function capitalize(word)
    if ((word == nil) or (Grichelde.F.type(word) ~= "string") or (Grichelde.F.length(word) < 1)) then
        return ""
    else
        local first, rest = Grichelde.F.getNextCharUtf8(word)
        local capital = Grichelde.F.toUpper(first)
        if (rest ~= nil) then
            return capital .. Grichelde.F.toLower(rest)
        else
           return capital
        end
    end
end

local function color(color, text)
    return color .. text .. Grichelde.COLOR_CODES.CLOSE
end

local function cPrefix(text)
    return Grichelde.F.color(Grichelde.COLOR_CODES.PREFIX, text)
end

local function cYellow(text)
    return Grichelde.F.color(Grichelde.COLOR_CODES.NORMAL, text)
end

local function cGray(text)
    return Grichelde.F.color(Grichelde.COLOR_CODES.GRAY, text)
end

local function cDarkgray(text)
    return Grichelde.F.color(Grichelde.COLOR_CODES.DARKGRAY, text)
end

local function cGreen(text)
    return Grichelde.F.color(Grichelde.COLOR_CODES.GREEN, text)
end

local function cOrange(text)
    return Grichelde.F.color(Grichelde.COLOR_CODES.ORANGE, text)
end

local function cRed(text)
    return Grichelde.F.color(Grichelde.COLOR_CODES.RED, text)
end

local function cHyperlink(text)
    return Grichelde.F.color(Grichelde.COLOR_CODES.HYPERLINK, text)
end

-- faster function lookups by mapping to local refs
Grichelde.F = {
    IsAddOnLoaded   = _G.IsAddOnLoaded,
    assert          = _G.assert,
    type            = _G.type,
    print           = _G.print,
    nilOrEmpty      = nilOrEmpty,
    pairs           = _G.pairs,
    ipairs          = _G.ipairs,
    spairs          = spairs,
    tContains       = _G.tContains,
    tFilter         = tFilter,
    tInsert         = _G.table.insert,
    tConcat         = _G.table.concat,
    tSize           = tSize,
    tIsEmpty        = tIsEmpty,
    tSort           = _G.table.sort,
    tClone          = tClone,
    tNext           = _G.next,
    tWipe           = _G.wipe,
    setmetatable    = _G.setmetatable,
    getmetatable    = _G.getmetatable,
    select          = _G.select,
    unpack          = _G.unpack,
    find            = _G.string.find,
    sub             = _G.string.sub,
    gsub            = _G.string.gsub,
    match           = _G.strmatch,
    gmatch          = _G.string.gmatch,
    join            = _G.strjoin,
    split           = _G.strsplit,
    toUpper         = _G.strupper,
    toLower         = _G.strlower,
    getNextCharUtf8 = getNextCharUtf8,
    isLetter        = isLetter,
    isNumber        = isNumber,
    isUpper         = isUpper,
    isLower         = isLower,
    isCapital       = isCapital,
    capitalize      = capitalize,
    color           = color,
    cPrefix         = cPrefix,
    cYellow         = cYellow,
    cGray           = cGray,
    cDarkgray       = cDarkgray,
    cGreen          = cGreen,
    cOrange         = cOrange,
    cRed            = cRed,
    cHyperlink      = cHyperlink,
    toByte          = _G.string.byte,
    bytes2Char      = _G.string.char,
    format          = _G.string.format,
    rep             = _G.string.rep,
    trim            = _G.strtrim,
    length          = _G.string.len,
    lengthUtf8      = _G.strlenutf8,
    toString        = _G.tostring,
    toNumber        = _G.tonumber,
    max             = _G.math.max,
    min             = _G.math.min,
}