local c=LibStub("\067\097\108\108\098\097\099\107\072\097\110\100\108\101\114-\049\046\048")local z=assert(ChatThrottleLib,"\065\099\101\067\111\109\109-\051\046\048 \114\101\113\117\105\114\101\115 \067\104\097\116\084\104\114\111\116\116\108\101\076\105\098")local Y,k="\065\099\101\067\111\109\109-\051\046\048",14 local O,u=LibStub:NewLibrary(Y,k)if not O then return end local N,P,I,j=type,next,pairs,tostring local n,M=string.sub,string.find local S=string.match local X,F=table.insert,table.concat local m,y=error,assert local x=Ambiguate O.embeds=O.embeds or{}local w="\001"local L="\002"local e="\003"local q="\004"O.multipart_origprefixes=nil O.multipart_reassemblers=nil O.multipart_spool=O.multipart_spool or{}function O.RegisterComm(Y,c,z)if z==nil then z="\079\110\067\111\109\109\082\101\099\101\105\118\101\100"end if#c>16 then m("\065\099\101\067\111\109\109\058\082\101\103\105\115\116\101\114\067\111\109\109\040\112\114\101\102\105\120\044\109\101\116\104\111\100\041\058 \112\114\101\102\105\120 \108\101\110\103\116\104 \105\115 \108\105\109\105\116\101\100 \116\111 \049\054 \099\104\097\114\097\099\116\101\114\115")end if C_ChatInfo then C_ChatInfo.RegisterAddonMessagePrefix(c)else RegisterAddonMessagePrefix(c)end return O._RegisterComm(Y,c,z)end local a=false function O.SendCommMessage(j,c,Y,k,O,u,P,I)u=u or"\078\079\082\077\065\076"if not(N(c)=="\115\116\114\105\110\103"and(N(Y)=="\115\116\114\105\110\103"and(N(k)=="\115\116\114\105\110\103"and((O==nil or N(O)=="\115\116\114\105\110\103"or N(O)=="\110\117\109\098\101\114")and(u=="\066\085\076\075"or u=="\078\079\082\077\065\076"or u=="\065\076\069\082\084")))))then m("\085\115\097\103\101\058 \083\101\110\100\067\111\109\109\077\101\115\115\097\103\101\040\097\100\100\111\110\044 \"\112\114\101\102\105\120\"\044 \"\116\101\120\116\"\044 \"\100\105\115\116\114\105\098\117\116\105\111\110\"\091\044 \"\116\097\114\103\101\116\"\091\044 \"\112\114\105\111\"\091\044 \099\097\108\108\098\097\099\107\070\110\044 \099\097\108\108\098\097\099\107\097\114\103\093\093\093\041",2)end local M=#Y local X=255 local F=c local y=nil if P then y=function(c,z)return P(I,c,M,z)end end local x if S(Y,"\094\091\001-\t\093")then if M+1>X then x=true else Y="\004"..Y end end if not x and M<=X then z:SendAddonMessage(u,c,Y,k,O,F,y,M)else X=X-1 local N=n(Y,1,X)z:SendAddonMessage(u,c,w..N,k,O,F,y,X)local P=1+X while P+X<=M do N=n(Y,P,(P+X)-1)z:SendAddonMessage(u,c,L..N,k,O,F,y,(P+X)-1)P=P+X end N=n(Y,P)z:SendAddonMessage(u,c,e..N,k,O,F,y,M)end end do local c=setmetatable({},{__mode="\107"})local function z()local z=P(c)if z then c[z]=nil for c=#z,3,-1 do z[c]=nil end return z end return{}end local function k(c,z,k)DEFAULT_CHAT_FRAME:AddMessage(Y..("\058 \087\097\114\110\105\110\103\058 \108\111\115\116 \110\101\116\119\111\114\107 \100\097\116\097 \114\101\103\097\114\100\105\110\103 \'"..(j(c)..("\' \102\114\111\109 \'"..(j(z)..("\' \040\105\110 "..(k.."\041")))))))end function O.OnReceiveMultipartFirst(u,c,z,Y,k)local N=c..("\t"..(Y..("\t"..k)))local P=O.multipart_spool P[N]=z end function O.OnReceiveMultipartNext(P,c,Y,k,u)local I=c..("\t"..(k..("\t"..u)))local j=O.multipart_spool local n=j[I]if not n then return end if N(n)~="\116\097\098\108\101"then local c=z()c[1]=n c[2]=Y j[I]=c else X(n,Y)end end function O.OnReceiveMultipartLast(P,z,Y,k,u)local I=z..("\t"..(k..("\t"..u)))local j=O.multipart_spool local n=j[I]if not n then return end j[I]=nil if N(n)=="\116\097\098\108\101"then X(n,Y)O.callbacks:Fire(z,F(n,""),k,u)c[n]=true else O.callbacks:Fire(z,n..Y,k,u)end end end if not O.callbacks then O.callbacks=c:New(O,"\095\082\101\103\105\115\116\101\114\067\111\109\109","\085\110\114\101\103\105\115\116\101\114\067\111\109\109","\085\110\114\101\103\105\115\116\101\114\065\108\108\067\111\109\109")end O.callbacks.OnUsed=nil O.callbacks.OnUnused=nil local function v(c,z,Y,k,u,N)if z=="\067\072\065\084\095\077\083\071\095\065\068\068\079\078"then N=x(N,"\110\111\110\101")local c,z=S(k,"\094\040\091\001-\t\093\041\040\046\042\041")if c then if c==w then O:OnReceiveMultipartFirst(Y,z,u,N)elseif c==L then O:OnReceiveMultipartNext(Y,z,u,N)elseif c==e then O:OnReceiveMultipartLast(Y,z,u,N)elseif c==q then O.callbacks:Fire(Y,z,u,N)else end else O.callbacks:Fire(Y,k,u,N)end else y(false,"\082\101\099\101\105\118\101\100 "..(j(z).." \101\118\101\110\116\063\033"))end end O.frame=O.frame or CreateFrame("\070\114\097\109\101","\065\099\101\067\111\109\109\051\048\070\114\097\109\101")O.frame:SetScript("\079\110\069\118\101\110\116",v)O.frame:UnregisterAllEvents()O.frame:RegisterEvent("\067\072\065\084\095\077\083\071\095\065\068\068\079\078")local p={"\082\101\103\105\115\116\101\114\067\111\109\109","\085\110\114\101\103\105\115\116\101\114\067\111\109\109";"\085\110\114\101\103\105\115\116\101\114\065\108\108\067\111\109\109","\083\101\110\100\067\111\109\109\077\101\115\115\097\103\101"}function O.Embed(z,c)for Y,k in I(p)do c[k]=z[k]end z.embeds[c]=true return c end function O.OnEmbedDisable(z,c)c:UnregisterAllComm()end for c,z in I(O.embeds)do O:Embed(c)end
--- **AceComm-3.0** allows you to send messages of unlimited length over the addon comm channels.
-- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\
-- **ChatThrottleLib** is of course being used to avoid being disconnected by the server.
--
-- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
-- and can be accessed directly, without having to explicitly call AceComm itself.\\
-- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you
-- make into AceComm.
-- @class file
-- @name AceComm-3.0
-- @release $Id: AceComm-3.0.lua 1333 2024-05-05 16:24:39Z nevcairiel $

