--[[
Name: SpellStatus-1.0
Revision: $Rev: 14589 $
Author(s): Nightdew (denzsolnightdew@gmail.com)
Website: http://www.wowace.com/index.php/SpellStatus-1.0
Documentation: http://www.wowace.com/index.php/SpellStatus-1.0
SVN: http://svn.wowace.com/root/trunk/SpellStatusLib/SpellStatus-1.0
Description: Status library that simplifies retrieving spell status information from the player
Dependencies: AceLibrary, AceDebug-2.0, AceEvent-2.0, AceHook-2.1, Deformat-2.0, Gratuity-2.0, SpellCache-1.0, (optional) SpellStatus-AimedShot-1.0
]]

local MAJOR_VERSION = "SpellStatus-1.0"
local MINOR_VERSION = "$Revision: 14590 $"

if (not AceLibrary) then 
	error(MAJOR_VERSION .. " requires AceLibrary.") 
end

if (not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION)) then 
	return 
end

local function CheckDependency(dependencies)
	for index, value in ipairs(dependencies) do
		if (not AceLibrary:HasInstance(value)) then 
			error(format("%s requires %s to function properly", MAJOR_VERSION, value))
		end
	end
end

local dependencyLibraries = {
	"AceDebug-2.0", 
	"AceEvent-2.0", 
	"AceHook-2.1", 
	"Deformat-2.0", 
	"Gratuity-2.0", 
	"SpellCache-1.0"
}

CheckDependency(dependencyLibraries)

local deformat = AceLibrary("Deformat-2.0")
local gratuity = AceLibrary("Gratuity-2.0")
local spellcache = AceLibrary("SpellCache-1.0")

--Create Library Object
local SpellStatus = {}
 
--Embed all needed mixins into the Library Object SpellStatus
AceLibrary("AceDebug-2.0"):embed(SpellStatus)
AceLibrary("AceEvent-2.0"):embed(SpellStatus)
AceLibrary("AceHook-2.1"):embed(SpellStatus)

--
-- Local functions not to be called from outside
--

local function InitializeHooks(self)
	self:Hook("CastSpell")
	self:Hook("CastSpellByName")
	self:Hook("UseAction")
	self:Hook("CastShapeshiftForm")
	self:Hook("UseInventoryItem")
	self:Hook("UseContainerItem")
	self:Hook("ToggleGameMenu")
	self:Hook("SpellStopCasting")
end

local function InitializeEventRegisters(self)
	self:RegisterEvent("SPELLCAST_START")
	self:RegisterEvent("SPELLCAST_STOP")
	self:RegisterEvent("SPELLCAST_INTERRUPTED")
	self:RegisterEvent("SPELLCAST_FAILED")
	self:RegisterEvent("SPELLCAST_DELAYED")
	self:RegisterEvent("SPELLCAST_CHANNEL_START")
	self:RegisterEvent("SPELLCAST_CHANNEL_STOP")
	self:RegisterEvent("SPELLCAST_CHANNEL_UPDATE")
	self:RegisterEvent("UI_ERROR_MESSAGE")
	self:RegisterEvent("CHAT_MSG_SPELL_FAILED_LOCALPLAYER")
	self:RegisterEvent("CHAT_MSG_SPELL_AURA_GONE_SELF")

	self:RegisterEvent("CHAT_MSG_COMBAT_SELF_HITS")
	self:RegisterEvent("CHAT_MSG_COMBAT_SELF_MISSES")
	
	self:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
	 
	--SPELLLOGSELFOTHER

	--Used to determine if we are wanding.
	self:RegisterEvent("START_AUTOREPEAT_SPELL")
	self:RegisterEvent("STOP_AUTOREPEAT_SPELL")
	
	self:RegisterEvent("PLAYER_ENTER_COMBAT")
	self:RegisterEvent("PLAYER_LEAVE_COMBAT")

	self:RegisterEvent("PLAYER_REGEN_DISABLED")
	self:RegisterEvent("PLAYER_REGEN_ENABLED")

	self:RegisterEvent("SpellCache_Updated")
end

local function ResetActiveVariables(self)
	self:LevelDebug(2, "ResetActiveVariables")
	--Active Spell
	self.vars.ActiveId = nil
	self.vars.ActiveName = nil
	self.vars.ActiveRank = nil
	self.vars.ActiveFullName = nil
	self.vars.ActiveCastStartTime = nil
	self.vars.ActiveCastStopTime = nil
	self.vars.ActiveCastDuration = nil

	--Casting
	self.vars.ActiveCastDelay = nil
	self.vars.ActiveCastDelayTotal = nil

	--Channeling
	self.vars.ActiveAction = nil
	self.vars.ActiveCastDisruption = nil
	self.vars.ActiveCastDisruptionTotal = nil
	
	--NextMelee
	self.vars.NextMeleeId = nil
	self.vars.NextMeleeName = nil
	self.vars.NextMeleeRank = nil
	self.vars.NextMeleeFullName = nil
