local c=29 local z=_G if z.ChatThrottleLib then if z.ChatThrottleLib.version>=c then return elseif not z.ChatThrottleLib.securelyHooked then print("\067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\058 \087\097\114\110\105\110\103\058 \084\104\101\114\101\'\115 \097\110 \065\078\067\073\069\078\084 \067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\046\108\117\097 \040\112\114\101-\119\111\119 \050\046\048\044 \060\118\049\054\041 \105\110 \097\110 \097\100\100\111\110 \115\111\109\101\119\104\101\114\101\046 \071\101\116 \116\104\101 \097\100\100\111\110 \117\112\100\097\116\101\100 \111\114 \099\111\112\121 \105\110 \097 \110\101\119\101\114 \067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\046\108\117\097 \040\062\061\118\049\054\041 \105\110 \105\116\033")z.SendChatMessage=z.ChatThrottleLib.ORIG_SendChatMessage if z.ChatThrottleLib.ORIG_SendAddonMessage then z.SendAddonMessage=z.ChatThrottleLib.ORIG_SendAddonMessage end end z.ChatThrottleLib.ORIG_SendChatMessage=nil z.ChatThrottleLib.ORIG_SendAddonMessage=nil end if not z.ChatThrottleLib then z.ChatThrottleLib={}end ChatThrottleLib=z.ChatThrottleLib local Y=z.ChatThrottleLib Y.version=c Y.MAX_CPS=800 Y.MSG_OVERHEAD=40 Y.BURST=4000 Y.MIN_FPS=20 local k=setmetatable local O=table.remove local u=tostring local N=GetTime local P=math.min local I=math.max local j=next local n=string.len local M=GetFramerate local S,X,F,m=unpack,type,pairs,table.wipe local y={}local x={__index=y}function y.New(c)local z={}k(z,x)return z end function y.Add(z,c)if z.pos then c.prev=z.pos.prev c.prev.next=c c.next=z.pos c.next.prev=c else c.next=c c.prev=c z.pos=c end end function y.Remove(z,c)c.next.prev=c.prev c.prev.next=c.next if z.pos==c then z.pos=c.next if z.pos==c then z.pos=nil end end end local function w(c,z)if not c.pos then c.pos=z.pos z.pos=nil elseif z.pos then c.pos.prev.next,z.pos.prev.next=z.pos,c.pos c.pos.prev,z.pos.prev=z.pos.prev,c.pos.prev z.pos=nil end end Y.PipeBin=nil local L=k({},{__mode="\107"})local function e(c)L[c]=true end local function q()local c=j(L)if c then m(c)L[c]=nil return c end return{}end Y.MsgBin=nil local a=k({},{__mode="\107"})local function v(c)c[1]=nil a[c]=true end local function p()local c=j(a)if c then a[c]=nil return c end return{}end function Y.Init(c)if not c.Prio then c.Prio={}c.Prio.ALERT={ByName={},Ring=y:New();avail=0}c.Prio.NORMAL={ByName={};Ring=y:New();avail=0}c.Prio.BULK={ByName={};Ring=y:New();avail=0}end if not c.BlockedQueuesDelay then for c,z in F(c.Prio)do z.Blocked=y:New()end end for c,z in F(c.Prio)do z.nTotalSent=z.nTotalSent or 0 end if not c.avail then c.avail=0 end if not c.nTotalSent then c.nTotalSent=0 end if not c.Frame then c.Frame=CreateFrame("\070\114\097\109\101")c.Frame:Hide()end c.Frame:SetScript("\079\110\085\112\100\097\116\101",c.OnUpdate)c.Frame:SetScript("\079\110\069\118\101\110\116",c.OnEvent)c.Frame:RegisterEvent("\080\076\065\089\069\082\095\069\078\084\069\082\073\078\071\095\087\079\082\076\068")c.OnUpdateDelay=0 c.BlockedQueuesDelay=0 c.LastAvailUpdate=N()c.HardThrottlingBeginTime=N()if not c.securelyHooked then c.securelyHooked=true hooksecurefunc("\083\101\110\100\067\104\097\116\077\101\115\115\097\103\101",function(...)return Y.Hook_SendChatMessage(...)end)hooksecurefunc(z.C_ChatInfo,"\083\101\110\100\065\100\100\111\110\077\101\115\115\097\103\101",function(...)return Y.Hook_SendAddonMessage(...)end)end if not c.securelyHookedLogged then c.securelyHookedLogged=true hooksecurefunc(z.C_ChatInfo,"\083\101\110\100\065\100\100\111\110\077\101\115\115\097\103\101\076\111\103\103\101\100",function(...)return Y.Hook_SendAddonMessageLogged(...)end)end if not c.securelyHookedBNGameData then c.securelyHookedBNGameData=true hooksecurefunc("\066\078\083\101\110\100\071\097\109\101\068\097\116\097",function(...)return Y.Hook_BNSendGameData(...)end)end c.nBypass=0 end local Q=false function Y.Hook_SendChatMessage(c,z,k,O,...)if Q then return end local N=Y local P=(n(u(c or""))+n(u(O or"")))+N.MSG_OVERHEAD N.avail=N.avail-P N.nBypass=N.nBypass+P end function Y.Hook_SendAddonMessage(c,z,k,O,...)if Q then return end local N=Y local P=(u(z or"")):len()+(u(c or"")):len()P=(P+(u(O or"")):len())+N.MSG_OVERHEAD N.avail=N.avail-P N.nBypass=N.nBypass+P end function Y.Hook_SendAddonMessageLogged(c,z,k,O,...)Y.Hook_SendAddonMessage(c,z,k,O,...)end function Y.Hook_BNSendGameData(c,z,k)Y.Hook_SendAddonMessage(z,k,"\087\072\073\083\080\069\082",c)end function Y.UpdateAvail(c)local z=N()local Y=c.MAX_CPS local k=Y*(z-c.LastAvailUpdate)local O=c.avail if z-c.HardThrottlingBeginTime<5 then O=P(O+k*.1,Y*.5)c.bChoking=true elseif M()<c.MIN_FPS then O=P(Y,O+k*.5)c.bChoking=true else O=P(c.BURST,O+k)c.bChoking=false end O=I(O,0-Y*2)c.avail=O c.LastAvailUpdate=z return O end local f=Enum.SendAddonMessageResult or{Success=0;AddonMessageThrottle=3;NotInGroup=5,ChannelThrottle=8,GeneralError=9}local function t(c,...)local z if not c then z=f.GeneralError else z=select(-1,true,...)if z==true then z=f.Success elseif z==false then z=f.GeneralError end end return z end local function K(c)return c==f.AddonMessageThrottle end local function C(...)return(geterrorhandler())(...)end local function W(c,...)Q=true local z=t(xpcall(c,C,...))Q=false return z end function Y.Despool(z,c)local Y=c.Ring while Y.pos and c.avail>Y.pos[1].nSize do local z=Y.pos local k=z[1]local u=W(k.f,S(k,1,k.n))if K(u)then c.Ring:Remove(z)c.Blocked:Add(z)else O(z,1)v(k)if not z[1]then c.Ring:Remove(z)c.ByName[z.name]=nil e(z)else Y.pos=Y.pos.next end local N=u==f.Success if N then c.avail=c.avail-k.nSize c.nTotalSent=c.nTotalSent+k.nSize end if k.callbackFn then securecallfunction(k.callbackFn,k.callbackArg,N,u)end end end end function Y.OnEvent(c,z)local k=Y if z=="\080\076\065\089\069\082\095\069\078\084\069\082\073\078\071\095\087\079\082\076\068"then k.HardThrottlingBeginTime=N()k.avail=0 end end function Y.OnUpdate(c,z)local k=Y k.OnUpdateDelay=k.OnUpdateDelay+z k.BlockedQueuesDelay=k.BlockedQueuesDelay+z if k.OnUpdateDelay<.08 then return end k.OnUpdateDelay=0 k:UpdateAvail()if k.avail<0 then return end if k.BlockedQueuesDelay>=.35 then for c,z in F(k.Prio)do w(z.Ring,z.Blocked)end k.BlockedQueuesDelay=0 end local O=0 local u=0 for c,z in F(k.Prio)do if z.Ring.pos then O=O+1 elseif z.Blocked.pos then u=u+1 end if not z.Ring.pos then k.avail=k.avail+z.avail z.avail=0 end end k.avail=P(k.avail,k.BURST)if O==0 then if u==0 then k.bQueueing=false k.Frame:Hide()end return end local N=k.avail/O k.avail=0 for c,z in F(k.Prio)do if z.Ring.pos then z.avail=z.avail+N k:Despool(z)end end end function Y.Enqueue(k,c,z,Y)local O=k.Prio[c]local u=O.ByName[z]if not u then k.Frame:Show()u=q()u.name=z O.ByName[z]=u O.Ring:Add(u)end u[#u+1]=Y k.bQueueing=true end function Y.SendChatMessage(n,c,Y,k,O,u,N,P,I,j)if not n or not c or not Y or not k or not n.Prio[c]then error("\085\115\097\103\101\058 \067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\058\083\101\110\100\067\104\097\116\077\101\115\115\097\103\101\040\"\123\066\085\076\075\124\124\078\079\082\077\065\076\124\124\065\076\069\082\084\125\"\044 \"\112\114\101\102\105\120\"\044 \"\116\101\120\116\"\091\044 \"\099\104\097\116\116\121\112\101\"\091\044 \"\108\097\110\103\117\097\103\101\"\091\044 \"\100\101\115\116\105\110\097\116\105\111\110\"\093\093\093",2)end if I and X(I)~="\102\117\110\099\116\105\111\110"then error("\067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\058\067\104\097\116\077\101\115\115\097\103\101\040\041\058 \099\097\108\108\098\097\099\107\070\110\058 \101\120\112\101\099\116\101\100 \102\117\110\099\116\105\111\110\044 \103\111\116 "..X(I),2)end local M=k:len()if M>255 then error("\067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\058\083\101\110\100\067\104\097\116\077\101\115\115\097\103\101\040\041\058 \109\101\115\115\097\103\101 \108\101\110\103\116\104 \099\097\110\110\111\116 \101\120\099\101\101\100 \050\053\053 \098\121\116\101\115",2)end M=M+n.MSG_OVERHEAD if not n.bQueueing and M<n:UpdateAvail()then local Y=W(z.SendChatMessage,k,O,u,N)if not K(Y)then local z=Y==f.Success if z then n.avail=n.avail-M n.Prio[c].nTotalSent=n.Prio[c].nTotalSent+M end if I then securecallfunction(I,j,z,Y)end return end end local S=p()S.f=z.SendChatMessage S[1]=k S[2]=O or"\083\065\089"S[3]=u S[4]=N S.n=4 S.nSize=M S.callbackFn=I S.callbackArg=j n:Enqueue(c,P or Y,S)end local function E(c,z,Y,k,O,u,N,P,I,j)local n=#O+c.MSG_OVERHEAD if not c.bQueueing and n<c:UpdateAvail()then local P=W(z,k,O,u,N)if not K(P)then local z=P==f.Success if z then c.avail=c.avail-n c.Prio[Y].nTotalSent=c.Prio[Y].nTotalSent+n end if I then securecallfunction(I,j,z,P)end return end end local M=p()M.f=z M[1]=k M[2]=O M[3]=u M[4]=N M.n=N~=nil and 4 or 3 M.nSize=n M.callbackFn=I M.callbackArg=j c:Enqueue(Y,P or k,M)end function Y.SendAddonMessage(j,c,Y,k,O,u,N,P,I)if not j or not c or not Y or not k or not O or not j.Prio[c]then error("\085\115\097\103\101\058 \067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\058\083\101\110\100\065\100\100\111\110\077\101\115\115\097\103\101\040\"\123\066\085\076\075\124\124\078\079\082\077\065\076\124\124\065\076\069\082\084\125\"\044 \"\112\114\101\102\105\120\"\044 \"\116\101\120\116\"\044 \"\099\104\097\116\116\121\112\101\"\091\044 \"\116\097\114\103\101\116\"\093\041",2)elseif P and X(P)~="\102\117\110\099\116\105\111\110"then error("\067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\058\083\101\110\100\065\100\100\111\110\077\101\115\115\097\103\101\040\041\058 \099\097\108\108\098\097\099\107\070\110\058 \101\120\112\101\099\116\101\100 \102\117\110\099\116\105\111\110\044 \103\111\116 "..X(P),2)elseif#k>255 then error("\067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\058\083\101\110\100\065\100\100\111\110\077\101\115\115\097\103\101\040\041\058 \109\101\115\115\097\103\101 \108\101\110\103\116\104 \099\097\110\110\111\116 \101\120\099\101\101\100 \050\053\053 \098\121\116\101\115",2)end local n=z.C_ChatInfo.SendAddonMessage E(j,n,c,Y,k,O,u,N,P,I)end function Y.SendAddonMessageLogged(j,c,Y,k,O,u,N,P,I)if not j or not c or not Y or not k or not O or not j.Prio[c]then error("\085\115\097\103\101\058 \067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\058\083\101\110\100\065\100\100\111\110\077\101\115\115\097\103\101\076\111\103\103\101\100\040\"\123\066\085\076\075\124\124\078\079\082\077\065\076\124\124\065\076\069\082\084\125\"\044 \"\112\114\101\102\105\120\"\044 \"\116\101\120\116\"\044 \"\099\104\097\116\116\121\112\101\"\091\044 \"\116\097\114\103\101\116\"\093\041",2)elseif P and X(P)~="\102\117\110\099\116\105\111\110"then error("\067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\058\083\101\110\100\065\100\100\111\110\077\101\115\115\097\103\101\076\111\103\103\101\100\040\041\058 \099\097\108\108\098\097\099\107\070\110\058 \101\120\112\101\099\116\101\100 \102\117\110\099\116\105\111\110\044 \103\111\116 "..X(P),2)elseif#k>255 then error("\067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\058\083\101\110\100\065\100\100\111\110\077\101\115\115\097\103\101\076\111\103\103\101\100\040\041\058 \109\101\115\115\097\103\101 \108\101\110\103\116\104 \099\097\110\110\111\116 \101\120\099\101\101\100 \050\053\053 \098\121\116\101\115",2)end local n=z.C_ChatInfo.SendAddonMessageLogged E(j,n,c,Y,k,O,u,N,P,I)end local function s(c,Y,k,O)return z.BNSendGameData(O,c,Y)end function Y.BNSendGameData(I,c,z,Y,k,O,u,N,P)if not I or not c or not z or not Y or not O or not k or not I.Prio[c]then error("\085\115\097\103\101\058 \067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\058\066\078\083\101\110\100\071\097\109\101\068\097\116\097\040\"\123\066\085\076\075\124\124\078\079\082\077\065\076\124\124\065\076\069\082\084\125\"\044 \"\112\114\101\102\105\120\"\044 \"\116\101\120\116\"\044 \"\099\104\097\116\116\121\112\101\"\044 \103\097\109\101\065\099\099\111\117\110\116\073\068\041",2)elseif N and X(N)~="\102\117\110\099\116\105\111\110"then error("\067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\058\066\078\083\101\110\100\071\097\109\101\068\097\116\097\040\041\058 \099\097\108\108\098\097\099\107\070\110\058 \101\120\112\101\099\116\101\100 \102\117\110\099\116\105\111\110\044 \103\111\116 "..X(N),2)elseif#Y>255 then error("\067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\058\066\078\083\101\110\100\071\097\109\101\068\097\116\097\040\041\058 \109\101\115\115\097\103\101 \108\101\110\103\116\104 \099\097\110\110\111\116 \101\120\099\101\101\100 \050\053\053 \098\121\116\101\115",2)elseif k~="\087\072\073\083\080\069\082"then error("\067\104\097\116\084\104\114\111\116\116\108\101\076\105\098\058\066\078\083\101\110\100\071\097\109\101\068\097\116\097\040\041\058 \099\104\097\116 \116\121\112\101 \109\117\115\116 \098\101 \'\087\072\073\083\080\069\082\'",2)end local j=s E(I,j,c,z,Y,k,O,u,N,P)end Y:Init()
--
-- ChatThrottleLib by Mikk
--
-- Manages AddOn chat output to keep player from getting kicked off.
--
-- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept
-- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage.
--
-- Priorities get an equal share of available bandwidth when fully loaded.
-- Communication channels are separated on extension+chattype+destination and
-- get round-robinned. (Destination only matters for whispers and channels,
-- obviously)
--
-- Will install hooks for SendChatMessage and SendAddonMessage to measure
-- bandwidth bypassing the library and use less bandwidth itself.
--
--
-- Fully embeddable library. Just copy this file into your addon directory,
-- add it to the .toc, and it's done.
--
-- Can run as a standalone addon also, but, really, just embed it! :-)
--
-- LICENSE: ChatThrottleLib is released into the Public Domain
--

