local _, Cell = ...
local L = Cell.L
local F = Cell.funcs

local LibDeflate = LibStub:GetLibrary("LibDeflate")
local deflateConfig = {level = 9}
local Serializer = LibStub:GetLibrary("LibSerialize")
local Comm = LibStub:GetLibrary("AceComm-3.0")

local function Serialize(data)
    local serialized = Serializer:Serialize(data) -- serialize
    local compressed = LibDeflate:CompressDeflate(serialized, deflateConfig) -- compress
    return LibDeflate:EncodeForWoWAddonChannel(compressed) -- encode
end

local function Deserialize(encoded)
    local decoded = LibDeflate:DecodeForWoWAddonChannel(encoded) -- decode
    local decompressed = LibDeflate:DecompressDeflate(decoded) -- decompress
    if not decompressed then
        F:Debug("Error decompressing: " .. errorMsg)
        return
    end
    local success, data = Serializer:Deserialize(decompressed) -- deserialize
    if not success then
        F:Debug("Error deserializing: " .. data)
        return
    end
    return data
end

-----------------------------------------
-- for WA
-----------------------------------------
function F:Notify(type, ...)
    if WeakAuras then
        WeakAuras.ScanEvents("CELL_NOTIFY", type, ...)
    end
end

-----------------------------------------
-- shared
-----------------------------------------
local sendChannel
local function UpdateSendChannel()
    if IsInGroup(LE_PARTY_CATEGORY_INSTANCE) then
        sendChannel = "INSTANCE_CHAT"
    elseif IsInRaid() then
        sendChannel = "RAID"
    else
        sendChannel = "PARTY"
    end
end

-----------------------------------------
-- Check Version
-----------------------------------------
local eventFrame = CreateFrame("Frame")
eventFrame:SetScript("OnEvent", function(self, event, ...)
    self[event](self, ...)
end)

eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
function eventFrame:GROUP_ROSTER_UPDATE()
    if IsInGroup() then
        eventFrame:UnregisterEvent("GROUP_ROSTER_UPDATE")
        UpdateSendChannel()
        Comm:SendCommMessage("CELL_VERSION", Cell.version, sendChannel, nil, "NORMAL")
    end
end

eventFrame:RegisterEvent("PLAYER_LOGIN")
function eventFrame:PLAYER_LOGIN()
    if IsInGuild() then
        Comm:SendCommMessage("CELL_VERSION", Cell.version, "GUILD", nil, "NORMAL")
    end
end

Comm:RegisterComm("CELL_VERSION", function(prefix, message, channel, sender)
    if sender == UnitName("player") then return end
    local version = tonumber(string.match(message, "%d+"))
    local myVersion = tonumber(string.match(Cell.version, "%d+"))
    if (not CellDB["lastVersionCheck"] or time()-CellDB["lastVersionCheck"]>=25200) and version and myVersion and myVersion < version then
        CellDB["lastVersionCheck"] = time()
        F:Print(L["New version found (%s). Please visit %s to get the latest version."]:format(message, "|cFF00CCFFhttps://www.curseforge.com/wow/addons/cell|r"))
    end
end)

-----------------------------------------
-- Notify Marks
-----------------------------------------
Comm:RegisterComm("CELL_MARKS", function(prefix, message, channel, sender)
    if sender == UnitName("player") then return end
    local data = Deserialize(message)
    if Cell.vars.hasPartyMarkPermission and CellDB["tools"]["marks"][1] and (strfind(CellDB["tools"]["marks"][3], "^target") or strfind(CellDB["tools"]["marks"][3], "^both")) and data then
        sender = F:GetClassColorStr(select(2, UnitClass(sender)))..sender.."|r"

        if data[1] then -- lock
            F:Print(L["%s lock %s on %s."]:format(sender, F:GetMarkEscapeSequence(data[2]), data[3]))
        else
            F:Print(L["%s unlock %s from %s."]:format(sender, F:GetMarkEscapeSequence(data[2]), data[3]))
        end
    end
end)