end

local function ResetVariables(self)
	self:LevelDebug(2, "ResetVariables")

	--Spell Attempted to cast
	self.vars.AttemptId = nil
	self.vars.AttemptName = nil
	self.vars.AttemptRank = nil
	self.vars.AttemptFullName = nil

	ResetActiveVariables(self)

	--UI_ERROR_MESSAGE
	self.vars.UIEM_Message = nil
	--CHATMSGSPELLFAILEDLOCALPLAYER
	self.vars.CMSFLP_SpellName = nil
	self.vars.CMSFLP_Message = nil
	
end

local function InitializeVariables(self)
	self:LevelDebug(2, "InitializeVariables")

	self.vars = {}

	--True while data is assigned until fail or stop or start and no SpellId
	self.vars.Using = false
	--True while data is assigned until fail or stop or start and SpellId
	self.vars.Preparing = false
	--Only true for spell with a casting time
	self.vars.Casting = false
	--Only true for channeling spells 
	self.vars.Channeling = false
	--Only true for spells that are next melee
	self.vars.NextMeleeing = false	
	
	--Whether or not the character is auto repeating
	self.vars.AutoRepeating = false
	--Are we attacking currently
	self.vars.Attacking = false
	--Are we currently in combat
	self.vars.Combating = false
	
	self.vars.Targeting = false

	--True when hooked into ToggleGameMenu
	self.vars.CancelTargeting = false
	self.vars.CancelCasting = false
	self.vars.CancelChanneling = false
	
	ResetVariables(self)
end

--
-- Activate method
--

local function activate(self, oldLib, oldDeactivate)
	--self:SetDebugging(true)
	--self:SetDebugLevel(3)

	self:LevelDebug(2, "SpellStatus - activate")
	
	if (oldLib) then
		oldLib:UnregisterAllEvents()
		oldLib:UnhookAll()
	end
	
	--Default code to clean up the oldlib
	if (oldDeactivate) then
		oldDeactivate(oldLib)
	end

	InitializeVariables(self)
	InitializeHooks(self)
	InitializeEventRegisters(self)
end


function SpellStatus:Report()
	self:LevelDebug(3, 
		format("Using: %s", tostring(self.vars.Using)), 
		format("Preparing: %s", tostring(self.vars.Preparing)), 
		format("Casting: %s", tostring(self.vars.Casting)), 
		format("Channeling: %s", tostring(self.vars.Channeling)),
		format("SpellName: %s", tostring(self:GetActiveSpellName()))
	)
end

function SpellStatus:IsUsing()
	return self.vars.Using == true
end

function SpellStatus:IsPreparing()
	return self.vars.Preparing == true
end

function SpellStatus:IsCasting()
	return self.vars.Casting == true
end

function SpellStatus:IsChanneling()
	return self.vars.Channeling == true
end

function SpellStatus:IsNextMeleeing()
	return self.vars.NextMeleeing == true
end

function SpellStatus:IsCastingOrChanneling()
	return self:IsCasting() or self:IsChanneling()
end

function SpellStatus:IsPreparingOrCastingOrChanneling()
	return self:IsPreparing() or self:IsCasting() or self:IsChanneling()
end

function SpellStatus:IsAutoRepeating()
	return self.vars.AutoRepeating == true
end

function SpellStatus:IsWanding()
	return ((self.vars.AutoRepeating == true) and HasWandEquipped()) and true or false
end

function SpellStatus:IsAttacking()
	return self.vars.Attacking == true
end

function SpellStatus:IsCombating()
	return self.vars.Combating == true
end

function SpellStatus:IsActiveSpell(spellId, spellName)
	local sId, sName = self:GetActiveSpellData()
	self:LevelDebug(2, "IsActiveSpell", spellId, sId, spellName, sName)
	--sId and spellId might be nil
	return (sId == spellId) and (sName == spellName)
end

function SpellStatus:GetActiveSpellData()
	return self.vars.ActiveId, self.vars.ActiveName, self.vars.ActiveRank, self.vars.ActiveFullName,
					self.vars.ActiveCastStartTime, self.vars.ActiveCastStopTime, self.vars.ActiveCastDuration,
					self.vars.ActiveAction
end

function SpellStatus:GetActiveSpellName()
	local _, spellName = self:GetActiveSpellData()
	return spellName
end