local CTL_VERSION = 29

local _G = _G

if _G.ChatThrottleLib then
	if _G.ChatThrottleLib.version >= CTL_VERSION then
		-- There's already a newer (or same) version loaded. Buh-bye.
		return
	elseif not _G.ChatThrottleLib.securelyHooked then
		print("ChatThrottleLib: Warning: There's an ANCIENT ChatThrottleLib.lua (pre-wow 2.0, <v16) in an addon somewhere. Get the addon updated or copy in a newer ChatThrottleLib.lua (>=v16) in it!")
		-- ATTEMPT to unhook; this'll behave badly if someone else has hooked...
		-- ... and if someone has securehooked, they can kiss that goodbye too... >.<
		_G.SendChatMessage = _G.ChatThrottleLib.ORIG_SendChatMessage
		if _G.ChatThrottleLib.ORIG_SendAddonMessage then
			_G.SendAddonMessage = _G.ChatThrottleLib.ORIG_SendAddonMessage
		end
	end
	_G.ChatThrottleLib.ORIG_SendChatMessage = nil
	_G.ChatThrottleLib.ORIG_SendAddonMessage = nil
end

if not _G.ChatThrottleLib then
	_G.ChatThrottleLib = {}
end

ChatThrottleLib = _G.ChatThrottleLib  -- in case some addon does "local ChatThrottleLib" above us and we're copypasted (AceComm-2, sigh)
local ChatThrottleLib = _G.ChatThrottleLib

