--<< ====================================================================== >>--
-- Module Setup                                                               --
--<< ====================================================================== >>--
AceTimerCast = AceTimer:AddModule()

function AceTimerCast:Enable()
	self.captive    = {}
	self.active     = {}
	self.rank_cache = {}
	self.iscasting  = FALSE
	self:Hook("UseAction")
	self:Hook("CastSpell")
	self:Hook("CastSpellByName")
	self:Hook("SpellTargetUnit")
	self:Hook("TargetUnit")
	self:Hook("SpellStopTargeting")
	self:Hook("SpellStopCasting")
	self:HookScript(WorldFrame, "OnMouseDown")
	self:RegisterEvent("SPELLCAST_FAILED")
	self:RegisterEvent("SPELLCAST_INTERRUPTED")
	self:RegisterEvent("SPELLCAST_START")
	self:RegisterEvent("SPELLCAST_STOP")
	self:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
	self:RegisterEvent("LEARNED_SPELL_IN_TAB")
	self:RegisterEvent("PLAYER_DEAD")
end

function AceTimerCast:Disable()
	if self.captive    then self.captive    = nil end
	if self.active     then self.active     = nil end
	if self.iscasting  then self.iscasting  = nil end
	if self.rank_cache then self.rank_cache = nil end
	self:UnregisterAllEvents(self)
	self:UnhookAllScripts()
end

--<< ====================================================================== >>--
-- Helpers                                                                    --
--<< ====================================================================== >>--
function AceTimerCast:LEARNED_SPELL_IN_TAB()
	for k in self.rank_cache do
		self.rank_cache[k] = nil 
	end
end

function AceTimerCast:PLAYER_DEAD()
	self.active.t = nil; self.active.n = nil; self.active.u = nil; self.active.r = nil
	self.captive.t = nil; self.captive.n = nil; self.captive.u = nil; self.captive.r = nil
	self.iscasting = FALSE
	self:KillBars()
end

--<< ====================================================================== >>--
-- Catch Spellcast                                                            --
--<< ====================================================================== >>--
function AceTimerCast:UseAction(slot, clicked, onself)
	if not self.iscasting then
		if self.captive.n then 
			self.captive.t = nil; self.captive.n = nil; self.captive.u = nil; self.captive.r = nil 
		end
		if not GetActionText(slot) and HasAction(slot) then 
			AceTimerTooltipTextRight1:SetText("")
			AceTimerTooltip:SetAction(slot)
			local name = AceTimerTooltipTextLeft1:GetText()
			local timer = self.timers[ACETIMER.SPELL][name]
			if timer then 
				local rank = AceTimerTooltipTextRight1:GetText()
				if not rank then rank = 0 end
				self:CatchSpellcast(timer, name, rank, onself)
			end
		end
	end
	return self:CallHook("UseAction", slot, clicked, onself)
end

function AceTimerCast:CastSpell(index, booktype)
	if not self.iscasting then
		if self.captive.n then 
			self.captive.t = nil; self.captive.n = nil; self.captive.u = nil; self.captive.r = nil 
		end
		local name, rank = GetSpellName(index, booktype)
		local timer = self.timers[ACETIMER.SPELL][name]
		if timer then 
			if not rank or rank == "" then rank = 0 end
			self:CatchSpellcast(timer, name, rank)
		end
	end
	return self:CallHook("CastSpell", index, booktype)
end

function AceTimerCast:CastSpellByName(text, onself)
	if not self.iscasting then
		if self.captive.n then 
			self.captive.t = nil; self.captive.n = nil; self.captive.u = nil; self.captive.r = nil 
		end
		local _,_, name, rank = string.find(text, "^(.+)%("..ACETIMER.PAT_RANK.."%)%s*$")
		if not name then _,_, name = string.find(text, "^(.+)%(%)%s*$") end
		if not name then name = text end
		local timer = self.timers[ACETIMER.SPELL][name]
		if timer then 
			self:CatchSpellcast(timer, name, rank and tonumber(rank), oneself)
		end
	end
	return self:CallHook("CastSpellByName", text, onself)
end

function AceTimerCast:CatchSpellcast(timer, name, rank, onself)
	if not rank then 
		if self.rank_cache[name] then rank = self.rank_cache[name]
		else
			local i, sn, sr = 1
			while true do sn, sr = GetSpellName(i, BOOKTYPE_SPELL)
				if not sn then break 
				elseif sn == name then rank = sr
				elseif rank then break 
				end
				i = i + 1
			end
			if not rank or rank == "" then rank = 0 end
			self.rank_cache[name] = rank
		end
	end
	if type(rank) == "string" then
		local _,_, found = string.find(rank, ACETIMER.PAT_RANK)
		rank = found and tonumber(found) or 0
	end
	
	local unit
	if timer.k.t then
		if timer.k.s then 
			if onself and onself == 1 then unit = UnitName("player")
			elseif UnitExists("target") then
				if timer.k.g then 
					if UnitIsFriend("player", "target") then unit = UnitName("target") end
				else
					if UnitCanAttack("player", "target") then unit = UnitName("target") end
				end
			end
		else
			if UnitExists("target") then unit = UnitName("target") else return end
		end
	else
		unit = "none"
	end
	self.captive.t = timer; self.captive.n = name; self.captive.u = unit; self.captive.r = rank