function F:NotifyMarkLock(mark, name, class)
    name = F:GetClassColorStr(class)..name.."|r"
    F:Print(L["%s lock %s on %s."]:format(L["You"], F:GetMarkEscapeSequence(mark), name))
    
    UpdateSendChannel()
    Comm:SendCommMessage("CELL_MARKS", Serialize({true, mark, name}), sendChannel, nil, "ALERT")
end

function F:NotifyMarkUnlock(mark, name, class)
    name = F:GetClassColorStr(class)..name.."|r"
    F:Print(L["%s unlock %s from %s."]:format(L["You"], F:GetMarkEscapeSequence(mark), name))

    UpdateSendChannel()
    Comm:SendCommMessage("CELL_MARKS", Serialize({false, mark, name}), sendChannel, nil, "ALERT")
end

-----------------------------------------
-- Priority Check
-----------------------------------------
local myPriority
local highestPriority = 99
Cell.hasHighestPriority = false

local function UpdatePriority()
    myPriority = 99
    if UnitIsGroupLeader("player") then
        myPriority = 0
    else
        if IsInRaid() then
            for i = 1, GetNumGroupMembers() do
                if UnitIsUnit("player", "raid"..i) then
                    myPriority = i
                    break
                end
            end
        elseif IsInGroup() then -- party
            local players = {}
            local pName, pRealm = UnitFullName("player")
            pRealm = pRealm or GetRealmName()
            pName = pName.."-"..pRealm
            tinsert(players, pName)
            
            for i = 1, GetNumGroupMembers()-1 do
                local name, realm = UnitFullName("party"..i)
                tinsert(players, name.."-"..(realm or pRealm))
            end
            table.sort(players)
            
            for i, p in pairs(players) do
                if p == pName then
                    myPriority = i
                    break
                end
            end
        end
    end

end

local t_check, t_send, t_update
function F:CheckPriority()
    UpdatePriority()
    -- NOTE: needs time to calc myPriority
    C_Timer.After(1, function()
        UpdateSendChannel()
        Comm:SendCommMessage("CELL_CPRIO", "chk", sendChannel, nil, "ALERT")
    end)
    -- if t_check then t_check:Cancel() end
    -- t_check = C_Timer.NewTimer(2, function()
    --     UpdateSendChannel()
    --     Comm:SendCommMessage("CELL_CPRIO", "chk", sendChannel, nil, "BULK")
    -- end)
end

Comm:RegisterComm("CELL_CPRIO", function(prefix, message, channel, sender)
    if not myPriority then return end -- receive CELL_CPRIO just after GOURP_JOINED 
    highestPriority = 99
    
    -- NOTE: wait for check requests
    if t_send then t_send:Cancel() end
    t_send = C_Timer.NewTimer(2, function()
        UpdateSendChannel()
        Comm:SendCommMessage("CELL_PRIO", tostring(myPriority), sendChannel, nil, "ALERT")
    end)
end)

Comm:RegisterComm("CELL_PRIO", function(prefix, message, channel, sender)
    if not myPriority then return end -- receive CELL_PRIO just after GOURP_JOINED

    local p = tonumber(message)
    if p then
        highestPriority = highestPriority < p and highestPriority or p

        if t_update then t_update:Cancel() end
        t_update = C_Timer.NewTimer(2, function()
            Cell.hasHighestPriority = myPriority <= highestPriority
            Cell:Fire("UpdatePriority", Cell.hasHighestPriority)
            F:Debug("|cff00ff00UpdatePriority:|r", Cell.hasHighestPriority)
        end)
    end
end)

-----------------------------------------
-- cross realm send
-----------------------------------------
local function CrossRealmSendCommMessage(prefix, message, playerName, priority, callbackFn)
    -- NOTE: unit needs to be in your group, or it will always return true
    if UnitIsSameServer(playerName) then
        Comm:SendCommMessage(prefix, message, "WHISPER", playerName, priority, callbackFn)
    else
        if UnitInParty(playerName) then
            Comm:SendCommMessage(prefix, playerName..":"..message, "PARTY", nil, priority, callbackFn)
        elseif UnitInRaid(playerName) then
            Comm:SendCommMessage(prefix, playerName..":"..message, "RAID", nil, priority, callbackFn)
        end
    end