--[[ AceComm-3.0

TODO: Time out old data rotting around from dead senders? Not a HUGE deal since the number of possible sender names is somewhat limited.

]]

local CallbackHandler = LibStub("CallbackHandler-1.0")
local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib")

local MAJOR, MINOR = "AceComm-3.0", 14
local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR)

if not AceComm then return end

-- Lua APIs
local type, next, pairs, tostring = type, next, pairs, tostring
local strsub, strfind = string.sub, string.find
local match = string.match
local tinsert, tconcat = table.insert, table.concat
local error, assert = error, assert

-- WoW APIs
local Ambiguate = Ambiguate

AceComm.embeds = AceComm.embeds or {}

-- for my sanity and yours, let's give the message type bytes some names
local MSG_MULTI_FIRST = "\001"
local MSG_MULTI_NEXT  = "\002"
local MSG_MULTI_LAST  = "\003"
local MSG_ESCAPE = "\004"

-- remove old structures (pre WoW 4.0)
AceComm.multipart_origprefixes = nil
AceComm.multipart_reassemblers = nil

-- the multipart message spool: indexed by a combination of sender+distribution+
AceComm.multipart_spool = AceComm.multipart_spool or {}

--- Register for Addon Traffic on a specified prefix
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent), max 16 characters
-- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived"
function AceComm:RegisterComm(prefix, method)
	if method == nil then
		method = "OnCommReceived"
	end

	if #prefix > 16 then -- TODO: 15?
		error("AceComm:RegisterComm(prefix,method): prefix length is limited to 16 characters")
	end
	if C_ChatInfo then
		C_ChatInfo.RegisterAddonMessagePrefix(prefix)
	else
		RegisterAddonMessagePrefix(prefix)
	end

	return AceComm._RegisterComm(self, prefix, method)	-- created by CallbackHandler
