local _, PUP = ... -- Internal namespace

_G["NSAPI"] = {}

local AceComm = LibStub("AceComm-3.0")
local LibSerialize = LibStub("LibSerialize")
local LibDeflate = LibStub("LibDeflate")
local allowedcomms = {
    ["NSI_NICKNAMES_COMMS"] = true,
    ["NSI_NICKNAMES_SYNC"] = true,
}

local del = ":"
function NSAPI:Broadcast(event, channel, ...) -- only used for weakauras, everything in the addon uses the internal PUP function instead.
    local message = event
    local argTable = { ... }
    local target = ""
    local argCount = #argTable
    -- Always send unitID as second argument after event
    local unitID = UnitInRaid("player") and "raid" .. UnitInRaid("player") or UnitName("player")
    message = string.format("%s" .. del .. "%s(%s)", message, unitID, "string")


    for i = 1, argCount do
        local functionArg = argTable[i]
        local argType = type(functionArg)

        if argType == "table" then
            functionArg = LibSerialize:Serialize(functionArg)
            functionArg = LibDeflate:CompressDeflate(functionArg)
            functionArg = LibDeflate:EncodeForWoWAddonChannel(functionArg)
            message = string.format("%s" .. del .. "%s(%s)", message, tostring(functionArg), argType)
        else
            if argType ~= "string" and argType ~= "number" and argType ~= "boolean" then
                functionArg = ""
                argType = "string"
            end
            message = string.format("%s" .. del .. "%s(%s)", message, tostring(functionArg), argType)
        end
    end
    if channel == "WHISPER" then -- create "fake" whisper addon msg that actually just uses RAID instead and will be checked on receive
        AceComm:SendCommMessage("NSWA_MSG2", message, "RAID")
    else
        AceComm:SendCommMessage("NSWA_MSG", message, channel)
    end
end

function PUP:Broadcast(event, channel, ...) -- using internal broadcast function for anything inside the addon to prevent users to send stuff they shouldn't be sending
    local message = event
    local argTable = { ... }
    local target = ""

    local argCount = #argTable
    -- Always send unitID as second argument after event
    local unitID = UnitInRaid("player") and "raid" .. UnitInRaid("player") or UnitName("player")
    message = string.format("%s" .. del .. "%s(%s)", message, unitID, "string")


    for i = 1, argCount do
        local functionArg = argTable[i]
        local argType = type(functionArg)

        if argType == "table" then
            functionArg = LibSerialize:Serialize(functionArg)
            functionArg = LibDeflate:CompressDeflate(functionArg)
            functionArg = LibDeflate:EncodeForWoWAddonChannel(functionArg)
            message = string.format("%s" .. del .. "%s(%s)", message, tostring(functionArg), argType)
        else
            if argType ~= "string" and argType ~= "number" and argType ~= "boolean" then
                functionArg = ""
                argType = "string"
            end
            message = string.format("%s" .. del .. "%s(%s)", message, tostring(functionArg), argType)
        end
    end
    if channel == "WHISPER" then -- create "fake" whisper addon msg that actually just uses RAID instead and will be checked on receive
        AceComm:SendCommMessage("NSI_WHISPER", message, "RAID")
    else
        AceComm:SendCommMessage("NSI_MSG", message, channel)
    end
end