ChatThrottleLib.version = CTL_VERSION



------------------ TWEAKABLES -----------------

ChatThrottleLib.MAX_CPS = 800			  -- 2000 seems to be safe if NOTHING ELSE is happening. let's call it 800.
ChatThrottleLib.MSG_OVERHEAD = 40		-- Guesstimate overhead for sending a message; source+dest+chattype+protocolstuff

ChatThrottleLib.BURST = 4000				-- WoW's server buffer seems to be about 32KB. 8KB should be safe, but seen disconnects on _some_ servers. Using 4KB now.

ChatThrottleLib.MIN_FPS = 20				-- Reduce output CPS to half (and don't burst) if FPS drops below this value


local setmetatable = setmetatable
local table_remove = table.remove
local tostring = tostring
local GetTime = GetTime
local math_min = math.min
local math_max = math.max
local next = next
local strlen = string.len
local GetFramerate = GetFramerate
local unpack,type,pairs,wipe = unpack,type,pairs,table.wipe


-----------------------------------------------------------------------
-- Double-linked ring implementation

local Ring = {}
local RingMeta = { __index = Ring }

function Ring:New()
	local ret = {}
	setmetatable(ret, RingMeta)
	return ret
end

function Ring:Add(obj)	-- Append at the "far end" of the ring (aka just before the current position)
	if self.pos then
		obj.prev = self.pos.prev
		obj.prev.next = obj
		obj.next = self.pos
		obj.next.prev = obj
	else
		obj.next = obj
		obj.prev = obj
		self.pos = obj
	end
end

function Ring:Remove(obj)
	obj.next.prev = obj.prev
	obj.prev.next = obj.next
	if self.pos == obj then
		self.pos = obj.next
		if self.pos == obj then
			self.pos = nil
		end
	end
end

-- Note that this is local because there's no upgrade logic for existing ring
-- metatables, and this isn't present on rings created in versions older than
-- v25.
local function Ring_Link(self, other)  -- Move and append all contents of another ring to this ring
	if not self.pos then
		-- This ring is empty, so just transfer ownership.
		self.pos = other.pos
		other.pos = nil
	elseif other.pos then
		-- Our tail should point to their head, and their tail to our head.
		self.pos.prev.next, other.pos.prev.next = other.pos, self.pos
		-- Our head should point to their tail, and their head to our tail.
		self.pos.prev, other.pos.prev = other.pos.prev, self.pos.prev
		other.pos = nil
	end
end



-----------------------------------------------------------------------
-- Recycling bin for pipes
-- A pipe is a plain integer-indexed queue of messages
-- Pipes normally live in Rings of pipes  (3 rings total, one per priority)

ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different
local PipeBin = setmetatable({}, {__mode="k"})

local function DelPipe(pipe)
	PipeBin[pipe] = true
end

local function NewPipe()
	local pipe = next(PipeBin)
	if pipe then
		wipe(pipe)
		PipeBin[pipe] = nil
		return pipe
	end
	return {}
end




-----------------------------------------------------------------------
-- Recycling bin for messages

ChatThrottleLib.MsgBin = nil -- pre-v19, drastically different
local MsgBin = setmetatable({}, {__mode="k"})

local function DelMsg(msg)
	msg[1] = nil
	-- there's more parameters, but they're very repetetive so the string pool doesn't suffer really, and it's faster to just not delete them.
	MsgBin[msg] = true
end

local function NewMsg()
	local msg = next(MsgBin)
	if msg then
		MsgBin[msg] = nil
		return msg
	end
	return {}
end


-----------------------------------------------------------------------
-- ChatThrottleLib:Init
-- Initialize queues, set up frame for OnUpdate, etc


function ChatThrottleLib:Init()

	-- Set up queues
	if not self.Prio then
		self.Prio = {}
		self.Prio["ALERT"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
		self.Prio["NORMAL"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
		self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
	end

	if not self.BlockedQueuesDelay then
		-- v25: Add blocked queues to rings to handle new client throttles.
		for _, Prio in pairs(self.Prio) do
			Prio.Blocked = Ring:New()
		end
	end

	-- v4: total send counters per priority
	for _, Prio in pairs(self.Prio) do
		Prio.nTotalSent = Prio.nTotalSent or 0
	end

	if not self.avail then
		self.avail = 0 -- v5
	end
	if not self.nTotalSent then
		self.nTotalSent = 0 -- v5
	end


	-- Set up a frame to get OnUpdate events
	if not self.Frame then
		self.Frame = CreateFrame("Frame")
		self.Frame:Hide()
	end
	self.Frame:SetScript("OnUpdate", self.OnUpdate)
	self.Frame:SetScript("OnEvent", self.OnEvent)	-- v11: Monitor P_E_W so we can throttle hard for a few seconds
	self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
	self.OnUpdateDelay = 0
	self.BlockedQueuesDelay = 0
	self.LastAvailUpdate = GetTime()
	self.HardThrottlingBeginTime = GetTime()	-- v11: Throttle hard for a few seconds after startup

	-- Hook SendChatMessage and SendAddonMessage so we can measure unpiped traffic and avoid overloads (v7)
	if not self.securelyHooked then
		-- Use secure hooks as of v16. Old regular hook support yanked out in v21.
		self.securelyHooked = true
		--SendChatMessage
		hooksecurefunc("SendChatMessage", function(...)
			return ChatThrottleLib.Hook_SendChatMessage(...)
		end)
		--SendAddonMessage
		hooksecurefunc(_G.C_ChatInfo, "SendAddonMessage", function(...)
			return ChatThrottleLib.Hook_SendAddonMessage(...)
		end)
	end

	-- v26: Hook SendAddonMessageLogged for traffic logging
	if not self.securelyHookedLogged then
		self.securelyHookedLogged = true
		hooksecurefunc(_G.C_ChatInfo, "SendAddonMessageLogged", function(...)
			return ChatThrottleLib.Hook_SendAddonMessageLogged(...)
		end)
	end

	-- v29: Hook BNSendGameData for traffic logging
	if not self.securelyHookedBNGameData then
		self.securelyHookedBNGameData = true
		hooksecurefunc("BNSendGameData", function(...)
			return ChatThrottleLib.Hook_BNSendGameData(...)
		end)
	end

	self.nBypass = 0
end


-----------------------------------------------------------------------
-- ChatThrottleLib.Hook_SendChatMessage / .Hook_SendAddonMessage

local bMyTraffic = false

function ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destination, ...)
	if bMyTraffic then
		return
	end
	local self = ChatThrottleLib
	local size = strlen(tostring(text or "")) + strlen(tostring(destination or "")) + self.MSG_OVERHEAD
	self.avail = self.avail - size
	self.nBypass = self.nBypass + size	-- just a statistic
end
function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...)
	if bMyTraffic then
		return
	end
	local self = ChatThrottleLib
	local size = tostring(text or ""):len() + tostring(prefix or ""):len();
	size = size + tostring(destination or ""):len() + self.MSG_OVERHEAD
	self.avail = self.avail - size
	self.nBypass = self.nBypass + size	-- just a statistic
end
function ChatThrottleLib.Hook_SendAddonMessageLogged(prefix, text, chattype, destination, ...)
	ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...)