end

local warnedPrefix=false

--- Send a message over the Addon Channel
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent)
-- @param text Data to send, nils (\000) not allowed. Any length.
-- @param distribution Addon channel, e.g. "RAID", "GUILD", etc; see SendAddonMessage API
-- @param target Destination for some distributions; see SendAddonMessage API
-- @param prio OPTIONAL: ChatThrottleLib priority, "BULK", "NORMAL" or "ALERT". Defaults to "NORMAL".
-- @param callbackFn OPTIONAL: callback function to be called as each chunk is sent. receives 3 args: the user supplied arg (see next), the number of bytes sent so far, and the number of bytes total to send.
-- @param callbackArg: OPTIONAL: first arg to the callback function. nil will be passed if not specified.
function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callbackFn, callbackArg)
	prio = prio or "NORMAL"	-- pasta's reference implementation had different prio for singlepart and multipart, but that's a very bad idea since that can easily lead to out-of-sequence delivery!
	if not( type(prefix)=="string" and
			type(text)=="string" and
			type(distribution)=="string" and
			(target==nil or type(target)=="string" or type(target)=="number") and
			(prio=="BULK" or prio=="NORMAL" or prio=="ALERT")
		) then
		error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2)
	end

	local textlen = #text
	local maxtextlen = 255  -- Yes, the max is 255 even if the dev post said 256. I tested. Char 256+ get silently truncated. /Mikk, 20110327
	local queueName = prefix

	local ctlCallback = nil
	if callbackFn then
		ctlCallback = function(sent, sendResult)
			return callbackFn(callbackArg, sent, textlen, sendResult)
		end
	end

	local forceMultipart
	if match(text, "^[\001-\009]") then -- 4.1+: see if the first character is a control character
		-- we need to escape the first character with a \004
		if textlen+1 > maxtextlen then	-- would we go over the size limit?
			forceMultipart = true	-- just make it multipart, no escape problems then
		else
			text = "\004" .. text
		end
	end

	if not forceMultipart and textlen <= maxtextlen then
		-- fits all in one message
		CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen)
	else
		maxtextlen = maxtextlen - 1	-- 1 extra byte for part indicator in prefix(4.0)/start of message(4.1)

		-- first part
		local chunk = strsub(text, 1, maxtextlen)
		CTL:SendAddonMessage(prio, prefix, MSG_MULTI_FIRST..chunk, distribution, target, queueName, ctlCallback, maxtextlen)

		-- continuation
		local pos = 1+maxtextlen

		while pos+maxtextlen <= textlen do
			chunk = strsub(text, pos, pos+maxtextlen-1)
			CTL:SendAddonMessage(prio, prefix, MSG_MULTI_NEXT..chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1)
			pos = pos + maxtextlen
		end

		-- final part
		chunk = strsub(text, pos)
		CTL:SendAddonMessage(prio, prefix, MSG_MULTI_LAST..chunk, distribution, target, queueName, ctlCallback, textlen)
	end
end


----------------------------------------
-- Message receiving
----------------------------------------

