--[[ Element: Cast Bar
	
	THIS FILE HEAVILY MODIFIED FOR USE WITH SUPERVILLAIN UI

]]
--GLOBAL NAMESPACE
local _G = _G;
--LUA
local unpack        = _G.unpack;
local select        = _G.select;
local assert        = _G.assert;
local error         = _G.error;
local print         = _G.print;
local pairs         = _G.pairs;
local next          = _G.next;
local tostring      = _G.tostring;
local setmetatable  = _G.setmetatable;
--STRING
local string        = _G.string;
local format        = string.format;
--MATH
local math          = _G.math;
local floor         = math.floor
local ceil          = math.ceil
--BLIZZARD API
local GetTime       	= _G.GetTime;
local CreateFrame       = _G.CreateFrame;
local GetNetStats       = _G.GetNetStats;
local UnitCastingInfo   = _G.UnitCastingInfo;
local UnitChannelInfo   = _G.UnitChannelInfo;
--local GetTradeskillRepeatCount  = _G.GetTradeskillRepeatCount;
local CastingBarFrame   	= _G.CastingBarFrame;
local PetCastingBarFrame   	= _G.PetCastingBarFrame;

local parent, ns = ...
local oUF = ns.oUF

local updateSafeZone = function(self)
	local sz = self.SafeZone
	local width = self:GetWidth()
	local _, _, _, ms = GetNetStats()

	-- Guard against GetNetStats returning latencies of 0.
	if(ms ~= 0) then
		-- MADNESS!
		local safeZonePercent = (width / self.max) * (ms / 1e5)
		if(safeZonePercent > 1) then safeZonePercent = 1 end
		sz:SetWidth(width * safeZonePercent)
		sz:Show()
	else
		sz:Hide()
	end
end

local UNIT_SPELLCAST_SENT = function (self, event, unit, spell, rank, target)
	local castbar = self.Castbar
	castbar.curTarget = (target and target ~= "") and target or nil
end

local UNIT_SPELLCAST_START = function(self, event, unit, spell)
	if(self.unit ~= unit) or not unit then return end

	local castbar = self.Castbar
	local name, _, text, texture, startTime, endTime, tradeskill, castid, interrupt = UnitCastingInfo(unit)
	if(not name) then
		castbar:Hide()
		return
	end

	endTime = endTime / 1e3
	startTime = startTime / 1e3

	local repeatCount = 1
	local start = GetTime() - startTime

	if(tradeskill and repeatCount >= 1) then
		if(castbar.previous ~= name) then
			castbar.recipecount = 1
			castbar.maxrecipe = repeatCount
			castbar.duration = start
		else
			castbar.recipecount = castbar.recipecount or 1
			castbar.maxrecipe = castbar.maxrecipe or repeatCount
			castbar.duration = castbar.duration or start
		end
	else
		castbar.recipecount = nil
		castbar.maxrecipe = 1
		castbar.duration = start
	end

	castbar.previous = name
	castbar.tradeskill = tradeskill
	castbar.castid = castid

	local max = (endTime - startTime) * castbar.maxrecipe

	castbar.max = max
	castbar.delay = 0
	castbar.casting = true
	castbar.interrupt = interrupt

	castbar:SetMinMaxValues(0, max)
	castbar:SetValue(0)

	if(castbar.Text) then castbar.Text:SetText(text) end
	if(castbar.Icon) then castbar.Icon:SetTexture(texture) end
	if(castbar.Time) then castbar.Time:SetText() end

	local shield = castbar.Shield
	if(shield and interrupt) then
		shield:Show()
	elseif(shield) then
		shield:Hide()
	end

	local sf = castbar.SafeZone
	if(sf) then
		sf:ClearAllPoints()
		sf:SetPoint'RIGHT'
		sf:SetPoint'TOP'
		sf:SetPoint'BOTTOM'
		updateSafeZone(castbar)
	end

	if(castbar.PostCastStart) then
		castbar:PostCastStart(unit, name, castid)
	end

	castbar:Show()
end

local UNIT_SPELLCAST_FAILED = function(self, event, unit, spellname, _, castid)
	if (self.unit ~= unit) or not unit then return end

	local castbar = self.Castbar
	if (castbar.castid ~= castid) then	return end

	castbar.previous = nil
	castbar.casting = nil
	castbar.tradeskill = nil
	castbar.recipecount = nil
	castbar.maxrecipe = 1
	castbar.interrupt = nil
	castbar:SetValue(0)
	castbar:Hide()

	if(castbar.PostCastFailed) then
		return castbar:PostCastFailed(unit, spellname, castid)
	end