end
function ChatThrottleLib.Hook_BNSendGameData(destination, prefix, text)
	ChatThrottleLib.Hook_SendAddonMessage(prefix, text, "WHISPER", destination)
end



-----------------------------------------------------------------------
-- ChatThrottleLib:UpdateAvail
-- Update self.avail with how much bandwidth is currently available

function ChatThrottleLib:UpdateAvail()
	local now = GetTime()
	local MAX_CPS = self.MAX_CPS;
	local newavail = MAX_CPS * (now - self.LastAvailUpdate)
	local avail = self.avail

	if now - self.HardThrottlingBeginTime < 5 then
		-- First 5 seconds after startup/zoning: VERY hard clamping to avoid irritating the server rate limiter, it seems very cranky then
		avail = math_min(avail + (newavail*0.1), MAX_CPS*0.5)
		self.bChoking = true
	elseif GetFramerate() < self.MIN_FPS then		-- GetFrameRate call takes ~0.002 secs
		avail = math_min(MAX_CPS, avail + newavail*0.5)
		self.bChoking = true		-- just a statistic
	else
		avail = math_min(self.BURST, avail + newavail)
		self.bChoking = false
	end

	avail = math_max(avail, 0-(MAX_CPS*2))	-- Can go negative when someone is eating bandwidth past the lib. but we refuse to stay silent for more than 2 seconds; if they can do it, we can.

	self.avail = avail
	self.LastAvailUpdate = now

	return avail