local function ReceiveComm(text, chan, sender, whisper, internal)
    local argTable = { strsplit(del, text) }
    local event = argTable[1]
    if (UnitExists(sender) and (UnitInRaid(sender) or UnitInParty(sender))) or (chan == "GUILD" and allowedcomms[event]) then -- block addon msg's from outside the raid, only exception being a the guild nickname comms.
        local formattedArgTable = {}
        table.remove(argTable, 1)
        if whisper then
            local target, argType = argTable[2]:match("(.*)%((%a+)%)") -- initially first entry is event, 2nd the unitid of the sender and 3rd the whisper target but we already removed first table entry
            if not (UnitIsUnit("player", target)) then
                return
            end
            table.remove(argTable, 2)
        end

        local tonext
        for i, functionArg in ipairs(argTable) do
            local argValue, argType = functionArg:match("(.*)%((%a+)%)")
            if tonext and argValue then argValue = tonext .. argValue end
            if argType == "number" then
                argValue = tonumber(argValue)
                tonext = nil
            elseif argType == "boolean" then
                argValue = argValue == "true"
                tonext = nil
            elseif argType == "table" then
                argValue = LibDeflate:DecodeForWoWAddonChannel(argValue)
                argValue = LibDeflate:DecompressDeflate(argValue)
                local success, t = LibSerialize:Deserialize(argValue)
                if success then
                    argValue = t
                else
                    argValue = ""
                end
                tonext = nil
            end
            if argValue and argType then
                if argValue == "" then
                    table.insert(formattedArgTable, false)
                else
                    table.insert(formattedArgTable, argValue)
                end
                tonext = nil
            end
            if not argType then
                tonext = tonext or ""
                tonext = tonext ..
                functionArg ..
                del                               -- if argtype wasn't given then this is part of a table that was falsely split by the delimeter so we're stitching it back together
            end
        end
        PUP:EventHandler(event, false, internal, unpack(formattedArgTable))
        WeakAuras.ScanEvents(event, unpack(formattedArgTable))
    end
end


AceComm:RegisterComm("NSWA_MSG", function(_, text, chan, sender) ReceiveComm(text, chan, sender, false, false) end)
AceComm:RegisterComm("NSWA_MSG2", function(_, text, chan, sender) ReceiveComm(text, chan, sender, true, false) end)
AceComm:RegisterComm("NSI_MSG", function(_, text, chan, sender) ReceiveComm(text, chan, sender, false, true) end)
AceComm:RegisterComm("NSI_WHISPER", function(_, text, chan, sender) ReceiveComm(text, chan, sender, true, true) end)


function NSAPI:Version() -- function for version check WA
    return 16
end

function NSAPI:Print(...)
    print("added ", ..., " to DevTool Logs")
end

function NSAPI:Shorten(unit, num, role, AddonName, combined) -- Returns color coded Name/Nickname
    local classFile = unit and select(2, UnitClass(unit))
    if role then                                             -- create role icon if requested
        local specid = 0
        if unit then specid = NSAPI:GetSpecs(unit) or WeakAuras.SpecForUnit(unit) or 0 end
        local icon = select(4, GetSpecializationInfoByID(specid))
        if icon then -- if we didn't get the specid can at least try to return the role icon
            role = "\124T" .. icon .. ":12:12:0:0:64:64:4:60:4:60\124t"
        else
            role = UnitGroupRolesAssigned(unit)
            if role ~= "NONE" then
                role = CreateAtlasMarkup(GetIconForRole(role), 0, 0)
            else
                role = ""
            end
        end
    end
    if classFile then -- basically "if unit found"
        local name = UnitName(unit)
        local color = GetClassColorObj(classFile)                                                                             -- shorten name before wrapping in color
        if color then                                                                                              -- should always be true anyway?
            return combined and role .. color:WrapTextInColorCode(name) or color:WrapTextInColorCode(name),
                combined and "" or role
        else
            return combined and role .. name or name, combined and "" or role
        end
    else
        return unit, "" -- return input if nothing was found
    end
end

function NSAPI:GetSpecs(unit)
    if unit then
        return PUP.specs[unit] or
        false                           -- return false if no information available for that unit so it goes to the next fallback
    else
        return PUP.specs -- if no unit is given then entire table is requested
    end
end

