--[[
Name: AceEvent-2.0
Revision: $Rev: 4599 $
Author(s): ckknight (ckknight@gmail.com)
Inspired By: AceEvent 1.x by Turan (<email here>)
Website: http://www.wowace.com/
Documentation: http://wiki.wowace.com/index.php/AceEvent-2.0
SVN: http://svn.wowace.com/root/trunk/Ace2/AceEvent-2.0
Description: Mixin to allow for event handling and inter-addon communication.
Dependencies: AceLibrary, AceOO-2.0, Compost-2.0 (optional)
]]

local MAJOR_VERSION = "AceEvent-2.0"
local MINOR_VERSION = "$Revision: 4599 $"

if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end
if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end

if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end

local AceOO = AceLibrary:GetInstance("AceOO-2.0")
local Mixin = AceOO.Mixin
local AceEvent = Mixin {
						"RegisterEvent",
						"UnregisterEvent",
						"UnregisterAllEvents",
						"TriggerEvent",
						"ScheduleEvent",
						"ScheduleRepeatingEvent",
						"CancelScheduledEvent",
						"CancelAllScheduledEvents",
						"TriggerDelayedEvent", -- remove on July 23
						"CancelDelayedEvent", -- remove on July 23
						"IsEventRegistered",
						"IsEventScheduled",
					   }

local Compost = AceLibrary:HasInstance("Compost-2.0") and AceLibrary("Compost-2.0")

function AceEvent:RegisterEvent(event, method, once)
	AceEvent:argCheck(event, 2, "string")
	AceEvent:argCheck(method, 3, "string", "function", "nil")
	AceEvent:argCheck(once, 4, "boolean", "nil")
	if not method then
		method = event
	end
	if type(method) == "string" and type(self[method]) ~= "function" then
		AceEvent:error("Cannot register event %q to method %q, it does not exist", event, method)
	end
	
	if not AceEvent.registry[event] then
		AceEvent.registry[event] = Compost and Compost:Acquire() or {}
		AceEvent.frame:RegisterEvent(event)
	end
	
	AceEvent.registry[event][self] = method
	
	if once then
		if not AceEvent.onceRegistry then
			AceEvent.onceRegistry = Compost and Compost:Acquire() or {}
		end
		if not AceEvent.onceRegistry[event] then
			AceEvent.onceRegistry[event] = Compost and Compost:Acquire() or {}
		end
		AceEvent.onceRegistry[event][self] = true
	else
		if AceEvent.onceRegistry and AceEvent.onceRegistry[event] then
			AceEvent.onceRegistry[event][self] = nil
		end
	end
end