end

local UNIT_SPELLCAST_INTERRUPTED = function(self, event, unit, spellname, _, castid)
	if(self.unit ~= unit) or not unit then return end

	local castbar = self.Castbar
	if (castbar.castid ~= castid) then	return end

	castbar.previous = nil
	castbar.casting = nil
	castbar.tradeskill = nil
	castbar.recipecount = nil
	castbar.maxrecipe = 1
	castbar.channeling = nil

	castbar:SetValue(0)
	castbar:Hide()

	if(castbar.PostCastInterrupted) then
		return castbar:PostCastInterrupted(unit, spellname, castid)
	end
end

local UNIT_SPELLCAST_INTERRUPTIBLE = function(self, event, unit)
	if(self.unit ~= unit) or not unit then return end

	local shield = self.Castbar.Shield
	if(shield) then
		shield:Hide()
	end

	local castbar = self.Castbar
	if(castbar.PostCastInterruptible) then
		return castbar:PostCastInterruptible(unit)
	end
end

local UNIT_SPELLCAST_NOT_INTERRUPTIBLE = function(self, event, unit)
	if(self.unit ~= unit) or not unit then return end

	local shield = self.Castbar.Shield
	if(shield) then
		shield:Show()
	end

	local castbar = self.Castbar
	if(castbar.PostCastNotInterruptible) then
		return castbar:PostCastNotInterruptible(unit)
	end
end

local UNIT_SPELLCAST_DELAYED = function(self, event, unit, spellname, _, castid)
	if(self.unit ~= unit) or not unit then return end

	local castbar = self.Castbar
	local name, _, text, texture, startTime, endTime = UnitCastingInfo(unit)
	if(not startTime or not castbar:IsShown()) then return end

	local duration = GetTime() - (startTime / 1000)
	if(duration < 0) then duration = 0 end
	castbar.previous = name
	castbar.delay = castbar.delay + castbar.duration - duration
	castbar.duration = duration

	castbar:SetValue(duration)

	if(castbar.PostCastDelayed) then
		return castbar:PostCastDelayed(unit, name, castid)
	end
end

local UNIT_SPELLCAST_STOP = function(self, event, unit, spellname, _, castid)
	if (self.unit ~= unit) or not unit then return end
	local castbar = self.Castbar
	if (castbar.castid ~= castid) then return end

	if(castbar.tradeskill and castbar.recipecount and castbar.recipecount >= 0) then
		castbar.recipecount = castbar.recipecount + 1
	else
		castbar.previous = nil
		castbar.casting = nil
		castbar.interrupt = nil
		castbar.tradeskill = nil
		castbar.recipecount = nil
		castbar.maxrecipe = 1
		castbar:SetValue(0)
	end

	if((not castbar.recipecount) or (castbar.recipecount and castbar.recipecount < 2)) then
		castbar:Hide()
	end

	if(castbar.PostCastStop) then
		return castbar:PostCastStop(unit, spellname, castid)
	end
end

local UNIT_SPELLCAST_CHANNEL_START = function(self, event, unit, spellname)
	if (self.unit ~= unit) or not unit then return end

	local castbar = self.Castbar
	local name, _, text, texture, startTime, endTime, isTrade, interrupt = UnitChannelInfo(unit)
	if (not name) then return end

	endTime = endTime / 1e3
	startTime = startTime / 1e3
	local max = (endTime - startTime)
	local duration = endTime - GetTime()

	castbar.previous = name
	castbar.duration = duration
	castbar.max = max
	castbar.delay = 0
	castbar.startTime = startTime
	castbar.endTime = endTime
	castbar.extraTickRatio = 0
	castbar.channeling = true
	castbar.interrupt = interrupt

	-- We have to do this, as it's possible for spell casts to never have _STOP
	-- executed or be fully completed by the OnUpdate handler before CHANNEL_START
	-- is called.
	castbar.casting = nil
	castbar.tradeskill = nil
	castbar.recipecount = nil
	castbar.maxrecipe = 1
	castbar.castid = nil

	castbar:SetMinMaxValues(0, max)
	castbar:SetValue(duration)

	if(castbar.Text) then castbar.Text:SetText(name) end
	if(castbar.Icon) then castbar.Icon:SetTexture(texture) end
	if(castbar.Time) then castbar.Time:SetText() end

	local shield = castbar.Shield
	if(shield and interrupt) then
		shield:Show()
	elseif(shield) then
		shield:Hide()
	end

	local sf = castbar.SafeZone
	if(sf) then
		sf:ClearAllPoints()
		sf:SetPoint'LEFT'
		sf:SetPoint'TOP'
		sf:SetPoint'BOTTOM'
		updateSafeZone(castbar)
	end

	if(castbar.PostChannelStart) then castbar:PostChannelStart(unit, name) end
	castbar:Show()