function SpellStatus:GetNextMeleeSpellData()
	return self.vars.NextMeleeId, self.vars.NextMeleeName, self.vars.NextMeleeRank, self.vars.NextMeleeFullName
end

--
-- Function Hooking
--

function SpellStatus:SpellCache_Updated()
end

local function AssignNextMeleeSpellData(self, spellId, spellName, spellRank, spellFullName)
	if (not spellId) then
		return false
	end
		
	gratuity:SetSpell(spellId, BOOKTYPE_SPELL)
	if (gratuity:Find(SPELL_ON_NEXT_SWING, 2, 3, false, true, true) == nil) then
		return false
	end
	
	self:LevelDebug(1, "AssignNextMeleeSpellData", spellId, spellName, spellRank, spellFullName)

	self.vars.NextMeleeing = true
	
	self.vars.NextMeleeId = spellId
	self.vars.NextMeleeName = spellName
	self.vars.NextMeleeRank = spellRank
	self.vars.NextMeleeFullName = spellFullName
	return true
end


local function AssignSpellData(self, spellId, spellName, spellRank, spellFullName)
	--self:LevelDebug(1, "AssignSpellData", spellId, spellName, spellRank, spellFullName)

	--If Next Melee spell stop dont continue
	if (AssignNextMeleeSpellData(self, spellId, spellName, spellRank, spellFullName)) then
		return
	end

	if (self.vars.Preparing or self.vars.Casting or self.vars.Channeling) then
		return
	end

	if (self.vars.Using and (spellName == self.vars.ActiveName)) then
		return
	end
	
	
	self:LevelDebug(1, "AssignSpellData", spellId, spellName, spellRank, spellFullName)

	ResetVariables(self)

	self.vars.ActiveId = spellId
	self.vars.ActiveName = spellName
	self.vars.ActiveRank = spellRank
	self.vars.ActiveFullName = spellFullName

	self.vars.Preparing = spellId ~= nil
	self.vars.Using = not self.vars.Preparing

	--Only preparing if you have a spellId
	if (self.vars.Preparing) then
		local aimedShot = AceLibrary:HasInstance("SpellStatus-AimedShot-1.0") and AceLibrary("SpellStatus-AimedShot-1.0") or nil
		if (aimedShot and aimedShot:Active()) then
			local aimedShotId, aimedShotDuration = aimedShot:MatchSpellId(spellId)
			if (aimedShotId) then
				self:SPELLCAST_START(spellName, aimedShotDuration)
			end
		end
	end
end

local function TriggerFailureEvent(self, overrideHasMessage)
	overrideHasMessage = overrideHasMessage == true
	--self:LevelDebug(2, "TriggerFailureEvent", overrideHasMessage)

	local hasMessage = self.vars.UIEM_Message or self.vars.CMSFLP_Message

	self:LevelDebug(2, "TriggerFailureEvent", hasMessage, overrideHasMessage)

	if (not (hasMessage or overrideHasMessage)) then
		return false
	end
	
	self:LevelDebug(2, "TriggerFailureEvent", overrideHasMessage)

	local isActiveSpell = false
	local sId, sName, sRank, sFullName
	
	if (self.vars.AttemptName) then
		sId = self.vars.AttemptId
		sName = self.vars.AttemptName
		sRank = self.vars.AttemptRank
		sFullName = self.vars.AttemptFullName
		self:LevelDebug(2, "TriggerFailureEvent Attempt", sId, sName, sRank, sFullName)
		self.vars.Using = false
		self.vars.Preparing = false
		self.vars.NextMeleeing = false
		--Necessary because the error might happen through multiple paths
		self.vars.AttemptCastFailure = true
	elseif (self.vars.CMSFLP_SpellName and (self.vars.CMSFLP_SpellName == self.vars.NextMeleeName)) then
		sId = self.vars.NextMeleeId
		sName = self.vars.NextMeleeName
		sRank = self.vars.NextMeleeRank
		sFullName = self.vars.NextMeleeFullName
		self:LevelDebug(2, "TriggerFailureEvent NextMelee", sId, sName, sRank, sFullName)
		self.vars.NextMeleeing = false
	elseif (overrideHasMessage or (self.vars.CMSFLP_SpellName and (self.vars.CMSFLP_SpellName == self.vars.ActiveName))) then
		isActiveSpell = true
		sId = self.vars.ActiveId
		sName = self.vars.ActiveName
		sRank = self.vars.ActiveRank
		sFullName = self.vars.ActiveFullName
		self:LevelDebug(2, "TriggerFailureEvent Active", sId, sName, sRank, sFullName)
		self.vars.Using = false
		self.vars.Preparing = false
		self.vars.Casting = false
		self.vars.Channeling = false
	else --must have been unrelated
		return false
	end
	
	self:TriggerEvent("SpellStatus_SpellCastFailure", 
		sId, sName, sRank, sFullName, isActiveSpell, 
		self.vars.UIEM_Message, self.vars.CMSFLP_SpellName, self.vars.CMSFLP_Message
	)
	
	self.vars.UIEM_Message = nil
	self.vars.CMSFLP_SpellName = nil
	self.vars.CMSFLP_Message = nil
	
	return true