end

--<< ====================================================================== >>--
-- Catch Spellcast Target                                                     --
--<< ====================================================================== >>--
function AceTimerCast:SpellTargetUnit(unit)
	if self.captive.n and not self.captive.u then 
		self.captive.u = UnitName(unit) 
	end
   	return self:CallHook("SpellTargetUnit", unit)
end

function AceTimerCast:TargetUnit(unit)
	if self.captive.n and not self.captive.u then 
		self.captive.u = UnitName(unit) 
	end
	return self:CallHook("TargetUnit", unit)
end

function AceTimerCast:OnMouseDown()
	if self.captive.n and not self.captive.u and arg1 == "LeftButton" and UnitExists("mouseover") then 
		self.captive.u = UnitName("mouseover") 
	end
	return self:CallScript(WorldFrame, "OnMouseDown", arg1)
end

--<< ====================================================================== >>--
-- Complete Spellcast                                                         --
--<< ====================================================================== >>--
function AceTimerCast:SPELLCAST_START()
	self.iscasting = TRUE
end

function AceTimerCast:SPELLCAST_STOP()
	self.iscasting = FALSE
	if self.captive.n then
		if self.captive.u == "none" then 
			self:StartTimer(self.captive.t, self.captive.n, self.captive.u, self.captive.r)
		else
			self.active.t = self.captive.t; self.active.n = self.captive.n; self.active.u = self.captive.u; self.active.r = self.captive.r
			Timex:AddSchedule("AceTimerCast", 0.5, nil, nil, self.CompleteCast, self)
		end
		self.captive.t = nil; self.captive.n = nil; self.captive.u = nil; self.captive.r = nil 
	end
end

function AceTimerCast:CompleteCast()
	if self.active.n then
		self:StartTimer(self.active.t, self.active.n, self.active.u, self.active.r)
		self.active.t = nil; self.active.n = nil; self.active.u = nil; self.active.r = nil
	end
end

--<< ====================================================================== >>--
-- Drop Spellcast                                                             --
--<< ====================================================================== >>--
function AceTimerCast:SpellStopCasting()
	if self.captive.n then
		self.captive.t = nil; self.captive.n = nil; self.captive.u = nil; self.captive.r = nil 
	end
	return self:CallHook("SpellStopCasting")
end

function AceTimerCast:SpellStopTargeting()
	if self.captive.n and SpellIsTargeting() then 
		self.captive.t = nil; self.captive.n = nil; self.captive.u = nil; self.captive.r = nil 
	end
	return self:CallHook("SpellStopTargeting")
end

function AceTimerCast:SPELLCAST_FAILED()
	if self.captive.n then
		self.captive.t = nil; self.captive.n = nil; self.captive.u = nil; self.captive.r = nil 
	end
	self.iscasting = FALSE
end

function AceTimerCast:SPELLCAST_INTERRUPTED()
	if self.active.n then 
		self.active.t = nil; self.active.n = nil; self.active.u = nil; self.active.r = nil
	end
end

function AceTimerCast:CHAT_MSG_SPELL_SELF_DAMAGE()
	if not self.active.n then return end
	if self:FindGlobal(arg1, SPELLDODGEDSELFOTHER)    -- "Your %s was dodged by %s."
	or self:FindGlobal(arg1, SPELLPARRIEDSELFOTHER)   -- "Your %s is parried by %s."
	or self:FindGlobal(arg1, SPELLEVADEDSELFOTHER)    -- "Your %s was evaded by %s."
	or self:FindGlobal(arg1, SPELLMISSSELFOTHER)      -- "Your %s missed %s."
	or self:FindGlobal(arg1, SPELLBLOCKEDSELFOTHER)   -- "Your %s was blocked by %s."
	or self:FindGlobal(arg1, SPELLRESISTSELFOTHER)    -- "Your %s was resisted by %s."
	or self:FindGlobal(arg1, SPELLIMMUNESELFOTHER)    -- "Your %s failed. %s is immune."
	or self:FindGlobal(arg1, SPELLLOGABSORBSELFOTHER) -- "Your %s is absorbed by %s."
	or self:FindGlobal(arg1, SPELLREFLECTSELFOTHER)   -- "Your %s is reflected back by %s."
	or self:FindGlobal(arg1, SPELLDEFLECTEDSELFOTHER) -- "Your %s was deflected by %s."
	then
		if self.gs_captured[1] == self.active.n and self.gs_captured[2] == self.active.u then 
			self.active.t = nil; self.active.n = nil; self.active.u = nil; self.active.r = nil
		end
	end
end