end


-----------------------------------------------------------------------
-- Despooling logic
-- Reminder:
-- - We have 3 Priorities, each containing a "Ring" construct ...
-- - ... made up of N "Pipe"s (1 for each destination/pipename)
-- - and each pipe contains messages

local SendAddonMessageResult = Enum.SendAddonMessageResult or {
	Success = 0,
	AddonMessageThrottle = 3,
	NotInGroup = 5,
	ChannelThrottle = 8,
	GeneralError = 9,
}

local function MapToSendResult(ok, ...)
	local result

	if not ok then
		-- The send function itself errored; don't look at anything else.
		result = SendAddonMessageResult.GeneralError
	else
		-- Grab the last return value from the send function and remap
		-- it from a boolean to an enum code. If there are no results,
		-- assume success (true).

		result = select(-1, true, ...)

		if result == true then
			result = SendAddonMessageResult.Success
		elseif result == false then
			result = SendAddonMessageResult.GeneralError
		end
	end

	return result
end

local function IsThrottledSendResult(result)
	return result == SendAddonMessageResult.AddonMessageThrottle
end

-- A copy of this function exists in FrameXML, but for clarity it's here too.
local function CallErrorHandler(...)
	return geterrorhandler()(...)
end

local function PerformSend(sendFunction, ...)
	bMyTraffic = true
	local sendResult = MapToSendResult(xpcall(sendFunction, CallErrorHandler, ...))
	bMyTraffic = false
	return sendResult