end

function ResetCastOriginal(self, sId, sName, sRank, sFullName)
	--setup variables if failing immediately
	self.vars.AttemptId = sId
	self.vars.AttemptName = sName
	self.vars.AttemptRank = sRank
	self.vars.AttemptFullName = sFullName
	
	--reset Error Message
	self.vars.UIEM_Message = nil
	self.vars.CMSFLP_SpellName = nil
	self.vars.CMSFLP_Message = nil

	self.vars.AttemptCastFailure = false
end

function CastOriginal(self, methodName, param1, param2, param3, sId, sName, sRank, sFullName)
	self:LevelDebug(1, ">>>> CastOriginal", 
		methodName, param1, param2, param3, 
		sId, sName, sRank, sFullName)

	ResetCastOriginal(self, sId, sName, sRank, sFullName)	

	self:LevelDebug(3, "-> CastOriginal")
	self.hooks[methodName](param1, param2, param3)
	self:LevelDebug(3, "<- CastOriginal")

	TriggerFailureEvent(self)
	if (not self.vars.AttemptCastFailure) then
		AssignSpellData(self, sId, sName, sRank, sFullName)
	end
	
	ResetCastOriginal(self, nil, nil, nil, nil)	

	self:LevelDebug(1, "<<<< CastOriginal", 
		methodName, param1, param2, param3, 
		sId, sName, sRank, sFullName)
end

function SpellStatus:CastSpellByName(spellName, onSelf)
	self:LevelDebug(2, ">>>> CastSpellByName", spellName, onSelf)

	local sName, sId, sRank, sFullName
	sName, sRank, sId, sFullName = spellcache:GetSpellData(spellName)

	CastOriginal(self, "CastSpellByName", spellName, onSelf, nil, sId, sName, sRank, sFullName)

	self:LevelDebug(2, "<<<< CastSpellByName2", spellName, onSelf)
end

function SpellStatus:CastSpell(spellId, spellbookType)
	self:LevelDebug(2, ">>>> CastSpell", spellId, spellbookType)

	local sId, sName, sRank, sFullName
	if (spellbookType == BOOKTYPE_SPELL) then
		sName, sRank, sId, sFullName = spellcache:GetSpellData(spellId)
	end
	
	CastOriginal(self, "CastSpell", spellId, spellbookType, nil, sId, sName, sRank, sFullName)

	self:LevelDebug(2, "<<<< CastSpell", spellId, spellbookType)
end

--When using UseAction through clicking an actiobar button, we need to get in
local function GetGratuitySpellData(self, slotId)
	local spellName = gratuity:GetLine(1)
	local spellRank = gratuity:GetLine(1, true)
	--empty slot?
	if (not spellName) then
		return
	end
	
	self:LevelDebug(3, "GetGratuitySpellData", spellName, spellRank)
	local sName, sRank, sId, sFullName = spellcache:GetSpellData(spellName, spellRank)
	if (sName) then
		return sId, sName, sRank, sFullName
	else
		return nil, spellName, nil, nil
	end
end

function SpellStatus:UseAction(slotId, checkCursor, onSelf)
	self:LevelDebug(1, ">>>> UseAction", slotId, checkCursor, onSelf)

	local actionText = GetActionText(slotId)
	local isMacro = actionText ~= nil
	--self:LevelDebug(2, "UseAction", actionText, tostring(isMacro))
	
	if (isMacro) then
		self.hooks["UseAction"](slotId, checkCursor, onSelf)
	else
		gratuity:SetAction(slotId)
		local sId, sName, sRank, sFullName = GetGratuitySpellData(self, slotId)
		if (sName) then
			CastOriginal(self, "UseAction", slotId, checkCursor, onSelf, sId, sName, sRank, sFullName)
		else
			self.hooks["UseAction"](slotId, checkCursor, onSelf)
		end
	end

	self:LevelDebug(1, "<<<< UseAction", slotId, checkCursor, onSelf)
end

function SpellStatus:CastPetAction(slotId, onUnit)
	self:LevelDebug(1, ">>>> CastPetAction", slotId, onUnit)
	gratuity:SetAction(slotId)
	local sId, sName, sRank, sFullName = GetGratuitySpellData(self, slotId)
	if (sName) then
		CastOriginal(self, "CastPetAction", slotId, onUnit, nil, sId, sName, sRank, sFullName)
	else
		self.hooks["CastPetAction"](slotId, onUnit)
	end
	self:LevelDebug(1, "<<<< CastPetAction", slotId, onUnit)
