
local AceComm = LibStub('AceComm-3.0')
local AceSerializer = LibStub('AceSerializer-3.0')
local CallbackHandler = LibStub('CallbackHandler-1.0')

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

local unpack = unpack
local CanAccessValue = canaccessvalue
local IsSecretValue = issecretvalue

local VALID_DISTRIBUTIONS = {
    PARTY = true,
    RAID = true,
    INSTANCE_CHAT = true,
    GUILD = true,
    OFFICER = true,
    WHISPER = true,
    CHANNEL = true,
}

local function IsSecretOrInaccessibleValue(value)
    if type(CanAccessValue) == 'function' then
        local ok, canAccess = pcall(CanAccessValue, value)
        if not ok or not canAccess then
            return true
        end
    end

    if type(IsSecretValue) == 'function' then
        local ok, isSecret = pcall(IsSecretValue, value)
        if not ok or isSecret then
            return true
        end
    end

    return false
end

local function IsSafeString(value)
    if value == nil or IsSecretOrInaccessibleValue(value) then
        return false
    end
    return type(value) == 'string' and value ~= ''
end

local function IsValidPrefix(prefix)
    return IsSafeString(prefix)
        and #prefix <= 16
        and not prefix:find('%z')
end

local function IsValidDistribution(distribution)
    return IsSafeString(distribution) and VALID_DISTRIBUTIONS[distribution] == true
end

local function Pack(...)
    return { n = select('#', ...), ... }
end

SocketMiddleware.CommHandler = SocketMiddleware.CommHandler or {}
SocketMiddleware.prefixes = SocketMiddleware.prefixes or {}
SocketMiddleware.handlers = SocketMiddleware.handlers or setmetatable({}, {
    __index = function(o, k)
        o[k] = {}
        o[k].Fire = CallbackHandler:New(o[k]).Fire
        return o[k]
    end
})

local handlers = SocketMiddleware.handlers
local prefixes = SocketMiddleware.prefixes

local CommHandler = SocketMiddleware.CommHandler
AceComm:Embed(CommHandler)

function CommHandler:OnCommRecv(prefix, data, distribution, sender)
    if not IsValidPrefix(prefix) or not IsSafeString(data) then
        return
    end
    if not IsValidDistribution(distribution) then
        return
    end
    if sender ~= nil and not IsSafeString(sender) then
        return
    end

    local result = Pack(pcall(AceSerializer.Deserialize, AceSerializer, data))
    if not result[1] then
        return
    end

    self:DealPacket(prefix, distribution, sender, unpack(result, 2, result.n))
end

function CommHandler:DealPacket(prefix, distribution, sender, ok, cmd, ...)
    if not ok then
        return
    end
    if not IsValidPrefix(prefix) or not IsValidDistribution(distribution) then
        return
    end
    if sender ~= nil and not IsSafeString(sender) then
        return
    end
    if not IsSafeString(cmd) or #cmd > 64 then
        return
    end

    local handler = rawget(handlers, prefix)
    if not handler or type(handler.Fire) ~= 'function' then
        return
    end

    handler:Fire(cmd, distribution, sender, ...)
end

function SocketMiddleware:Listen(prefix, errorlvl)
    if not IsValidPrefix(prefix) then
        error('Invalid socket prefix', errorlvl or 2)
    end
    if prefixes[self] then
        error('Can`t listen more prefix', errorlvl or 2)
    end

    local ok, reason = pcall(CommHandler.RegisterComm, CommHandler, prefix, 'OnCommRecv')
    if not ok then
        error(tostring(reason or 'RegisterComm failed'), errorlvl or 2)
    end

    local handler = handlers[prefix]
    self.RegisterCallback = handler.RegisterCallback
    self.UnregisterCallback = handler.UnregisterCallback
    self.UnregisterAllCallbacks = handler.UnregisterAllCallbacks

    prefixes[self] = prefix
end

function SocketMiddleware:Send(distribution, target, ...)
    local prefix = prefixes[self]
    if not IsValidPrefix(prefix) then
        return false, 'NO_PREFIX'
    end
    if not IsValidDistribution(distribution) then
        return false, 'INVALID_DISTRIBUTION'
    end
    if distribution == 'WHISPER' and not IsSafeString(target) then
        return false, 'INVALID_TARGET'
    end
    if distribution == 'CHANNEL' and type(target) ~= 'number' then
        return false, 'INVALID_CHANNEL'
    end

    local ok, payload = pcall(AceSerializer.Serialize, AceSerializer, ...)
    if not ok or type(payload) ~= 'string' or payload == '' then
        return false, 'SERIALIZE_FAILED'
    end

    local sentOK, result = pcall(CommHandler.SendCommMessage, CommHandler, prefix, payload, distribution, target)
    if not sentOK then
        return false, result
    end

    return true, result
end

local mixins = {
    'Send',
    'Listen',
}

function SocketMiddleware:Embed(target)
    for i, v in ipairs(mixins) do
        target[v] = self[v]
    end
end