end

function ChatThrottleLib:Despool(Prio)
	local ring = Prio.Ring
	while ring.pos and Prio.avail > ring.pos[1].nSize do
		local pipe = ring.pos
		local msg = pipe[1]
		local sendResult = PerformSend(msg.f, unpack(msg, 1, msg.n))

		if IsThrottledSendResult(sendResult) then
			-- Message was throttled; move the pipe into the blocked ring.
			Prio.Ring:Remove(pipe)
			Prio.Blocked:Add(pipe)
		else
			-- Dequeue message after submission.
			table_remove(pipe, 1)
			DelMsg(msg)

			if not pipe[1] then  -- did we remove last msg in this pipe?
				Prio.Ring:Remove(pipe)
				Prio.ByName[pipe.name] = nil
				DelPipe(pipe)
			else
				ring.pos = ring.pos.next
			end

			-- Update bandwidth counters on successful sends.
			local didSend = (sendResult == SendAddonMessageResult.Success)
			if didSend then
				Prio.avail = Prio.avail - msg.nSize
				Prio.nTotalSent = Prio.nTotalSent + msg.nSize
			end

			-- Notify caller of message submission.
			if msg.callbackFn then
				securecallfunction(msg.callbackFn, msg.callbackArg, didSend, sendResult)
			end
		end
	end