end

function SpellStatus:CastShapeshiftForm(index)
	self:LevelDebug(2, ">>>> CastShapeshiftForm", index)

	gratuity:SetShapeshift(index)
	local sId, sName, sRank, sFullName = GetGratuitySpellData(self, slotId)
	if (sName) then
		CastOriginal(self, "CastShapeshiftForm", index, nil, nil, sId, sName, sRank, sFullName)
	else
		self.hooks["CastShapeshiftForm"](index)
	end
	
	self:LevelDebug(2, "<<<< CastShapeshiftForm", index)
end

local function GetItemLinkData(slotId, bagId)
	local itemLink = nil

	if (bagId == nil) then
		itemLink = GetInventoryItemLink("player", slotId)
	else
		itemLink = GetContainerItemLink(bagId, slotId)
	end

	if (itemLink) then
		local _, _, itemName = string.find(itemLink, "^.*%[(.*)%].*$")
		return itemName, itemLink
	end
end

function SpellStatus:UseInventoryItem(slotId)
	self:LevelDebug(2, ">>>> UseInventoryItem", slotId)
	
	local itemName, itemLink = GetItemLinkData(slotId)
	
	if (itemLink) then
		CastOriginal(self, "UseInventoryItem", slotId, nil, nil, nil, itemName, nil, itemLink)
	else
		self.hooks["UseInventoryItem"](slotId)
	end
	
	self:LevelDebug(2, "<<<< UseInventoryItem", slotId)
end

function SpellStatus:UseContainerItem(bagId, slotId)
	self:LevelDebug(2, ">>>> UseContainerItem", bagId, slotId)
	
	local itemName, itemLink = GetItemLinkData(slotId, bagId)
	
	if (itemLink) then
		CastOriginal(self, "UseContainerItem", bagId, slotId, nil, nil, itemName, nil, itemLink)
	else
		self.hooks["UseContainerItem"](bagId, slotId)
	end
	
	self:LevelDebug(2, "<<<< UseContainerItem", bagId, slotId)
end

function SpellStatus:ToggleGameMenu()
	--self:LevelDebug(3, "ToggleGameMenu1", SpellIsTargeting())

	--If these hook is called and targeting, targeting will always be killed first
	self.vars.CancelTargeting = SpellIsTargeting()
	self.vars.CancelCasting = self.vars.Casting
	self.vars.CancelChanneling = self.vars.Channeling

	self.hooks["ToggleGameMenu"]()

	self.vars.CancelTargeting = false
	self.vars.CancelCasting = false
	self.vars.CancelChanneling = false

	--self:LevelDebug(3, "ToggleGameMenu2", SpellIsTargeting())
end

function SpellStatus:SpellStopCasting()
	self:LevelDebug(2, ">>>> SpellStopCasting")
	if (self:IsCastingOrChanneling()) then
		self.vars.SpellStopCastingActiveName = self.vars.ActiveName
	end
	self.hooks["SpellStopCasting"]()
	self:LevelDebug(2, "<<<< SpellStopCasting")
end

function SpellStatus:SPELLCAST_START(spellName, duration)
	self:LevelDebug(1, "SPELLCAST_START", spellName, duration)

	self.vars.SpellStopCastingActiveName = nil
	self.vars.Using = false
	self.vars.Preparing = false
	self.vars.Casting = true

	if (self.vars.ActiveName ~= spellName) then
		ResetActiveVariables(self)
		self.vars.ActiveName = spellName
		self.vars.ActiveFullName = spellName
	end
			

	self.vars.ActiveCastStartTime = GetTime()
	self.vars.ActiveCastDuration = duration
	self.vars.ActiveCastStopTime = self.vars.ActiveCastStartTime + (self.vars.ActiveCastDuration/1000)

	self.vars.ActiveCastDelay = nil
	self.vars.ActiveCastDelayTotal = nil

	self:TriggerEvent(
		"SpellStatus_SpellCastCastingStart", 
		self.vars.ActiveId,
		self.vars.ActiveName, 
		self.vars.ActiveRank,
		self.vars.ActiveFullName,
		self.vars.ActiveCastStartTime,
		self.vars.ActiveCastStopTime,
		self.vars.ActiveCastDuration
	)
end