end

-----------------------------------------
-- Send / Receive Raid Debuffs and Layouts
-----------------------------------------
local function filterFunc(self, event, msg, player, arg1, arg2, arg3, flag, channelId, ...)
    local newMsg = ""

    local type = msg:match("%[Cell:(.+): .+]")
    if type == "Debuffs" then
        local bossName, instanceName, playerName = msg:match("%[Cell:Debuffs: (.+) %((.+)%) %- ([^%s]+%-[^%s]+)%]")
        if bossName and instanceName and playerName then
            newMsg = "|Hgarrmission:cell-debuffs|h|cFFFF0066["..L[type]..": "..bossName.." ("..instanceName..") - "..playerName.."]|h|r"
        else
            instanceName, playerName = msg:match("%[Cell:Debuffs: (.+) %- ([^%s]+%-[^%s]+)%]")
            if instanceName and playerName then
                newMsg = "|Hgarrmission:cell-debuffs|h|cFFFF0066["..L[type]..": "..instanceName.." - "..playerName.."]|h|r"
            end
        end
    elseif type == "Layout" then
        local layoutName, playerName = msg:match("%[Cell:Layout: (.+) %- ([^%s]+%-[^%s]+)%]")
        if layoutName and playerName then
            if layoutName == "default" then
                -- NOTE: convert "default"
                layoutName = _G.DEFAULT
            end
            newMsg = "|Hgarrmission:cell-layout|h|cFFFF0066["..L[type]..": "..layoutName.." - "..playerName.."]|h|r"
        end
    end

    if newMsg ~= "" then
        return false, newMsg, player, arg1, arg2, arg3, flag, channelId, ...
    end
end

ChatFrame_AddMessageEventFilter("CHAT_MSG_CHANNEL", filterFunc)
ChatFrame_AddMessageEventFilter("CHAT_MSG_YELL", filterFunc)
ChatFrame_AddMessageEventFilter("CHAT_MSG_GUILD", filterFunc)
ChatFrame_AddMessageEventFilter("CHAT_MSG_OFFICER", filterFunc)
ChatFrame_AddMessageEventFilter("CHAT_MSG_PARTY", filterFunc)
ChatFrame_AddMessageEventFilter("CHAT_MSG_PARTY_LEADER", filterFunc)
ChatFrame_AddMessageEventFilter("CHAT_MSG_RAID", filterFunc)
ChatFrame_AddMessageEventFilter("CHAT_MSG_RAID_LEADER", filterFunc)
ChatFrame_AddMessageEventFilter("CHAT_MSG_SAY", filterFunc)
ChatFrame_AddMessageEventFilter("CHAT_MSG_WHISPER", filterFunc)
ChatFrame_AddMessageEventFilter("CHAT_MSG_WHISPER_INFORM", filterFunc)
ChatFrame_AddMessageEventFilter("CHAT_MSG_BN_WHISPER", filterFunc)
ChatFrame_AddMessageEventFilter("CHAT_MSG_BN_WHISPER_INFORM", filterFunc)
ChatFrame_AddMessageEventFilter("CHAT_MSG_INSTANCE_CHAT", filterFunc)
ChatFrame_AddMessageEventFilter("CHAT_MSG_INSTANCE_CHAT_LEADER", filterFunc)

local isRequesting

-- NOTE: received
Comm:RegisterComm("CELL_SEND", function(prefix, message, channel, sender)
    if not isRequesting then return end
    if channel ~= "WHISPER" then
        local target
        target, message = strsplit(":", message, 2)
        if target ~= Cell.vars.playerNameFull then
            return
        end
    end

    local receivedData = Deserialize(message)

    if Cell.frames.receivingFrame then
        if receivedData then
            Cell.frames.receivingFrame:ShowImport(true, receivedData, function()
                isRequesting = false
            end)
        else
            Cell.frames.receivingFrame:ShowImport(false, nil, function()
                isRequesting = false
            end)
        end
    end
end)