end


function ChatThrottleLib.OnEvent(this,event)
	-- v11: We know that the rate limiter is touchy after login. Assume that it's touchy after zoning, too.
	local self = ChatThrottleLib
	if event == "PLAYER_ENTERING_WORLD" then
		self.HardThrottlingBeginTime = GetTime()	-- Throttle hard for a few seconds after zoning
		self.avail = 0
	end
end


function ChatThrottleLib.OnUpdate(this,delay)
	local self = ChatThrottleLib

	self.OnUpdateDelay = self.OnUpdateDelay + delay
	self.BlockedQueuesDelay = self.BlockedQueuesDelay + delay
	if self.OnUpdateDelay < 0.08 then
		return
	end
	self.OnUpdateDelay = 0

	self:UpdateAvail()

	if self.avail < 0  then
		return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu.
	end

	-- Integrate blocked queues back into their rings periodically.
	if self.BlockedQueuesDelay >= 0.35 then
		for _, Prio in pairs(self.Prio) do
			Ring_Link(Prio.Ring, Prio.Blocked)
		end

		self.BlockedQueuesDelay = 0
	end

	-- See how many of our priorities have queued messages. This is split
	-- into two counters because priorities that consist only of blocked
	-- queues must keep our OnUpdate alive, but shouldn't count toward
	-- bandwidth distribution.
	local nSendablePrios = 0
	local nBlockedPrios = 0

	for prioname, Prio in pairs(self.Prio) do
		if Prio.Ring.pos then
			nSendablePrios = nSendablePrios + 1
		elseif Prio.Blocked.pos then
			nBlockedPrios = nBlockedPrios + 1
		end

		-- Collect unused bandwidth from priorities with nothing to send.
		if not Prio.Ring.pos then
			self.avail = self.avail + Prio.avail
			Prio.avail = 0
		end
	end

	-- Bandwidth reclamation may take us back over the burst cap.
	self.avail = math_min(self.avail, self.BURST)

	-- If we can't currently send on any priorities, stop processing early.
	if nSendablePrios == 0 then
		-- If we're completely out of data to send, disable queue processing.
		if nBlockedPrios == 0 then
			self.bQueueing = false
			self.Frame:Hide()
		end

		return
	end

	-- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues
	local avail = self.avail / nSendablePrios
	self.avail = 0

	for prioname, Prio in pairs(self.Prio) do
		if Prio.Ring.pos then
			Prio.avail = Prio.avail + avail
			self:Despool(Prio)
		end
	end
end




-----------------------------------------------------------------------
-- Spooling logic