function SpellStatus:SPELLCAST_DELAYED(delay)
	self:LevelDebug(1, "SPELLCAST_DELAYED", delay)

	if (self.vars.ActiveCastStopTime == nil) then
		return
	end

	self.vars.ActiveCastDelay = delay/1000
	self.vars.ActiveCastDelayTotal = (self.vars.ActiveCastDelayTotal or 0) + self.vars.ActiveCastDelay
	self.vars.ActiveCastStopTime = self.vars.ActiveCastStopTime + self.vars.ActiveCastDelay

	self:TriggerEvent(
		"SpellStatus_SpellCastCastingChange", 
		self.vars.ActiveId,
		self.vars.ActiveName, 
		self.vars.ActiveRank,
		self.vars.ActiveFullName,
		self.vars.ActiveCastStartTime,
		self.vars.ActiveCastStopTime,
		self.vars.ActiveCastDuration,
		self.vars.ActiveCastDelay,
		self.vars.ActiveCastDelayTotal
	)
end

function SpellStatus:SPELLCAST_STOP()
	self:LevelDebug(1, "SPELLCAST_STOP")

	--If canceling targeting.. ignore stop!
	if (self.vars.CancelTargeting or self.vars.CancelCasting or self.vars.CancelChanneling) then
		return
	end

	--if (self.vars.ActiveId == nil) then
	if (self.vars.ActiveName == nil) then
		return
	end
	
	--Player probably moved initiating a client and server side SPELLCAST_STOP
	if (not(	self.vars.Using or 
				self.vars.Preparing or 
				self.vars.Casting or 
				self.vars.Channeling or
				self.vars.AutoRepeating )) then
		return
	end
	
	if (TriggerFailureEvent(self)) then
		return
	end
	
	self.vars.Using = false
	self.vars.Preparing = false

	--Second SPELLCAST_STOP received from server after SpellStopCasting was called
	if ((not self.vars.Casting) and 
		self.vars.SpellStopCastingActiveName and 
		(self.vars.ActiveName == self.vars.SpellStopCastingActiveName)) then
		self.vars.SpellStopCastingActiveName = nil
		return
	end

	local castingInstant = (not self.vars.Casting) and (not self.vars.Channeling)
	self.vars.Casting = false

	--Generate Finish Event if not channeling
	if (not self.vars.Channeling) then
		local sD = castingInstant and self or self.vars.ActiveCastingData

		self.vars.ActiveCastStartTime = self.vars.ActiveCastStartTime or GetTime()
		self.vars.ActiveCastStopTime = GetTime()
		
		self:TriggerEvent(
			castingInstant and "SpellStatus_SpellCastInstant" or "SpellStatus_SpellCastCastingFinish", 
			self.vars.ActiveId,
			self.vars.ActiveName, 
			self.vars.ActiveRank,
			self.vars.ActiveFullName,
			self.vars.ActiveCastStartTime,
			self.vars.ActiveCastStopTime,
			self.vars.ActiveCastDuration,
			self.vars.ActiveCastDelayTotal
		)
		--We cant reset because maybe we have a failure!!!
		--ResetActiveVariables(self)
	end
end

function SpellStatus:SPELLCAST_CHANNEL_START(duration, action)
	self:LevelDebug(2, "SPELLCAST_CHANNEL_START", duration, action)

	self.vars.SpellStopCastingActiveName = nil
	self.vars.Using = false
	self.vars.Preparing = false
	self.vars.Channeling = true
	
	local sD = self.vars.ActiveChannelingData

	self.vars.ActiveCastStartTime = GetTime()
	self.vars.ActiveCastDuration = duration / 1000
	self.vars.ActiveCastStopTime = self.vars.ActiveCastStartTime + self.vars.ActiveCastDuration
	self.vars.ActiveAction = action

	self.vars.ActiveId = self.vars.ActiveId
	self.vars.ActiveName = self.vars.ActiveName
	self.vars.ActiveRank = self.vars.ActiveRank
	self.vars.ActiveFullName = self.vars.ActiveFullName
	
	self.vars.ActiveCastDisruption = nil
	self.vars.ActiveCastDisruptionTotal = nil

	self:TriggerEvent(
		"SpellStatus_SpellCastChannelingStart", 
		self.vars.ActiveId,
		self.vars.ActiveName, 
		self.vars.ActiveRank,
		self.vars.ActiveFullName,
		self.vars.ActiveCastStartTime,
		self.vars.ActiveCastStopTime,
		self.vars.ActiveCastDuration,
		self.vars.ActiveAction
	)
end

