
local AceSerializer = assert(LibStub('AceSerializer-3.0', true), 'BroadHandler-1.0 requires AceSerializer-3.0')
local CallbackHandler = assert(LibStub('CallbackHandler-1.0', true), 'BroadHandler-1.0 requires CallbackHandler-1.0')
local CTL = assert(ChatThrottleLib, 'BroadHandler-1.0 requires ChatThrottleLib')
local Base64 = assert(LibStub('NetEaseBase64-1.0', true), 'BroadHandler-1,0 requires NetEaseBase64-1.0')

local MAJOR, MINOR = 'NetEaseBroadMiddleware-2.0', 2
local BroadMiddleware,oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not BroadMiddleware then return end

local msgCacheMeta = {__index = function(t, k)
    t[k] = {}
    return t[k]
end}

BroadMiddleware.EventHandler = BroadMiddleware.EventHandler or CreateFrame('Frame')

BroadMiddleware.channels = BroadMiddleware.channels or {}
BroadMiddleware.handlers = BroadMiddleware.handlers or setmetatable({}, {
    __index = function(t, k)
        t[k] = {}
        t[k].msgCache = setmetatable({}, msgCacheMeta)
        t[k].FireBroad = CallbackHandler:New(t[k], 'RegisterBroad', 'UnregisterBroad', 'UnregisterAllBroad').Fire
        t[k].FireServerBroad = CallbackHandler:New(t[k], 'RegisterServerBroad', 'UnregisterServerBroad', 'UnregisterAllServerBroad').Fire
        return t[k]
    end
})

local handlers = BroadMiddleware.handlers
local channels = BroadMiddleware.channels
local EventHandler = BroadMiddleware.EventHandler

local PACKET_FIRST = '#'
local PACKET_NEXT  = '&'
local PACKET_LAST  = '$'
local PACKET_ONE   = '@'
local MAX_LEN = 254

local function safeReadString(value, required)
    if type(issecretvalue) == 'function' then
        local ok, secret = pcall(issecretvalue, value)
        if not ok or secret then
            return nil, false
        end
    end

    local ok, result = pcall(function()
        if value == nil and not required then
            return nil
        end
        if type(value) ~= 'string' then
            return nil
        end
        return value
    end)
    if not ok then
        return nil, false
    end
    if required and result == nil then
        return nil, false
    end
    return result, true
end

local function safeAmbiguate(value)
    local ok, result = pcall(Ambiguate, value, 'none')
    if ok and type(result) == 'string' and result ~= '' then
        return result
    end
end

EventHandler:UnregisterAllEvents()
EventHandler:RegisterEvent('CHAT_MSG_CHANNEL')
EventHandler:SetScript('OnEvent', function(self, event, msg, target, _, _, _, flag, _, channelId, channelName)
    local ok

    msg, ok = safeReadString(msg, true)
    if not ok then
        return
    end

    target, ok = safeReadString(target, true)
    if not ok then
        return
    end

    flag, ok = safeReadString(flag, false)
    if not ok then
        return
    end

    channelName, ok = safeReadString(channelName, true)
    if not ok then
        return
    end

    if not channels[channelName] then
        return
    end

    local control, data
    ok, control, data = pcall(function()
        return msg:match('^([#&$@])(.*)$')
    end)
    if not ok or not control then
        return
    end

    target = safeAmbiguate(target)
    if not target then
        return
    end

    local handler = handlers[channelName]
    local cache = handler.msgCache[target]

    if control == PACKET_FIRST then
        tinsert(wipe(cache), data)
    elseif control == PACKET_NEXT or control == PACKET_LAST then
        tinsert(cache, data)
    end

    if control == PACKET_LAST or control == PACKET_ONE then
        if control == PACKET_LAST then
            data = tconcat(cache)
            wipe(cache)
        end
        self:DealPacket(handler, target, flag, self:Decode(data))
    end
end)

function EventHandler:Decode(data)
    local ok, decoded = pcall(Base64.DeCode, Base64, data)
    if not ok or type(decoded) ~= 'string' then
        return false
    end

    local success, a, b, c, d, e, f, g, h, i, j = pcall(AceSerializer.Deserialize, AceSerializer, decoded)
    if not success then
        return false
    end
    return a, b, c, d, e, f, g, h, i, j
end

function EventHandler:Encode(...)
    return Base64:EnCode(AceSerializer:Serialize(...))
end

function EventHandler:DealPacket(handler, target, flag, ok, cmd, ...)
    if not ok then
        return
    end
    if flag ~= 'GM' then
        handler:FireBroad(cmd, target, ...)
    else
        handler:FireServerBroad(cmd, ...)
    end
end

function BroadMiddleware:Listen(channelName)
    local old = channels[self]
    if old then
        local handler = handlers[old]
        handler.UnregisterAllBroad(self)
        handler.UnregisterAllServerBroad(self)
    end

    local handler = handlers[channelName]

    self.RegisterBroad = handler.RegisterBroad
    self.UnregisterBroad = handler.UnregisterBroad
    self.UnregisterAllBroad = handler.UnregisterAllBroad
    self.RegisterServerBroad = handler.RegisterServerBroad
    self.UnregisterServerBroad = handler.UnregisterServerBroad
    self.UnregisterAllServerBroad = handler.UnregisterAllServerBroad
    self.SendBroad = BroadMiddleware.Send

    channels[channelName] = true
    channels[self] = channelName
end

function BroadMiddleware:Send(cmd, ...)
    if not channels[self] then
        return
    end

    local channelId = GetChannelName(channels[self])
    if not channelId or channelId == 0 then
        return
    end

    local chatType = 'CHANNEL'
    local id = channelId
    local data = EventHandler:Encode(cmd, ...)
    local dataLen = #data
    local queueName = cmd

    if dataLen < MAX_LEN then
        CTL:SendChatMessage('NORMAL', cmd, PACKET_ONE..data, chatType, nil, id, queueName)
    else
        local chunk = strsub(data, 1, MAX_LEN)

        CTL:SendChatMessage('NORMAL', cmd, PACKET_FIRST..chunk, chatType, nil, id, queueName)

        local pos = 1 + MAX_LEN
        while pos + MAX_LEN <= dataLen do
            chunk = strsub(data, pos, pos + MAX_LEN - 1)

            CTL:SendChatMessage('NORMAL', cmd, PACKET_NEXT..chunk, chatType, nil, id, queueName)
            pos = pos + MAX_LEN
        end

        chunk = strsub(data, pos)
        CTL:SendChatMessage('NORMAL', cmd, PACKET_LAST..chunk, chatType, nil, id, queueName)
    end
end