function NSAPI:GetNote(disablecheck) -- Get rid of extra spaces and color coding. Also converts nicknames
    if not C_AddOns.IsAddOnLoaded("MRT") then
        print("Addon MRT is disabled, can't read the note")
        return "empty"
    end
    if not VMRT.Note.Text1 then
        print("No MRT Note found")
        return "empty"
    end
    local persnote = _G.VMRT.Note.SelfText or ""
    persnote = strtrim(persnote)
    PUP.persnotedisable = false
    if persnote and persnote ~= "" then
        for line in persnote:gmatch('[^\r\n]+') do
            line = strtrim(line)
            if line == "nsdisable" then
                PUP.persnotedisable = true
                NSAPI.disable = true
                if disablecheck then return "" end
                break
            end
        end
    end
    local note = _G.VMRT.Note.Text1 or ""
    if (not PUP.RawNote) or PUP.RawNote ~= note or (NSAPI.disable and not disable) then -- only do this if the note has changed or not been checked at all this session
        PUP.RawNote = note
        PUP.notedisable = false
        local newnote = ""
        local list = false
        note = strtrim(note)
        for line in note:gmatch('[^\r\n]+') do
            line = strtrim(line)
            if line == "nsdisable" then -- global disable all NS Auras for everyone in the raid
                NSAPI.disable = true
                PUP.notedisable = true
                if disablecheck then return "" end -- end early if we found the only thing we care about
                -- would like to just return "" in all cases here but then interrupt aura stops working with nsdisable.
            end
            --check for start/end of the name list
            if string.match(line, "ns.*start") or line == "intstart" then -- match any string that starts with "ns" and ends with "start" as well as the interrupt WA
                list = true
            elseif string.match(line, "ns.*end") or line == "intend" then
                list = false
                newnote = newnote .. line .. "\n"
            end
            if list then
                newnote = newnote .. line .. "\n"
            end
        end
        if disablecheck then return "" end          -- if all we care about is checking if assignments are disabled then just return an empty string early.
        note = newnote
        note = note:gsub("||r", "")                 -- clean colorcode
        note = note:gsub("||c%x%x%x%x%x%x%x%x", "") -- clean colorcode
        local namelist = {}
        for name in note:gmatch("%S+") do           -- finding all strings
            local charname = (UnitIsVisible(name) and name) or NSAPI:GetChar(name, true, "Note")
            if name ~= charname and UnitExists(charname) and not namelist[name] then
                namelist[name] = charname
            end
        end
        for nickname, charname in pairs(namelist) do
            note = note:gsub("(%f[%w])" .. nickname .. "(%f[%W])", "%1" .. charname .. "%2")
        end
        PUP.Note = note
    end
    NSAPI.disable = PUP.notedisable or PUP.persnotedisable
    PUP.Note = PUP.Note or ""
    return PUP.Note
end

function PUP:UnitAura(unit, spellID) -- simplify aura checking for myself
    if unit and UnitExists(unit) and spellID then
        local spell = C_Spell.GetSpellInfo(spellID)
        return spell and C_UnitAuras.GetAuraDataBySpellName(unit, spell.name)
    end
end

function PUP:Difficultycheck(encountercheck, num) -- check if current difficulty is a Normal/Heroic/Mythic raid and also allow checking if we are currently in an encounter
    local difficultyID = select(3, GetInstanceInfo()) or 0
    return (difficultyID <= 16 and difficultyID >= num) and ((not encountercheck) or PUP:EncounterCheck())
end

function PUP:EncounterCheck()
    return WeakAuras.CurrentEncounter
end

-- this one is public as I want to use it in WeakAuras as well
function NSAPI:DeathCheck(unit)
    if unit and UnitExists(unit) then
        return (UnitIsDead(unit) and not UnitIsFeignDeath(unit)) or PUP:UnitAura(unit, 27827)
    end
end

-- technically don't need this to be public but it's good for backwards compatibility for a while
function NSAPI:GetHash(text)
    local counter = 1
    local len = string.len(text)
    for i = 1, len, 3 do
        counter = math.fmod(counter * 8161, 4294967279) + -- 2^32 - 17: Prime!
            (string.byte(text, i) * 16776193) +
            ((string.byte(text, i + 1) or (len - i + 256)) * 8372226) +
            ((string.byte(text, i + 2) or (len - i + 256)) * 3932164)
    end
    return math.fmod(counter, 4294967291) -- 2^32 - 5: Prime (and different from the prime in the loop)
end

function NSAPI:TTS(sound, voice)        
end

function NSAPI:PrivateAura()
    local now = GetTime()
    if (not NSAPI.LastPAMacro) or NSAPI.LastPAMacro < now - 4 then -- putting this into global NSAPI namespace to allow auras to reset it if ever required
        NSAPI.LastPAMacro = now
        WeakAuras.ScanEvents("NS_PA_MACRO", true)                -- this is for backwards compatibility
        PUP:Broadcast("NS_PAMACRO", "RAID", "nilcheck")            -- this will be used going forward, slightly different wording to prevent issues with old auras
    end
end

function NSAPI:GetChar(name, nick, AddonName)
    return name 
end