--Any changes while channeling will come through this event
function SpellStatus:SPELLCAST_CHANNEL_UPDATE(duration)
	self:LevelDebug(2, "SPELLCAST_CHANNEL_UPDATE", duration)

	local sD = self.vars.ActiveChannelingData

	local timeStamp = GetTime()
	
	--New Duration
	local spellCastDuration = duration / 1000
	self:LevelDebug(3, "spellCastDuration", spellCastDuration)
	--Store old stop time
	local spellCastStopTime = self.vars.ActiveCastStopTime
	--Time Passed since Channeling
	local spellCastTimePassed = timeStamp - self.vars.ActiveCastStartTime
	self:LevelDebug(3, "spellCastTimePassed", spellCastTimePassed)
	--What channel time did we loose
	self.vars.ActiveCastDisruption = self.vars.ActiveCastDuration - spellCastDuration - spellCastTimePassed
	self.vars.ActiveCastDisruptionTotal = (self.vars.ActiveCastDisruptionTotal or 0) + self.vars.ActiveCastDisruption
	self.vars.ActiveCastStopTime = timeStamp + spellCastDuration

	self:TriggerEvent(
		"SpellStatus_SpellCastChannelingChange", 
		self.vars.ActiveId,
		self.vars.ActiveName, 
		self.vars.ActiveRank,
		self.vars.ActiveFullName,
		self.vars.ActiveCastStartTime,
		self.vars.ActiveCastStopTime,
		self.vars.ActiveCastDuration,
		self.vars.ActiveAction,
		self.vars.ActiveCastDisruption,
		self.vars.ActiveCastDisruptionTotal
	)
end

function SpellStatus:SPELLCAST_CHANNEL_STOP()
	self:LevelDebug(2, "SPELLCAST_CHANNEL_STOP")

	self.vars.Using = false
	self.vars.Preparing = false
	self.vars.Channeling = false

	self.vars.ActiveCastStopTime = GetTime()

	self:TriggerEvent(
		"SpellStatus_SpellCastChannelingFinish", 
		self.vars.ActiveId,
		self.vars.ActiveName, 
		self.vars.ActiveRank,
		self.vars.ActiveFullName,
		self.vars.ActiveCastStartTime,
		self.vars.ActiveCastStopTime,
		self.vars.ActiveCastDuration,
		self.vars.ActiveAction,
		self.vars.ActiveCastDisruptionTotal
	)
end

--Always thrown before CHAT_MSG_SPELL_FAILED_LOCALPLAYER which might be thrown
function SpellStatus:UI_ERROR_MESSAGE(message)
	self:LevelDebug(2, "UI_ERROR_MESSAGE", message)

	self.vars.UIEM_Message = message
end

local FAILUREMESSAGE = {
	SPELLFAILCASTSELF,
	SPELLFAILPERFORMSELF
}

function SpellStatus:CHAT_MSG_SPELL_FAILED_LOCALPLAYER(message)
	self:LevelDebug(2, "CHAT_MSG_SPELL_FAILED_LOCALPLAYER", message)

	table.foreach(FAILUREMESSAGE, 
		function(key, value)
			local spellName, spellFailureMessage = deformat:Deformat(message, value)
			if (spellName) then
				self.vars.CMSFLP_SpellName = spellName
				self.vars.CMSFLP_Message = spellFailureMessage
			end
			--if returning ~= nil the foreach will stop.
			return spellName
		end
	)
end

local CHAT_MSG_SPELL_SELF_DAMAGEMESSAGES = {
	IMMUNESPELLSELFOTHER,
	SIMPLECASTSELFOTHER,
	SIMPLEPERFORMSELFOTHER,
	SPELLBLOCKEDSELFOTHER,
	SPELLDODGEDSELFOTHER,
	SPELLEVADEDSELFOTHER,
	SPELLIMMUNESELFOTHER,
	SPELLINTERRUPTSELFOTHER,
	SPELLLOGABSORBSELFOTHER,
	SPELLLOGABSORBSELFSELF,
	SPELLLOGCRITSCHOOLSELFOTHER,
	SPELLLOGCRITSCHOOLSELFSELF,
	SPELLLOGCRITSELFOTHER,
	SPELLLOGSCHOOLSELFOTHER,
	SPELLLOGSCHOOLSELFSELF,
	SPELLLOGSELFOTHER,
	SPELLMISSSELFOTHER,
	SPELLPARRIEDSELFOTHER,
	SPELLREFLECTSELFOTHER,
	SPELLRESISTSELFOTHER,
	SPELLRESISTSELFSELF,
	SPELLTERSEPERFORM_SELF,
	SPELLTERSE_SELF
}

local CHAT_MSG_SPELL_SELF_DAMAGEMESSAGETRAILERS = {
	"",
	ABSORB_TRAILER,
	BLOCK_TRAILER,
	CRUSHING_TRAILER,
	GLANCING_TRAILER,
	RESIST_TRAILER,
	VULNERABLE_TRAILER
}

