--[[
Name: SpellStatusV2-2.0
Revision: $Rev: 15001 $
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 = "SpellStatusV2-2.0"
local MINOR_VERSION = "$Revision: 15001 $"

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 SpellStatusV2 = {}

--Embed all needed mixins into the Library Object SpellStatus
AceLibrary("AceDebug-2.0"):embed(SpellStatusV2)
AceLibrary("AceEvent-2.0"):embed(SpellStatusV2)
AceLibrary("AceHook-2.1"):embed(SpellStatusV2)

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

SpellstatusV2IndexToIcon = {
	[1] = "|cFFF7EF52[Star]|r", --u+2726
	[2] = "|cFFE76100[Circle]|r", --u+25CF
	[3] = "|cFFDE55E7[Diamond]|r", --u+2666
	[4] = "|cFF2BD923[Triangle]|r", --u+25BC
	[5] = "|cFF8FB9D0[Moon]|r", --u+263D
	[6] = "|cFF00B9F3[Square]|r", --u+25A0
	[7] = "|cFFB20A05[X]|r", --u+2716
	[8] = "|cFFF1EFE4[Skull]|r", --u+263B
}

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
	self.vars.ActiveTarget = 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, "SpellStatusV2 - 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 SpellStatusV2: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 SpellStatusV2:IsUsing()
	return self.vars.Using == true
end

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

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

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

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

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

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

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

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

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

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

function SpellStatusV2: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 SpellStatusV2: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 SpellStatusV2:GetActiveSpellName()
	local _, spellName = self:GetActiveSpellData()
	return spellName
end

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

--
-- Function Hooking
--

function SpellStatusV2: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, spellTarget)
	--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.ActiveTarget = spellTarget

	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("SpellStatusV2_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)

	local spellTarget = UnitName("target")
	if spellTarget and GetRaidTargetIndex("target") then
		spellTarget = spellTarget .. " " .. SpellstatusV2IndexToIcon[GetRaidTargetIndex("target")]
	end
	if methodName == "CastSpellByName" and param2 == true then
		spellTarget = UnitName("player")
	end
	if methodName == "UseAction" and param3 == true then
		spellTarget = UnitName("player")
	end
	if spellTarget == nil then
		spellTarget = "none"
	end

	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, spellTarget)
	end

	ResetCastOriginal(self, nil, nil, nil, nil)

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

function SpellStatusV2: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 SpellStatusV2: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 SpellStatusV2: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 SpellStatusV2: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 SpellStatusV2: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 SpellStatusV2: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 SpellStatusV2: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 SpellStatusV2: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 SpellStatusV2: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(
			"SpellStatusV2_SpellCastCastingStart",
			self.vars.ActiveId,
			self.vars.ActiveName,
			self.vars.ActiveRank,
			self.vars.ActiveFullName,
			self.vars.ActiveCastStartTime,
			self.vars.ActiveCastStopTime,
			self.vars.ActiveCastDuration,
			self.vars.ActiveTarget
	)
end

function SpellStatusV2: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(
			"SpellStatusV2_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 SpellStatusV2: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 "SpellStatusV2_SpellCastInstant" or "SpellStatusV2_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,
				self.vars.ActiveTarget
		)
		--We cant reset because maybe we have a failure!!!
		--ResetActiveVariables(self)
	end
end

function SpellStatusV2: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(
			"SpellStatusV2_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 SpellStatusV2: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(
			"SpellStatusV2_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 SpellStatusV2: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(
			"SpellStatusV2_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 SpellStatusV2:UI_ERROR_MESSAGE(message)
	self:LevelDebug(2, "UI_ERROR_MESSAGE", message)

	self.vars.UIEM_Message = message
end

local FAILUREMESSAGE = {
	SPELLFAILCASTSELF,
	SPELLFAILPERFORMSELF
}

function SpellStatusV2: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(
				"SpellStatusV2_SpellCastInstant",
				self.vars.NextMeleeId,
				self.vars.NextMeleeName,
				self.vars.NextMeleeRank,
				self.vars.NextMeleeFullName
		)
	end
end

function SpellStatusV2: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 SpellStatusV2: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(
			"SpellStatusV2_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 SpellStatusV2:SPELLCAST_INTERRUPTED()
	self:LevelDebug(2, "SPELLCAST_INTERRUPTED")

	TriggerFailureEvent(self, true)
end

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

	TriggerFailureEvent(self, true)
end

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

	self.vars.AutoRepeating = true
end

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

	self.vars.AutoRepeating = false
end

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

	self.vars.Attacking = true
end

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

	self.vars.Attacking = false
end

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

	self.vars.Combating = true
end

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

	self.vars.Combating = false
end

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

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


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


