--[[
Name: SpellEvents-1.0
Revision: $Rev: 32935 $
Author: Kameril
Website: http://www.wowace.com/wiki/SpellEvents-1.0
Documentation: http://www.wowace.com/wiki/SpellEvents-1.0
SVN: svn://svn.wowace.com/wowace/trunk/SpellEventsLib/
Description: A library that aims to make sense of Blizzard's new spell events.
Dependencies: AceLibrary, AceEvent-2.0, Deformat-2.0
]]

local MAJOR_VERSION = "SpellEvents-1.0"
local MINOR_VERSION = "$Revision: 32935 $"

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

local deformat = AceLibrary:HasInstance('Deformat-2.0') and AceLibrary('Deformat-2.0')

local Spell = {}
local UnitCastingInfo, UnitChannelInfo = UnitCastingInfo, UnitChannelInfo

-- Player's current casts
local pSpell, pRank, pDisplayName, pIcon, pStartTime, pEndTime, pIsTradeSkill

local playerCasts, playerChannels, playerInstants

-- Other units
local unitSpell, unitRank, unitDisplay, unitIcon, unitStartTime, unitEndTime, unitIsTradeSkill, unitChannel

function Spell:UNIT_SPELLCAST_START(unit)
	local spell, rank, displayName, icon, startTime, endTime, isTradeSkill = UnitCastingInfo(unit)
	if not spell then return end
	if unit == "player" then
		playerInstants[spell] = nil
		playerChannels[spell] = nil

		pSpell, pRank, pDisplayName, pIcon, pStartTime, pEndTime, pIsTradeSkill = spell, rank, displayName, icon, startTime, endTime, isTradeSkill
		self:TriggerEvent("SpellEvents_Player_Spell_Start", pSpell, pRank, pDisplayName, pIcon, pStartTime, pEndTime, pIsTradeSkill, playerCasts[spell])
	else
		unitSpell[unit] = spell
		unitChannel[unit] = nil
		unitRank[unit] = rank
		unitDisplay[unit] = displayName
		unitIcon[unit] = icon
		unitStartTime[unit] = startTime
		unitEndTime[unit] = endTime
		unitIsTradeSkill[unit] = isTradeSkill
		self:TriggerEvent("SpellEvents_Unit_Spell_Start", unit, spell, rank, displayName, icon, startTime, endTime, isTradeSkill)
	end
end


function Spell:UNIT_SPELLCAST_STOP(unit)
	if unit == "player" then
		if pSpell and not playerChannels[pSpell] then
			self:TriggerEvent("SpellEvents_Player_Spell_Interrupted", pSpell, pRank, pDisplayName, pIcon, pStartTime, pEndTime, pIsTradeSkill, playerCasts[pSpell])
			playerChannels[pSpell] = nil
			pSpell, pRank, pDisplayName, pIcon, pStartTime, pEndTime, pIsTradeSkill = nil
		end
	elseif unitSpell[unit] then
		self:TriggerEvent("SpellEvents_Unit_Spell_Stop", unit, unitSpell[unit], unitRank[unit], 
			unitDisplay[unit], unitIcon[unit], unitStartTime[unit], unitEndTime[unit], unitIsTradeSkill[unit])
		unitSpell[unit], unitRank[unit], unitDisplay[unit], unitIcon[unit], 
		unitStartTime[unit], unitEndTime[unit], unitIsTradeSkill[unit] = nil
	end
end

do
	local patterns = { SPELLFAILCASTSELF, SPELLFAILPERFORMSELF }
	local spell, reason
	function Spell:CHAT_MSG_SPELL_FAILED_LOCALPLAYER(msg)
		for i, pattern in ipairs(patterns) do
			spell, reason = deformat(msg, pattern)
			if spell then
				return
			end
		end
	end
	function Spell:UNIT_SPELLCAST_FAILED(unit) --This event isn't received for any unit that isn't the player
		if spell and unit == "player" then
			self:TriggerEvent("SpellEvents_Player_Spell_Failed", spell, reason)
			spell, reason = nil
		end
	end
end

--This event isn't received for any unit that isn't the player
function Spell:UNIT_SPELLCAST_INTERRUPTED(unit)
	if unit == "player" then
		if pSpell then
			self:TriggerEvent("SpellEvents_Player_Spell_Interrupted", pSpell, pRank, pDisplayName, pIcon, pStartTime, pEndTime, pIsTradeSkill, playerCasts[pSpell])
			playerCasts[pSpell] = nil
			pSpell, pRank, pDisplayName, pIcon, pStartTime, pEndTime, pIsTradeSkill = nil
		end
	end
end

function Spell:UNIT_SPELLCAST_DELAYED(unit)
	local spell,_,_,_, startTime, endTime = UnitCastingInfo(unit)
	if not spell then return end
	if unit == "player" then
		self:TriggerEvent("SpellEvents_Player_Spell_Changed", pSpell, endTime - pEndTime, startTime, endTime)
		pStartTime, pEndTime = startTime, endTime
	elseif unitSpell[unit] then
		self:TriggerEvent("SpellEvents_Unit_Spell_Changed", unit, spell, endTime - unitEndTime[unit], startTime, endTime)
		unitStartTime[unit] = startTime
		unitEndTime[unit] = endTime
	end
end