function ParseCHAT_MSG_SPELL_SELF_DAMAGE(self, message, damageMessage, damageMessageTrailer)
	local spellName = deformat:Deformat(message, damageMessage..damageMessageTrailer)
	--self:LevelDebug(2, "ParseCHAT_MSG_SPELL_SELF_DAMAGE", self.vars.NextMeleeing, self.vars.NextMeleeName, spellName)
	if (spellName and (spellName == self.vars.NextMeleeName)) then
		self.vars.NextMeleeing = false

		self:TriggerEvent(
			"SpellStatus_SpellCastInstant", 
			self.vars.NextMeleeId,
			self.vars.NextMeleeName, 
			self.vars.NextMeleeRank,
			self.vars.NextMeleeFullName
		)
	end
end

function SpellStatus:CHAT_MSG_SPELL_SELF_DAMAGE(message)
	self:LevelDebug(2, "CHAT_MSG_SPELL_SELF_DAMAGE", message)

	if (not self.vars.NextMeleeing) then
		return
	end

	table.foreach(
		CHAT_MSG_SPELL_SELF_DAMAGEMESSAGES, 
		function(_, damageMessage)
			table.foreach(
				CHAT_MSG_SPELL_SELF_DAMAGEMESSAGETRAILERS, 
				function(_, damageMessageTrailer)
					ParseCHAT_MSG_SPELL_SELF_DAMAGE(self, message, damageMessage, damageMessageTrailer)
					if (not self.vars.NextMeleeing) then
						return true
					end
				end
			)
			if (not self.vars.NextMeleeing) then
				return true
			end
		end
	)
end

function SpellStatus:CHAT_MSG_SPELL_AURA_GONE_SELF(message)
	self:LevelDebug(2, "CHAT_MSG_SPELL_AURA_GONE_SELF", message)

	if (not(self.vars.Using or self.vars.Preparing or self.vars.Casting or self.vars.Channeling)) then
		return
	end

	local spellName = deformat:Deformat(message, AURAREMOVEDSELF)
	if ((not spellName) or (spellName ~= self.vars.ActiveName)) then
		return
	end

	self.vars.Using = false
	self.vars.Preparing = false
	self.vars.Casting = false
	self.vars.Channeling = false
	
	self:TriggerEvent(
		"SpellStatus_SpellCastCancelAura", 
		self.vars.ActiveId, self.vars.ActiveName,  self.vars.ActiveRank, 
		self.vars.ActiveFullName or self.vars.ActiveName, GetTime()
	)
end

--Failed is called only when it was semi possible to cast the spell
function SpellStatus:SPELLCAST_INTERRUPTED()
	self:LevelDebug(2, "SPELLCAST_INTERRUPTED")

	TriggerFailureEvent(self, true) 
end

function SpellStatus:SPELLCAST_FAILED()
	self:LevelDebug(2, "SPELLCAST_FAILED")

	TriggerFailureEvent(self, true) 
end

function SpellStatus:START_AUTOREPEAT_SPELL()
	self:LevelDebug(2, "START_AUTOREPEAT_SPELL")

	self.vars.AutoRepeating = true
end

function SpellStatus:STOP_AUTOREPEAT_SPELL()
	self:LevelDebug(2, "STOP_AUTOREPEAT_SPELL")

	self.vars.AutoRepeating = false
end

function SpellStatus:PLAYER_ENTER_COMBAT()
	self:LevelDebug(2, "PLAYER_ENTER_COMBAT")

	self.vars.Attacking = true
end

function SpellStatus:PLAYER_LEAVE_COMBAT()
	self:LevelDebug(2, "PLAYER_LEAVE_COMBAT")

	self.vars.Attacking = false
end

function SpellStatus:PLAYER_REGEN_DISABLED()
	self:LevelDebug(2, "PLAYER_REGEN_DISABLED")

	self.vars.Combating = true
end

function SpellStatus:PLAYER_REGEN_ENABLED()
	self:LevelDebug(2, "PLAYER_REGEN_ENABLED")

	self.vars.Combating = false
end

function SpellStatus:CHAT_MSG_COMBAT_SELF_HITS(chatMessage)
	self:LevelDebug(2, "CHAT_MSG_COMBAT_SELF_HITS", chatMessage)
end

function SpellStatus:CHAT_MSG_COMBAT_SELF_MISSES(chatMessage)
	self:LevelDebug(2, "CHAT_MSG_COMBAT_SELF_MISSES", chatMessage)
end


--
-- Final Registration of the library.
--
AceLibrary:Register(SpellStatus, MAJOR_VERSION, MINOR_VERSION, activate)
SpellStatus = AceLibrary(MAJOR_VERSION)