do
	local compost = setmetatable({}, {__mode = "k"})
	local function new()
		local t = next(compost)
		if t then
			compost[t]=nil
			for i=#t,3,-1 do	-- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten
				t[i]=nil
			end
			return t
		end

		return {}
	end

	local function lostdatawarning(prefix,sender,where)
		DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")")
	end

	function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender)
		local key = prefix.."\t"..distribution.."\t"..sender	-- a unique stream is defined by the prefix + distribution + sender
		local spool = AceComm.multipart_spool

		--[[
		if spool[key] then
			lostdatawarning(prefix,sender,"First")
			-- continue and overwrite
		end
		--]]

		spool[key] = message  -- plain string for now
	end

	function AceComm:OnReceiveMultipartNext(prefix, message, distribution, sender)
		local key = prefix.."\t"..distribution.."\t"..sender	-- a unique stream is defined by the prefix + distribution + sender
		local spool = AceComm.multipart_spool
		local olddata = spool[key]

		if not olddata then
			--lostdatawarning(prefix,sender,"Next")
			return
		end

		if type(olddata)~="table" then
			-- ... but what we have is not a table. So make it one. (Pull a composted one if available)
			local t = new()
			t[1] = olddata    -- add old data as first string
			t[2] = message    -- and new message as second string
			spool[key] = t    -- and put the table in the spool instead of the old string
		else
			tinsert(olddata, message)
		end
	end

	function AceComm:OnReceiveMultipartLast(prefix, message, distribution, sender)
		local key = prefix.."\t"..distribution.."\t"..sender	-- a unique stream is defined by the prefix + distribution + sender
		local spool = AceComm.multipart_spool
		local olddata = spool[key]

		if not olddata then
			--lostdatawarning(prefix,sender,"End")
			return
		end

		spool[key] = nil

		if type(olddata) == "table" then
			-- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat
			tinsert(olddata, message)
			AceComm.callbacks:Fire(prefix, tconcat(olddata, ""), distribution, sender)
			compost[olddata] = true
		else
			-- if we've only received a "first", the spooled data will still only be a string
			AceComm.callbacks:Fire(prefix, olddata..message, distribution, sender)
		end
	end
end






----------------------------------------
-- Embed CallbackHandler
----------------------------------------

if not AceComm.callbacks then
	AceComm.callbacks = CallbackHandler:New(AceComm,
						"_RegisterComm",
						"UnregisterComm",
						"UnregisterAllComm")
end

AceComm.callbacks.OnUsed = nil
AceComm.callbacks.OnUnused = nil

local function OnEvent(self, event, prefix, message, distribution, sender)
	if event == "CHAT_MSG_ADDON" then
		sender = Ambiguate(sender, "none")
		local control, rest = match(message, "^([\001-\009])(.*)")
		if control then
			if control==MSG_MULTI_FIRST then
				AceComm:OnReceiveMultipartFirst(prefix, rest, distribution, sender)
			elseif control==MSG_MULTI_NEXT then
				AceComm:OnReceiveMultipartNext(prefix, rest, distribution, sender)
			elseif control==MSG_MULTI_LAST then
				AceComm:OnReceiveMultipartLast(prefix, rest, distribution, sender)
			elseif control==MSG_ESCAPE then
				AceComm.callbacks:Fire(prefix, rest, distribution, sender)
			else
				-- unknown control character, ignore SILENTLY (dont warn unnecessarily about future extensions!)
			end
		else
			-- single part: fire it off immediately and let CallbackHandler decide if it's registered or not
			AceComm.callbacks:Fire(prefix, message, distribution, sender)
		end
	else
		assert(false, "Received "..tostring(event).." event?!")
	end
end

AceComm.frame = AceComm.frame or CreateFrame("Frame", "AceComm30Frame")
AceComm.frame:SetScript("OnEvent", OnEvent)
AceComm.frame:UnregisterAllEvents()
AceComm.frame:RegisterEvent("CHAT_MSG_ADDON")


----------------------------------------
-- Base library stuff
----------------------------------------

local mixins = {
	"RegisterComm",
	"UnregisterComm",
	"UnregisterAllComm",
	"SendCommMessage",
}

-- Embeds AceComm-3.0 into the target object making the functions from the mixins list available on target:..
-- @param target target object to embed AceComm-3.0 in
function AceComm:Embed(target)
	for k, v in pairs(mixins) do
		target[v] = self[v]
	end
	self.embeds[target] = true
	return target
end

function AceComm:OnEmbedDisable(target)
	target:UnregisterAllComm()
end

-- Update embeds
for target, v in pairs(AceComm.embeds) do
	AceComm:Embed(target)
end