end

local UNIT_SPELLCAST_CHANNEL_UPDATE = function(self, event, unit, spellname)
	if(self.unit ~= unit) or not unit then return end

	local castbar = self.Castbar
	local name, _, text, texture, startTime, endTime, oldStart = UnitChannelInfo(unit)
	if(not name or not castbar:IsShown()) then
		return
	end

	castbar.previous = name
	local duration = (endTime / 1000) - GetTime()
	local startDelay = castbar.startTime - startTime / 1000
	castbar.startTime = startTime / 1000
	castbar.endTime = endTime / 1000
	castbar.delay = castbar.delay + startDelay
	
	castbar.duration = duration
	castbar.max = (endTime - startTime) / 1000

	castbar:SetMinMaxValues(0, castbar.max)
	castbar:SetValue(duration)

	if(castbar.PostChannelUpdate) then
		return castbar:PostChannelUpdate(unit, name)
	end
end

local UNIT_SPELLCAST_CHANNEL_STOP = function(self, event, unit, spellname)
	if(self.unit ~= unit) or not unit then return end

	local castbar = self.Castbar
	if(castbar:IsShown()) then
		castbar.channeling = nil
		castbar.interrupt = nil

		castbar:SetValue(castbar.max)
		castbar:Hide()

		if(castbar.PostChannelStop) then
			return castbar:PostChannelStop(unit, spellname)
		end
	end
end

local UpdateCastingTimeInfo = function(self, duration)
	if(self.Time) then
		if(self.delay ~= 0) then
			if(self.CustomDelayText) then
				self:CustomDelayText(duration)
			else
				self.Time:SetFormattedText("%.1f|cffff0000-%.1f|r", duration, self.delay)
			end
		elseif(self.recipecount and self.recipecount > 0 and self.maxrecipe and self.maxrecipe > 1) then
			self.Time:SetText(self.recipecount .. "/" .. self.maxrecipe)
		else
			if(self.CustomTimeText) then
				self:CustomTimeText(duration)
			else
				self.Time:SetFormattedText("%.1f", duration)
			end
		end
	end
	if(self.Spark) then
		self.Spark:SetPoint("CENTER", self, "LEFT", (duration / self.max) * self:GetWidth(), 0)
	end
end

local onUpdate = function(self, elapsed)
	self.lastUpdate = (self.lastUpdate or 0) + elapsed

	if not (self.casting or self.channeling) then
		self.unitName = nil
		self.previous = nil
		self.casting = nil
		self.tradeskill = nil
		self.recipecount = nil
		self.maxrecipe = 1
		self.castid = nil
		self.channeling = nil

		self:SetValue(1)
		self:Hide()
		return
	end

	if(self.casting) then
		local duration = self.duration + self.lastUpdate
		if(duration >= self.max) then
			self.previous = nil
			self.casting = nil
			self.tradeskill = nil
			self.recipecount = nil
			self.maxrecipe = 1
			self:Hide()

			if(self.PostCastStop) then self:PostCastStop(self.__owner.unit) end
			return
		end

		UpdateCastingTimeInfo(self, duration)

		self.duration = duration
		self:SetValue(duration)
	elseif(self.channeling) then
		local duration = self.duration - self.lastUpdate

		if(duration <= 0) then
			self.channeling = nil
			self:Hide()

			if(self.PostChannelStop) then self:PostChannelStop(self.__owner.unit) end
			return
		end

		UpdateCastingTimeInfo(self, duration)

		self.duration = duration
		self:SetValue(duration)
	end
	
	self.lastUpdate = 0
end

local Update = function(self, ...)
	UNIT_SPELLCAST_START(self, ...)
	return UNIT_SPELLCAST_CHANNEL_START(self, ...)
end

local ForceUpdate = function(element)
	return Update(element.__owner, 'ForceUpdate', element.__owner.unit)
end