local _G = getfenv(0)
function AceEvent:TriggerEvent(event, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
	AceEvent:argCheck(event, 2, "string")
	local _G_event = _G.event
	_G.event = event

	if AceEvent.registry[event] then
		if AceEvent.onceRegistry and AceEvent.onceRegistry[event] then
			for obj in pairs(AceEvent.onceRegistry[event]) do
				local mem, time
				if AceEvent.debugTable then
					if not AceEvent.debugTable[event] then
						AceEvent.debugTable[event] = Compost and Compost:Acquire() or {}
					end
					if not AceEvent.debugTable[event][obj] then
						AceEvent.debugTable[event][obj] = Compost and Compost:AcquireHash(
							'mem', 0,
							'time', 0
						) or {
							mem = 0,
							time = 0
						}
					end
					mem, time = gcinfo(), GetTime()
				end
				local method = AceEvent.registry[event][obj]
				AceEvent.registry[event][obj] = nil
				AceEvent.onceRegistry[event][obj] = nil
				if type(method) == "string" then
					if obj[method] then
						obj[method](obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
					end
				elseif method then -- function
					method(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
				end
				if AceEvent.debugTable then
					mem, time = mem - gcinfo(), time - GetTime()
					AceEvent.debugTable[event][obj].mem = AceEvent.debugTable[event][obj].mem + mem
					AceEvent.debugTable[event][obj].time = AceEvent.debugTable[event][obj].time + time
				end
				obj = nil
			end
		end
		for obj, method in pairs(AceEvent.registry[event]) do
			local mem, time
			if AceEvent.debugTable then
				if not AceEvent.debugTable[event] then
					AceEvent.debugTable[event] = Compost and Compost:Acquire() or {}
				end
				if not AceEvent.debugTable[event][obj] then
					AceEvent.debugTable[event][obj] = Compost and Compost:AcquireHash(
						'mem', 0,
						'time', 0
					) or {
						mem = 0,
						time = 0
					}
				end
				mem, time = gcinfo(), GetTime()
			end
			if type(method) == "string" then
				if obj[method] then
					obj[method](obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
				end
			else -- function
				method(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
			end
			if AceEvent.debugTable then
				mem, time = mem - gcinfo(), time - GetTime()
				AceEvent.debugTable[event][obj].mem = AceEvent.debugTable[event][obj].mem + mem
				AceEvent.debugTable[event][obj].time = AceEvent.debugTable[event][obj].time + time
			end
		end
	end
	_G.event = _G_event
end

local GetTime = GetTime
local delayRegistry
local function OnUpdate()
	local t = GetTime()
	local last = nil
	for k,v in pairs(delayRegistry) do
		if v.time <= t then
			if v.repeatDelay then
				v.time = t + v.repeatDelay
				last = k
			else
				delayRegistry[k] = nil
			end
			local event = v.event
			if type(event) == "function" then
				event(unpack(v))
			else
				AceEvent:TriggerEvent(event, unpack(v))
			end
			if not v.repeatDelay and Compost then
				Compost:Reclaim(v)
			end
			k = last
		else
			last = k
		end
	end
	if not next(delayRegistry) then
		AceEvent.frame:Hide()
	end
end

local stage = 3
if tonumber(date("%Y%m%d")) < 20060716 then
	stage = 1
elseif tonumber(date("%Y%m%d")) < 20060723 then
	stage = 2
end

if stage <= 2 then
	function AceEvent:TriggerDelayedEvent(event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
		if stage == 2 then
			local line = string.gsub(debugstack(), ".-\n(.-)\n.*", "%1")
			DEFAULT_CHAT_MESSAGE:AddMessage(line .. " - `TriggerDelayedEvent' has been replaced with `ScheduleEvent'. This will cause an error on July 23, 2006.")
		end
		self:ScheduleEvent(event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
	end
	function AceEvent:CancelDelayedEvent(t)
		if stage == 2 then
			local line = string.gsub(debugstack(), ".-\n(.-)\n.*", "%1")
			DEFAULT_CHAT_MESSAGE:AddMessage(line .. " - `CancelDelayedEvent' has been replaced with `CancelScheduledEvent'. This will cause an error on July 23, 2006.")
		end
		self:CancelScheduledEvent(t)
	end
end

local function ScheduleEvent(self, repeating, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
	local id
	if type(event) == "string" or type(event) == "table" then
		if type(event) == "table" then
			if not delayRegistry[event] then
				AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.")
			end
		end
		if type(delay) ~= "number" then
			id, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20
			AceEvent:argCheck(event, 3, "string", "function", --[[ so message is right ]] "number")
			AceEvent:argCheck(delay, 4, "number")
			self:CancelScheduledEvent(id)
		end
	else
		AceEvent:argCheck(event, 2, "string", "function")
		AceEvent:argCheck(delay, 3, "number")
	end
	
	if not AceEvent.delayRegistry then
		AceEvent.delayRegistry = Compost and Compost:Acquire() or {}
		delayRegistry = AceEvent.delayRegistry
		AceEvent.frame:SetScript("OnUpdate", OnUpdate)
	end
	local t
	if type(id) == "table" then
		for k in pairs(id) do
			id[k] = nil
		end
		table.setn(id, 0)
		t = id
	else
		t = Compost and Compost:Acquire() or {}
	end
	t.n = 0
	table.insert(t, a1)
	table.insert(t, a2)
	table.insert(t, a3)
	table.insert(t, a4)
	table.insert(t, a5)
	table.insert(t, a6)
	table.insert(t, a7)
	table.insert(t, a8)
	table.insert(t, a9)
	table.insert(t, a10)
	table.insert(t, a11)
	table.insert(t, a12)
	table.insert(t, a13)
	table.insert(t, a14)
	table.insert(t, a15)
	table.insert(t, a16)
	table.insert(t, a17)
	table.insert(t, a18)
	table.insert(t, a19)
	table.insert(t, a20)
	t.event = event
	t.time = GetTime() + delay
	t.self = self
	t.id = id or t
	t.repeatDelay = repeating and delay
	AceEvent.delayRegistry[t.id] = t
	AceEvent.frame:Show()
	return t.id
end

function AceEvent:ScheduleEvent(event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
	return ScheduleEvent(self, false, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
end

function AceEvent:ScheduleRepeatingEvent(event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
	return ScheduleEvent(self, true, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
end

function AceEvent:CancelScheduledEvent(t)
	AceEvent:argCheck(t, 2, "string", "table")
	if AceEvent.delayRegistry then
		local v = AceEvent.delayRegistry[t]
		if v then
			AceEvent.delayRegistry[t] = nil
			if Compost then
				Compost:Reclaim(v)
			end
			if not next(AceEvent.delayRegistry) then
				AceEvent.frame:Hide()
			end
			return true
		end
	end
	return false
end

function AceEvent:IsEventScheduled(t)
	AceEvent:argCheck(t, 2, "string", "table")
	if AceEvent.delayRegistry then
		local v = AceEvent.delayRegistry[t]
		if v then
			if Compost then
				Compost:Reclaim(v)
			end
			return true, v.time - GetTime()
		end
	end
	return false, nil
end

function AceEvent:UnregisterEvent(event)
	AceEvent:argCheck(event, 2, "string")

	if AceEvent.registry[event] and AceEvent.registry[event][self] then
		AceEvent.registry[event][self] = nil
	else
		AceEvent:error("Cannot unregister an event that you are not registered with.")
	end
end

function AceEvent:UnregisterAllEvents()
	for event, data in pairs(AceEvent.registry) do
		data[self] = nil
	end
end

function AceEvent:CancelAllScheduledEvents()
	if AceEvent.delayRegistry then
		for k,v in pairs(AceEvent.delayRegistry) do
			if v.self == target then
				if Compost then
					Compost:Reclaim(v)
				end
				AceEvent.delayRegistry[k] = nil
			end
		end
	end
end

function AceEvent:IsEventRegistered(event)
	AceEvent:argCheck(event, 2, "string")
	if AceEvent.registry[event] and AceEvent.registry[event][self] then
		return true, AceEvent.registry[event][self]
	end
	return false, nil
end

function AceEvent:OnEmbedDisable(target)
	self.UnregisterAllEvents(target)

	self.CancelAllScheduledEvents(target)
end

function AceEvent:EnableDebugging()
	if not self.debugTable then
		self.debugTable = {}
	end
end

function AceEvent:IsFullyInitialized()
	return self.postInit or false
end

function AceEvent:IsPostPlayerLogin()
	return self.playerLogin or false
end

function AceEvent:activate(oldLib, oldDeactivate)
	AceEvent = self

	if oldLib then
		self.onceRegistry = oldLib.onceRegistry
		self.delayRegistry = oldLib.delayRegistry
		self.registry = oldLib.registry
		self.frame = oldLib.frame
		self.debugTable = oldLib.debugTable
		self.playerLogin = oldLib.pew or DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.defaultLanguage and true
		self.postInit = oldLib.postInit or self.playerLogin and ChatTypeInfo and ChatTypeInfo.WHISPER and ChatTypeInfo.WHISPER.r and true
	end
	if not self.registry then
		self.registry = {}
	end
	if not self.frame then
		self.frame = CreateFrame("Frame")
	end
	self.frame:SetScript("OnEvent", function()
		if event then
			self:TriggerEvent(event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
		end
	end)
	if self.delayRegistry then
		delayRegistry = self.delayRegistry
		self.frame:SetScript("OnUpdate", OnUpdate)
	end
	
	self:UnregisterAllEvents()
	self:CancelAllScheduledEvents()
	
	if not self.playerLogin then
		self:RegisterEvent("PLAYER_LOGIN", function()
			self.playerLogin = true
		end, true)
	end
	
	if not self.postInit then
		local isReload = true
		local function func()
			self.postInit = true
			self:TriggerEvent("AceEvent_FullyInitialized")
			self:UnregisterEvent("CHAT_MSG_CHANNEL_NOTICE")
			self:UnregisterEvent("SPELLS_CHANGED")
		end
		self:RegisterEvent("MEETINGSTONE_CHANGED", function()
			self.playerLogin = true
			self:ScheduleEvent("AceEvent_FullyInitialized", func, isReload and 1 or 4)
		end, true)
		self:RegisterEvent("CHAT_MSG_CHANNEL_NOTICE", function()
			self:ScheduleEvent("AceEvent_FullyInitialized", func, 0.05)
		end)
		self:RegisterEvent("SPELLS_CHANGED", function()
			isReload = false
		end)
	end
	
	self.super.activate(self, oldLib, oldDeactivate)
	if oldLib then
		oldDeactivate(oldLib)
	end
end

local function external(self, major, instance)
	if major == "Compost-2.0" then
		Compost = instance
	end
end

AceLibrary:Register(AceEvent, MAJOR_VERSION, MINOR_VERSION, AceEvent.activate, nil, external)
AceEvent = AceLibrary(MAJOR_VERSION)