-- NOTE: progress received
Comm:RegisterComm("CELL_SEND_PROG", function(prefix, message, channel, sender)
    if not isRequesting then return end
    if channel ~= "WHISPER" then
        local target
        target, message = strsplit(":", message, 2)
        if target ~= Cell.vars.playerNameFull then
            return
        end
    end
    
    local done, total = strsplit("|", message)
    done, total = tonumber(done), tonumber(total)

    if Cell.frames.receivingFrame then
        Cell.frames.receivingFrame:ShowProgress(done, total)
    end
end)

-- NOTE: request received
Comm:RegisterComm("CELL_REQ", function(prefix, message, channel, requester)
    if channel ~= "WHISPER" then
        local target
        target, message = strsplit(":", message, 2)
        if target ~= Cell.vars.playerNameFull then
            return
        end
    end

    -- check request
    local type, name1, name2 = strsplit(":", message)
    local requestData

    -- print(type, name1, name2)
    if type == "Debuffs" then
        local instanceId, bossId = F:GetInstanceAndBossId(name1, name2)
        if not instanceId then return end -- invalid instanceName

        requestData = {
            ["type"] = "Debuffs",
            ["instanceId"] = instanceId,
            ["bossId"] = bossId,
            ["version"] = Cell.versionNum
        }

        -- check db
        if not bossId then -- all bosses
            if CellDB["raidDebuffs"][instanceId] then
                requestData["data"] = CellDB["raidDebuffs"][instanceId]
            end
        else
            if CellDB["raidDebuffs"][instanceId] and CellDB["raidDebuffs"][instanceId][bossId] then
                requestData["data"] = CellDB["raidDebuffs"][instanceId][bossId]
            end
        end
    elseif type == "Layout" then
        if name1 == _G.DEFAULT then
            -- NOTE: convert "DEFAULT"
            name1 = "default"
        end
        if name1 and CellDB["layouts"][name1] then
            requestData = {
                ["type"] = "Layout",
                ["name"] = name1,
                ["version"] = Cell.versionNum,
                ["data"] = CellDB["layouts"][name1]
            }
        end
    end

    -- texplore(requestData)

    if not requestData then return end
    CrossRealmSendCommMessage("CELL_SEND", Serialize(requestData), requester, "BULK", function(arg, done, total)
        -- send progress
        CrossRealmSendCommMessage("CELL_SEND_PROG", done.."|"..total, requester, "ALERT")
    end)
end)

local function ShowReceivingFrame(type, playerName, name1, name2)
    if not Cell.frames.receivingFrame then
        Cell.frames.receivingFrame = Cell:CreateReceivingFrame(Cell.frames.mainFrame)
        Cell.frames.receivingFrame:SetOnCancel(function(b)
            isRequesting = false
        end)
    end
    
    Cell.frames.receivingFrame:SetOnRequest(function(b)
        isRequesting = true
        --! send request
        CrossRealmSendCommMessage("CELL_REQ", type..":"..name1..":"..(name2 or ""), playerName, "ALERT")
    end)
    
    Cell.frames.receivingFrame:ShowFrame(type, playerName, name1, name2)
end

hooksecurefunc("SetItemRef", function(link, text)
    if isRequesting then return end
    if link == "garrmission:cell-debuffs" then
        local bossName, instanceName, playerName = text:match("|Hgarrmission:cell%-debuffs|h|cFFFF0066%[.+: (.+) %((.+)%) %- ([^%s]+%-[^%s]+)%]|h|r")
        if bossName and instanceName and playerName then
            ShowReceivingFrame("Debuffs", playerName, instanceName, bossName)
        else
            instanceName, playerName = text:match("|Hgarrmission:cell%-debuffs|h|cFFFF0066%[.+: (.+) %- ([^%s]+%-[^%s]+)%]|h|r")
            ShowReceivingFrame("Debuffs", playerName, instanceName)
        end
    elseif link == "garrmission:cell-layout" then
        local layoutName, playerName = text:match("|Hgarrmission:cell%-layout|h|cFFFF0066%[.+: (.+) %- ([^%s]+%-[^%s]+)%]|h|r")
        ShowReceivingFrame("Layout", playerName, layoutName)
    end
end)