function ChatThrottleLib:Enqueue(prioname, pipename, msg)
	local Prio = self.Prio[prioname]
	local pipe = Prio.ByName[pipename]
	if not pipe then
		self.Frame:Show()
		pipe = NewPipe()
		pipe.name = pipename
		Prio.ByName[pipename] = pipe
		Prio.Ring:Add(pipe)
	end

	pipe[#pipe + 1] = msg

	self.bQueueing = true
end

function ChatThrottleLib:SendChatMessage(prio, prefix,   text, chattype, language, destination, queueName, callbackFn, callbackArg)
	if not self or not prio or not prefix or not text or not self.Prio[prio] then
		error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix", "text"[, "chattype"[, "language"[, "destination"]]]', 2)
	end
	if callbackFn and type(callbackFn)~="function" then
		error('ChatThrottleLib:ChatMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
	end

	local nSize = text:len()

	if nSize>255 then
		error("ChatThrottleLib:SendChatMessage(): message length cannot exceed 255 bytes", 2)
	end

	nSize = nSize + self.MSG_OVERHEAD

	-- Check if there's room in the global available bandwidth gauge to send directly
	if not self.bQueueing and nSize < self:UpdateAvail() then
		local sendResult = PerformSend(_G.SendChatMessage, text, chattype, language, destination)

		if not IsThrottledSendResult(sendResult) then
			local didSend = (sendResult == SendAddonMessageResult.Success)

			if didSend then
				self.avail = self.avail - nSize
				self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
			end

			if callbackFn then
				securecallfunction(callbackFn, callbackArg, didSend, sendResult)
			end

			return
		end
	end

	-- Message needs to be queued
	local msg = NewMsg()
	msg.f = _G.SendChatMessage
	msg[1] = text
	msg[2] = chattype or "SAY"
	msg[3] = language
	msg[4] = destination
	msg.n = 4
	msg.nSize = nSize
	msg.callbackFn = callbackFn
	msg.callbackArg = callbackArg

	self:Enqueue(prio, queueName or prefix, msg)
end


local function SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
	local nSize = #text + self.MSG_OVERHEAD

	-- Check if there's room in the global available bandwidth gauge to send directly
	if not self.bQueueing and nSize < self:UpdateAvail() then
		local sendResult = PerformSend(sendFunction, prefix, text, chattype, target)

		if not IsThrottledSendResult(sendResult) then
			local didSend = (sendResult == SendAddonMessageResult.Success)

			if didSend then
				self.avail = self.avail - nSize
				self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
			end

			if callbackFn then
				securecallfunction(callbackFn, callbackArg, didSend, sendResult)
			end

			return
		end
	end

	-- Message needs to be queued
	local msg = NewMsg()
	msg.f = sendFunction
	msg[1] = prefix
	msg[2] = text
	msg[3] = chattype
	msg[4] = target
	msg.n = (target~=nil) and 4 or 3;
	msg.nSize = nSize
	msg.callbackFn = callbackFn
	msg.callbackArg = callbackArg

	self:Enqueue(prio, queueName or prefix, msg)
end


function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
	if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then
		error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2)
	elseif callbackFn and type(callbackFn)~="function" then
		error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
	elseif #text>255 then
		error("ChatThrottleLib:SendAddonMessage(): message length cannot exceed 255 bytes", 2)
	end

	local sendFunction = _G.C_ChatInfo.SendAddonMessage
	SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
end


function ChatThrottleLib:SendAddonMessageLogged(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
	if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then
		error('Usage: ChatThrottleLib:SendAddonMessageLogged("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2)
	elseif callbackFn and type(callbackFn)~="function" then
		error('ChatThrottleLib:SendAddonMessageLogged(): callbackFn: expected function, got '..type(callbackFn), 2)
	elseif #text>255 then
		error("ChatThrottleLib:SendAddonMessageLogged(): message length cannot exceed 255 bytes", 2)
	end

	local sendFunction = _G.C_ChatInfo.SendAddonMessageLogged
	SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
end

local function BNSendGameDataReordered(prefix, text, _, gameAccountID)
	return _G.BNSendGameData(gameAccountID, prefix, text)
end

function ChatThrottleLib:BNSendGameData(prio, prefix, text, chattype, gameAccountID, queueName, callbackFn, callbackArg)
	-- Note that this API is intentionally limited to 255 bytes of data
	-- for reasons of traffic fairness, which is less than the 4078 bytes
	-- BNSendGameData natively supports. Additionally, a chat type is required
	-- but must always be set to 'WHISPER' to match what is exposed by the
	-- receipt event.
	--
	-- If splitting messages, callers must also be aware that message
	-- delivery over BNSendGameData is unordered.

	if not self or not prio or not prefix or not text or not gameAccountID or not chattype or not self.Prio[prio] then
		error('Usage: ChatThrottleLib:BNSendGameData("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype", gameAccountID)', 2)
	elseif callbackFn and type(callbackFn)~="function" then
		error('ChatThrottleLib:BNSendGameData(): callbackFn: expected function, got '..type(callbackFn), 2)
	elseif #text>255 then
		error("ChatThrottleLib:BNSendGameData(): message length cannot exceed 255 bytes", 2)
	elseif chattype ~= "WHISPER" then
		error("ChatThrottleLib:BNSendGameData(): chat type must be 'WHISPER'", 2)
	end

	local sendFunction = BNSendGameDataReordered
	SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, gameAccountID, queueName, callbackFn, callbackArg)
end


-----------------------------------------------------------------------
-- Get the ball rolling!

ChatThrottleLib:Init()

--[[ WoWBench debugging snippet
if(WOWB_VER) then
	local function SayTimer()
		print("SAY: "..GetTime().." "..arg1)
	end
	ChatThrottleLib.Frame:SetScript("OnEvent", SayTimer)
	ChatThrottleLib.Frame:RegisterEvent("CHAT_MSG_SAY")
end
]]