local Enable = function(object, unit)
	local castbar = object.Castbar

	if(castbar) then
		castbar.__owner = object
		castbar.ForceUpdate = ForceUpdate

		if(not (unit and unit:match'%wtarget$')) then
			object:RegisterEvent("UNIT_SPELLCAST_SENT", UNIT_SPELLCAST_SENT)
			object:RegisterEvent("UNIT_SPELLCAST_START", UNIT_SPELLCAST_START)
			object:RegisterEvent("UNIT_SPELLCAST_FAILED", UNIT_SPELLCAST_FAILED)
			object:RegisterEvent("UNIT_SPELLCAST_STOP", UNIT_SPELLCAST_STOP)
			object:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED", UNIT_SPELLCAST_INTERRUPTED)
			object:RegisterEvent("UNIT_SPELLCAST_INTERRUPTIBLE", UNIT_SPELLCAST_INTERRUPTIBLE)
			object:RegisterEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", UNIT_SPELLCAST_NOT_INTERRUPTIBLE)
			object:RegisterEvent("UNIT_SPELLCAST_DELAYED", UNIT_SPELLCAST_DELAYED)
			object:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START", UNIT_SPELLCAST_CHANNEL_START)
			object:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE", UNIT_SPELLCAST_CHANNEL_UPDATE)
			object:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP", UNIT_SPELLCAST_CHANNEL_STOP)
			--object:RegisterEvent("UPDATE_TRADESKILL_RECAST", UPDATE_TRADESKILL_RECAST)
		end

		castbar:SetScript("OnUpdate", castbar.OnUpdate or onUpdate)

		if(object.unit == "player") then
			CastingBarFrame:UnregisterAllEvents()
			CastingBarFrame.Show = CastingBarFrame.Hide
			CastingBarFrame:Hide()
		elseif(object.unit == 'pet') then
			PetCastingBarFrame:UnregisterAllEvents()
			PetCastingBarFrame.Show = PetCastingBarFrame.Hide
			PetCastingBarFrame:Hide()
		end

		if(castbar:IsObjectType'StatusBar' and not castbar:GetStatusBarTexture()) then
			castbar:SetStatusBarTexture[[Interface\TargetingFrame\UI-StatusBar]]
		end

		local spark = castbar.Spark
		if(spark and spark:IsObjectType'Texture' and not spark:GetTexture()) then
			spark:SetTexture[[Interface\CastingBar\UI-CastingBar-Spark]]
		end

		local shield = castbar.Shield
		if(shield and shield:IsObjectType'Texture' and not shield:GetTexture()) then
			shield:SetTexture[[Interface\CastingBar\UI-CastingBar-Small-Shield]]
		end

		local sz = castbar.SafeZone
		if(sz and sz:IsObjectType'Texture' and not sz:GetTexture()) then
			sz:SetColorTexture(1, 0, 0)
		end

		castbar:Hide()

		return true
	end
end

local Disable = function(object, unit)
	local castbar = object.Castbar

	if(castbar) then
		object:UnregisterEvent("UNIT_SPELLCAST_SENT", UNIT_SPELLCAST_SENT)
		object:UnregisterEvent("UNIT_SPELLCAST_START", UNIT_SPELLCAST_START)
		object:UnregisterEvent("UNIT_SPELLCAST_FAILED", UNIT_SPELLCAST_FAILED)
		object:UnregisterEvent("UNIT_SPELLCAST_STOP", UNIT_SPELLCAST_STOP)
		object:UnregisterEvent("UNIT_SPELLCAST_INTERRUPTED", UNIT_SPELLCAST_INTERRUPTED)
		object:UnregisterEvent("UNIT_SPELLCAST_INTERRUPTIBLE", UNIT_SPELLCAST_INTERRUPTIBLE)
		object:UnregisterEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", UNIT_SPELLCAST_NOT_INTERRUPTIBLE)
		object:UnregisterEvent("UNIT_SPELLCAST_DELAYED", UNIT_SPELLCAST_DELAYED)
		object:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_START", UNIT_SPELLCAST_CHANNEL_START)
		object:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE", UNIT_SPELLCAST_CHANNEL_UPDATE)
		object:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_STOP", UNIT_SPELLCAST_CHANNEL_STOP)
		--object:UnregisterEvent("UPDATE_TRADESKILL_RECAST", UPDATE_TRADESKILL_RECAST)

		castbar:SetScript("OnUpdate", nil)
	end
end

oUF:AddElement('Castbar', Update, Enable, Disable)