function Spell:UNIT_SPELLCAST_CHANNEL_START(unit)
	local spell, rank, displayName, icon, startTime, endTime, isTradeSkill = UnitChannelInfo(unit)
	if not spell then return end
	if unit == "player" then
		playerInstants[spell] = nil
		playerCasts[spell] = nil
		playerChannels[spell] = playerChannels[spell] or ""
		pSpell, pRank, pDisplayName, pIcon, pStartTime, pEndTime, pIsTradeSkill = spell, rank, displayName, icon, startTime, endTime, isTradeSkill
		self:TriggerEvent("SpellEvents_Player_Channel_Start", pSpell, pRank, pDisplayName, pIcon, pStartTime, pEndTime, pIsTradeSkill, playerCasts[spell])
	else
		unitSpell[unit] = nil
		unitChannel[unit] = spell
		unitRank[unit] = rank
		unitDisplay[unit] = displayName
		unitIcon[unit] = icon
		unitStartTime[unit] = startTime
		unitEndTime[unit] = endTime
		unitIsTradeSkill[unit] = isTradeSkill
		self:TriggerEvent("SpellEvents_Unit_Channel_Start", unit, spell, rank, displayName, icon, startTime, endTime, isTradeSkill)
	end
end

function Spell:UNIT_SPELLCAST_CHANNEL_UPDATE(unit)
	local spell, rank, displayName, icon, startTime, endTime, isTradeSkill = UnitChannelInfo(unit)
	if not spell then return end
	if unit == "player" then
		if pSpell then
			self:TriggerEvent("SpellEvents_Player_Channel_Changed",  pSpell, endTime - pEndTime)
			pStartTime, pEndTime = startTime, endTime
		end
	elseif unitChannel[unit] then
		self:TriggerEvent("SpellEvents_Unit_Channel_Changed", unit, spell, endTime - unitEndTime[unit], startTime, endTime)
		unitStartTime[unit] = startTime
		unitEndTime[unit] = endTime
	end
end

function Spell:UNIT_SPELLCAST_CHANNEL_STOP(unit)
	if unit == "player" then
		if pSpell then
			self:TriggerEvent("SpellEvents_Player_Channel_Stop", pSpell, pRank, pDisplayName, pIcon, pStartTime, pEndTime, pIsTradeSkill, playerChannels[pSpell])
			playerChannels[pSpell] = nil
			pSpell, pRank, pDisplayName, pIcon, pStartTime, pEndTime, pIsTradeSkill = nil
		end
	elseif unitChannel[unit] then
		self:TriggerEvent("SpellEvents_Unit_Channel_Stop", unit, unitChannel[unit], unitRank[unit], 
			unitDisplay[unit], unitIcon[unit], unitStartTime[unit], unitEndTime[unit], unitIsTradeSkill[unit])
		unitChannel[unit], unitRank[unit], unitDisplay[unit], unitIcon[unit], 
		unitStartTime[unit], unitEndTime[unit], unitIsTradeSkill[unit] = nil
	end
end

function Spell:UNIT_SPELLCAST_SENT(unit, spell, rank, target)
	pSpell, pRank = spell, rank
	playerInstants[spell] = target
	playerCasts[spell] = target
	playerChannels[spell] = target
end

function Spell:UNIT_SPELLCAST_SUCCEEDED(unit, spell, rank)
	if playerInstants[spell] then
		self:TriggerEvent("SpellEvents_Player_Instant_Success", spell, rank, playerInstants[spell])
	elseif playerCasts[spell] then
		self:TriggerEvent("SpellEvents_Player_Spell_Success", pSpell, pRank, pDisplayName, pIcon, pStartTime, pEndTime, pIsTradeSkill, playerCasts[spell])
		pSpell, pRank, pDisplayName, pIcon, pStartTime, pEndTime, pIsTradeSkill = nil
	end
	playerInstants[spell] = nil
	playerCasts[spell] = nil
end

local function activate(self, oldLib, oldDeactivate)
	unitSpell = {}
	unitRank = {}
	unitDisplay = {}
	unitIcon = {}
	unitStartTime = {}
	unitEndTime = {}
	unitIsTradeSkill = {}
	unitChannel = {}

	playerCasts = {}
	playerChannels = {}
	playerInstants = {}

	if oldDeactivate then
		oldDeactivate(oldLib)
	end
end

local function deactivate(self)
	if self.UnregisterAllEvents then
		self:UnregisterAllEvents()
	end
end

local function external(self, major, instance)
	if major == 'AceEvent-2.0' then
		instance:embed(self)
		
		self:RegisterEvent("UNIT_SPELLCAST_START")
		self:RegisterEvent("UNIT_SPELLCAST_STOP")
		self:RegisterEvent("UNIT_SPELLCAST_FAILED")
		self:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
		self:RegisterEvent("UNIT_SPELLCAST_DELAYED")
	
		self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
		self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
		self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
	
		self:RegisterEvent("UNIT_SPELLCAST_SENT")
		self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
	
		self:RegisterEvent("CHAT_MSG_SPELL_FAILED_LOCALPLAYER")
		
	elseif major == 'Deformat-2.0' then
		deformat = instance
	end
end

AceLibrary:Register(Spell, MAJOR_VERSION, MINOR_VERSION, activate, deactivate, external)
Spell = nil
