local L = LibStub("AceLocale-3.0"):GetLocale("IceHUD", false)
IceCastBar = IceCore_CreateClass(IceBarElement)

local IceHUD = _G.IceHUD

IceCastBar.Actions = { None = "NONE", Cast = "CAST", Channel = "CHANNEL", Instant = "INSTANT", Success = "SUCCESS", Failure = "FAILURE", ReverseChannel = "REVERSECHANNEL" }

IceCastBar.prototype.action = nil
IceCastBar.prototype.actionStartTime = nil
IceCastBar.prototype.actionDuration = nil
IceCastBar.prototype.actionMessage = nil
IceCastBar.prototype.unit = nil
IceCastBar.prototype.current = nil

local SPELL_POWER_MANA = SPELL_POWER_MANA
if Enum and Enum.PowerType then
	SPELL_POWER_MANA = Enum.PowerType.Mana
end

local UnitCastingInfo, UnitChannelInfo = UnitCastingInfo, UnitChannelInfo
if not UnitCastingInfo then
	UnitCastingInfo = CastingInfo
	UnitChannelInfo = ChannelInfo
end

local GetSpellName = GetSpellInfo
if C_Spell and C_Spell.GetSpellName then
	GetSpellName = C_Spell.GetSpellName
end

local GetSpellInfo = GetSpellInfo
if not GetSpellInfo and C_Spell and C_Spell.GetSpellInfo then
	GetSpellInfo = function(spellID)
		if not spellID then
			return nil
		end

		local spellInfo = C_Spell.GetSpellInfo(spellID)
		if spellInfo then
			return spellInfo.name, nil, spellInfo.iconID, spellInfo.castTime, spellInfo.minRange, spellInfo.maxRange, spellInfo.spellID, spellInfo.originalIconID
		end
	end
end

-- Fulzamoth 2019-09-27 : Use LibClassicCasterino if it's there so we can use TargetCast 
--                        module in Classic WoW
if IceHUD.WowClassic then
	LibClassicCasterino = LibStub("LibClassicCasterino", true)
	if LibClassicCasterino then
		UnitCastingInfo = function(unit)
			return LibClassicCasterino:UnitCastingInfo(unit)
		end
		UnitChannelInfo = function(unit)
			return LibClassicCasterino:UnitChannelInfo(unit)
		end
	end
end
-- end Fulzamoth change

local AuraIconWidth = 20
local AuraIconHeight = 20

-- Constructor --
function IceCastBar.prototype:init(name)
	IceCastBar.super.prototype.init(self, name)

	self:SetDefaultColor("CastCasting", 242, 242, 10)
	self:SetDefaultColor("CastChanneling", 242, 242, 10)
	self:SetDefaultColor("CastSuccess", 242, 242, 70)
	self:SetDefaultColor("CastFail", 1, 0, 0)
	if GetUnitEmpowerMinHoldTime then
		self:SetDefaultColor("EmpowerStage0", 165, 165, 165)
		self:SetDefaultColor("EmpowerStage1", 242, 42, 10)
		self:SetDefaultColor("EmpowerStage2", 242, 142, 10)
		self:SetDefaultColor("EmpowerStage3", 242, 242, 10)
		self:SetDefaultColor("EmpowerStage4", 242, 242, 242)
	end
	self.unit = "player"

	self.delay = 0
	self.action = IceCastBar.Actions.None
end


-- 'Public' methods -----------------------------------------------------------

function IceCastBar.prototype:Enable(core)
	IceCastBar.super.prototype.Enable(self, core)

	-- Fulzamoth 2019-09-27 : LibClassicCasterino support
	--                        Setup callback to the library, and route events to
	--                        IceHUD's handler functions.
	if LibClassicCasterino then
			local CastbarEventHandler = function(event, ...) -- unitTarget, castGUID, spellID
				if (event == "UNIT_SPELLCAST_START") then
					return IceCastBar.prototype.SpellCastStart(self, event, ...)
				elseif (event == "UNIT_SPELLCAST_DELAYED") then
					return IceCastBar.prototype.SpellCastDelayed(self, event, ...)
				elseif (event == "UNIT_SPELLCAST_STOP") then
					return IceCastBar.prototype.SpellCastStop(self, event, ...)
				elseif (event == "UNIT_SPELLCAST_FAILED") then
					return IceCastBar.prototype.SpellCastFailed(self, event, ...)
				elseif (event == "UNIT_SPELLCAST_INTERRUPTED") then
					return IceCastBar.prototype.SpellCastInterrupted(self, event, ...)
				elseif (event == "UNIT_SPELLCAST_CHANNEL_START") then
					return IceCastBar.prototype.SpellCastChannelStart(self, event, ...)
				elseif (event == "UNIT_SPELLCAST_CHANNEL_UPDATE") then
					return IceCastBar.prototype.SpellCastChannelUpdate(self, event, ...)
				elseif (event == "UNIT_SPELLCAST_CHANNEL_STOP") then
					return IceCastBar.prototype.SpellCastChannelStop(self, event, ...)
				end
			end
			LibClassicCasterino.RegisterCallback(self,"UNIT_SPELLCAST_START", CastbarEventHandler) 
			LibClassicCasterino.RegisterCallback(self,"UNIT_SPELLCAST_DELAYED", CastbarEventHandler) -- only for player
			LibClassicCasterino.RegisterCallback(self,"UNIT_SPELLCAST_STOP", CastbarEventHandler)
			LibClassicCasterino.RegisterCallback(self,"UNIT_SPELLCAST_FAILED", CastbarEventHandler)
			LibClassicCasterino.RegisterCallback(self,"UNIT_SPELLCAST_INTERRUPTED", CastbarEventHandler)
			LibClassicCasterino.RegisterCallback(self,"UNIT_SPELLCAST_CHANNEL_START", CastbarEventHandler)
			LibClassicCasterino.RegisterCallback(self,"UNIT_SPELLCAST_CHANNEL_UPDATE", CastbarEventHandler) -- only for player
			LibClassicCasterino.RegisterCallback(self,"UNIT_SPELLCAST_CHANNEL_STOP", CastbarEventHandler)
	else -- No LibClassicCasterino, or we're not on Classic, so use IceHUD's normal event handlers.

		self:RegisterEvent("UNIT_SPELLCAST_SENT", "SpellCastSent") -- "player", spell, rank, target
		self:RegisterEvent("CURRENT_SPELL_CAST_CHANGED", "SpellCastChanged")
		self:RegisterEvent("UNIT_SPELLCAST_START", "SpellCastStart") -- unit, spell, rank
		self:RegisterEvent("UNIT_SPELLCAST_STOP", "SpellCastStop") -- unit, spell, rank

		self:RegisterEvent("UNIT_SPELLCAST_FAILED", "SpellCastFailed") -- unit, spell, rank
		self:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED", "SpellCastInterrupted") -- unit, spell, rank

		self:RegisterEvent("UNIT_SPELLCAST_DELAYED", "SpellCastDelayed") -- unit, spell, rank
		self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED", "SpellCastSucceeded") -- "player", spell, rank

		self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START", "SpellCastChannelStart") -- unit, spell, rank
		self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE", "SpellCastChannelUpdate") -- unit, spell, rank
		self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP", "SpellCastChannelStop") -- unit, spell, rank

		if GetUnitEmpowerHoldAtMaxTime then
			self:RegisterEvent("UNIT_SPELLCAST_EMPOWER_START", "SpellCastChannelStart")
			self:RegisterEvent("UNIT_SPELLCAST_EMPOWER_UPDATE", "SpellCastChannelUpdate")
			self:RegisterEvent("UNIT_SPELLCAST_EMPOWER_STOP", "SpellCastEmpowerStop")
		end

	end
	self:Show(false)
end

function IceCastBar.prototype:GetDefaultSettings()
	local settings = IceCastBar.super.prototype.GetDefaultSettings(self)

	settings["showSpellRank"] = true
	settings["showCastTime"] = true
	settings["displayAuraIcon"] = false
	settings["auraIconXOffset"] = 40
	settings["auraIconYOffset"] = 0
	settings["auraIconScale"] = 1
	settings["reverseChannel"] = true

	return settings
end

function IceCastBar.prototype:GetOptions()
	local opts = IceCastBar.super.prototype.GetOptions(self)

	opts["showCastTime"] =
	{
		type = 'toggle',
		name = L["Show spell cast time"],
		desc = L["Whether or not to show the remaining cast time of a spell being cast."],
		get = function()
			return self.moduleSettings.showCastTime
		end,
		set = function(info, value)
			self.moduleSettings.showCastTime = value
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		order = 39.998
	}
if IceHUD.SpellFunctionsReturnRank then
	opts["showSpellRank"] =
	{
		type = 'toggle',
		name = L["Show spell rank"],
		desc = L["Whether or not to show the rank of a spell being cast."],
		get = function()
			return self.moduleSettings.showSpellRank
		end,
		set = function(info, value)
			self.moduleSettings.showSpellRank = value
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		order = 39.999
	}
end
	opts["iconSettings"] = {
		type = 'group',
		name = "|c"..self.configColor..L["Icon Settings"].."|r",
		args = {
			displayAuraIcon = {
				type = 'toggle',
				name = L["Display aura icon"],
				desc = L["Whether or not to display an icon for the aura that this bar is tracking"],
				get = function()
					return self.moduleSettings.displayAuraIcon
				end,
				set = function(info, v)
					self.moduleSettings.displayAuraIcon = v
					if self.barFrame.icon then
						if v then
							self.barFrame.icon:Show()
						else
							self.barFrame.icon:Hide()
						end
					end
				end,
				disabled = function()
					return not self.moduleSettings.enabled
				end,
				order = 51,
			},

			auraIconXOffset = {
				type = 'range',
				min = -250,
				max = 250,
				step = 1,
				name = L["Aura icon horizontal offset"],
				desc = L["Adjust the horizontal position of the aura icon"],
				get = function()
					return self.moduleSettings.auraIconXOffset
				end,
				set = function(info, v)
					self.moduleSettings.auraIconXOffset = v
					self:PositionIcons()
				end,
				disabled = function()
					return not self.moduleSettings.enabled or not self.moduleSettings.displayAuraIcon
				end,
				order = 52,
			},

			auraIconYOffset = {
				type = 'range',
				min = -250,
				max = 250,
				step = 1,
				name = L["Aura icon vertical offset"],
				desc = L["Adjust the vertical position of the aura icon"],
				get = function()
					return self.moduleSettings.auraIconYOffset
				end,
				set = function(info, v)
					self.moduleSettings.auraIconYOffset = v
					self:PositionIcons()
				end,
				disabled = function()
					return not self.moduleSettings.enabled or not self.moduleSettings.displayAuraIcon
				end,
				order = 53,
			},

			auraIconScale = {
				type = 'range',
				min = 0.1,
				max = 3.0,
				step = 0.05,
				name = L["Aura icon scale"],
				desc = L["Adjusts the size of the aura icon for this bar"],
				get = function()
					return self.moduleSettings.auraIconScale
				end,
				set = function(info, v)
					self.moduleSettings.auraIconScale = v
					self:PositionIcons()
				end,
				disabled = function()
					return not self.moduleSettings.enabled or not self.moduleSettings.displayAuraIcon
				end,
				order = 54,
			},
		},
	}

	opts["reverseChannel"] = {
		type = 'toggle',
		name = L["Reverse channeling"],
		desc = L["Whether or not to reverse the direction of the cast bar when a spell is being channeled. For example, if a normal cast causes this bar to fill up, then checking this option will cause a channeled spell to empty the bar instead."],
		get = function()
			return self.moduleSettings.reverseChannel
		end,
		set = function(info, v)
			self.moduleSettings.reverseChannel = v
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end,
		order = 32.5,
	}

	return opts
end

function IceCastBar.prototype:IsFull(scale)
	local retval = IceCastBar.super.prototype.IsFull(self, scale)
	if retval then
		if self.action and self.action ~= IceCastBar.Actions.None then
			return false
		end
	end
	return retval
end

-- 'Protected' methods --------------------------------------------------------

-- OVERRIDE
function IceCastBar.prototype:CreateFrame()
	IceCastBar.super.prototype.CreateFrame(self)

	self.frame.bottomUpperText:SetWidth(self.settings.gap + 30)

	if not self.barFrame.icon then
		self.barFrame.icon = self.masterFrame:CreateTexture(nil, "OVERLAY")
		-- default texture so that 'config mode' can work without activating the bar first
		self.barFrame.icon:SetTexture("Interface\\Icons\\Spell_Frost_Frost")
		-- this cuts off the border around the buff icon
		self.barFrame.icon:SetTexCoord(0.1, 0.9, 0.1, 0.9)
	end
	self:PositionIcons()
end

function IceCastBar.prototype:PositionIcons()
	if not self.barFrame or not self.barFrame.icon then
		return
	end

	self.barFrame.icon:ClearAllPoints()
	self.barFrame.icon:SetPoint("TOPLEFT", self.frame, "TOPLEFT", self.moduleSettings.auraIconXOffset, self.moduleSettings.auraIconYOffset)
	self.barFrame.icon:SetWidth(AuraIconWidth * self.moduleSettings.auraIconScale)
	self.barFrame.icon:SetHeight(AuraIconHeight * self.moduleSettings.auraIconScale)
end

function IceCastBar.prototype:GetRemainingCastTime()
	if IceHUD.CanAccessValue(self.actionStartTime) and IceHUD.CanAccessValue(self.actionDuration) then
		return self.actionStartTime + self.actionDuration - GetTime()
	end

	return self.actionDuration
end

function IceCastBar.prototype:GetCurrentCastDurationMs()
	return (GetTime() - (self.actionStartTime or GetTime())) * 1000
end

function IceCastBar.prototype:IsCastOrChannel(action)
	return action == IceCastBar.Actions.Cast or action == IceCastBar.Actions.Channel or action == IceCastBar.Actions.ReverseChannel
end

-- this is an IceBarElement utility that controls low-level text coordinate placement. we use our own SetTimerDuration-based methods of inverting for castbars instead.
function IceCastBar.prototype:ShouldReverseFill()
	return false
end

-- OnUpdate handler
function IceCastBar.prototype:MyOnUpdate()
	-- safety catch
	if self.action == IceCastBar.Actions.None then
		--IceHUD:Debug("Stopping action ", self.action)
		self:StopBar()
		IceHUD.IceCore:RequestUpdates(self, nil)
		return
	end

	self:Update()
	self:SetTextAlpha()

	-- handle casting and channeling
	if self:IsCastOrChannel(self.action) then
		if not IceHUD.CanAccessValue(self.actionDuration) then
			return
		end

		local remainingTime = self:GetRemainingCastTime()
		local scale = 1 - (self.actionDuration ~= 0 and remainingTime / self.actionDuration or 0)

		if self.action == IceCastBar.Actions.ReverseChannel then
			scale = self.actionDuration ~= 0 and remainingTime / self.actionDuration or 0
		end

		if not self.barFrame.SetTimerDuration then
			self:UpdateBar(IceHUD:Clamp(scale, 0, 1), self:GetCurrentCastingColor())
		elseif self.NumStages then
			self:SetBarColorRGBA(self:GetColor(self:GetCurrentCastingColor()))
		end

		if remainingTime <= -0.5 then
			self:StopBar()
		end

		local timeString = self.moduleSettings.showCastTime and string.format("%.1fs ", remainingTime) or ""
		local empowerString = self.NumStages ~= nil and (L["Stage %d"]):format(self:GetCurrentStage()) or ""
		local line1 = timeString .. (self.actionMessage or "")
		if self.moduleSettings.empowerStageTextDisplay == "TOPLINE" then
			line1 = line1 .. " " .. empowerString
		end
		if self.moduleSettings.empowerStageTextDisplay == "BOTTOMLINE" then
			self:SetBottomText2(empowerString)
		end
		self:SetBottomText1(line1)

		return
	end

	-- handle bar flashes
	if (self.action == IceCastBar.Actions.Instant or
		self.action == IceCastBar.Actions.Success or
		self.action == IceCastBar.Actions.Failure)
	then
		local scale
		if self.actionStartTime then
			scale = GetTime() - self.actionStartTime
		end

		if scale and (scale > 1) then
			self:StopBar()
			return
		end

		self:UpdateBar(1, self:GetCurrentCastingColor())

		if scale then
			if (self.action == IceCastBar.Actions.Failure) then
				self:FlashBar("CastFail", 1-scale, self.actionMessage, "CastFail")
			else
				self:FlashBar("CastSuccess", 1-scale, self.actionMessage)
			end
		end
		return
	end

	-- something went wrong
	IceHUD:Debug("OnUpdate error ", self.action, " -- ", self.actionStartTime, self.actionDuration, self.actionMessage)
	self:StopBar()
end

function IceCastBar.prototype:GetStageDuration(stage)
	if not GetUnitEmpowerMinHoldTime then
		return 0
	end

	if stage == 0 then
		return GetUnitEmpowerMinHoldTime(self.unit)
	end

	return GetUnitEmpowerStageDuration(self.unit, stage);
end

function IceCastBar.prototype:GetDurationUpToStage(stage)
	if stage == nil or not IceHUD.CanAccessValue(stage) then
		return 0
	end

	local total = 0
	for i=0,stage-1 do
		local duration = self:GetStageDuration(i)
		if not IceHUD.CanAccessValue(duration) then
			return duration
		end
		total = total + duration
	end

	return total
end

function IceCastBar.prototype:GetCurrentStage()
	if not GetUnitEmpowerMinHoldTime then
		return 0
	end

	local castDuration = self:GetCurrentCastDurationMs()
	for i=1,self.NumStages do
		local stageDuration = self:GetDurationUpToStage(i)
		if not IceHUD.CanAccessValue(stageDuration) then
			return i
		end
		if castDuration < stageDuration then
			return i - 1
		end
	end

	return self.NumStages
end

function IceCastBar.prototype:IsCastingEmpowerSpell()
	return self.NumStages ~= nil
end

function IceCastBar.prototype:GetCurrentCastingColor()
	if self:IsCastingEmpowerSpell() and IceHUD.CanAccessValue(self.NumStages) then
		return ("EmpowerStage%d"):format(self:GetCurrentStage())
	end

	if self.action == IceCastBar.Actions.Channel or self.action == IceCastBar.Actions.ReverseChannel then
		return "CastChanneling"
	end

	return "CastCasting"
end

function IceCastBar.prototype:FlashBar(color, alpha, text, textColor)
	if self.action == IceCastBar.Actions.Instant and self.moduleSettings.flashInstants == "Never" then
		return
	end

	if alpha then
		self.frame:SetAlpha(alpha)
	end

	if not self.skipSetColorOnFlash then
		local r, g, b = self.settings.backgroundColor.r, self.settings.backgroundColor.g, self.settings.backgroundColor.b
		if (self.settings.backgroundToggle) then
			r, g, b = self:GetColor(color)
		end

		self.frame.bg:SetVertexColor(r, g, b, 0.3)
		self:SetBarColorRGBA(self:GetColor(color, 0.8))
	end

	self:SetScale(1)
	self:SetBottomText1(text, textColor or "Text")
end


function IceCastBar.prototype:StartBar(action, message, spellId)
	local isChannel
	local spell, rank, displayName, icon, startTime, endTime, isTradeSkill, numStages, _, castGuid, castBarId
	if IceHUD.SpellFunctionsReturnRank then
		spell, rank, displayName, icon, startTime, endTime, isTradeSkill, castGuid = UnitCastingInfo(self.unit)
	else
		spell, displayName, icon, startTime, endTime, isTradeSkill, castGuid, _, _, castBarId = UnitCastingInfo(self.unit)
	end
	if not (spell) then
		isChannel = true
		if IceHUD.SpellFunctionsReturnRank then
			spell, rank, displayName, icon, startTime, endTime = UnitChannelInfo(self.unit)
		else
			spell, displayName, icon, startTime, endTime, isTradeSkill, _, _, _, numStages, castBarId = UnitChannelInfo(self.unit)
		end
	end

	if spellId and not spell then
		spell, rank, icon = GetSpellInfo(spellId)
	end

	if IceHUD.CanAccessValue(numStages) and numStages and numStages > 0 then
		self.NumStages = numStages
		if IceHUD.CanAccessValue(endTime) then
			-- unclear what to do about this if we can't access endTime.
			-- is there a way to compute an enemy's empower hold?
			endTime = endTime + GetUnitEmpowerHoldAtMaxTime(self.unit)
		end
		action = IceCastBar.Actions.ReverseChannel
	else
		self.NumStages = nil
	end

	if self.moduleSettings.reverseChannel then
		if action == IceCastBar.Actions.Channel then
			action = IceCastBar.Actions.ReverseChannel
		elseif action == IceCastBar.Actions.ReverseChannel then
			action = IceCastBar.Actions.Channel
		end
	end

	-- Fulzamoth 2019-09-27 : LibClassicCasterino won't return spell info on target's failed or interrupted cast
	if LibClassicCasterino and not spell then
		self:StopBar()
	elseif not spell then
		return
	end

	if icon ~= nil then
		self.barFrame.icon:SetTexture(icon)
	end

	if self:IsInConfigMode() or self.moduleSettings.displayAuraIcon then
		self.barFrame.icon:Show()
	else
		self.barFrame.icon:Hide()
	end

	if self:IsCastOrChannel(action) then
		if IceHUD.IsSecretEnv() then
			self.current = castBarId
		else
			self.current = castGuid
		end
	else
		self.current = nil
	end
	self.action = action
	self.lastSpell = spell
	self.actionStartTime = GetTime()
	self.actionMessage = message

	if not message and spell then
		self.actionMessage = spell .. (self.moduleSettings.showSpellRank and self:GetShortRank(rank) or "")
	end

	IceHUD:Debug("  StartBar", self.action, self.current, self.actionMessage)

	local setupUpdates = true
	if startTime and endTime then
		if not IceHUD.CanAccessValue(startTime) then
			startTime = GetTime()
		end
		self.actionStartTime = startTime / 1000

		if IceHUD.CanAccessValue(endTime) then
			self.actionDuration = (endTime - startTime) / 1000
		else
			self.actionDuration = endTime
		end

		if self.barFrame.SetTimerDuration then
			local duration
			if self.NumStages and IceHUD.CanAccessValue(endTime) then
				duration = C_DurationUtil.CreateDuration()
				---@diagnostic disable-next-line: undefined-field
				duration:SetTimeSpan(startTime / 1000, endTime / 1000)
			else
				duration = isChannel and UnitChannelDuration(self.unit) or UnitCastingDuration(self.unit)
			end

			if not duration then
				return
			end

			self:SetTimerDuration(duration, action)
			self:UpdateBar(0, self:GetCurrentCastingColor())
			self:SetBottomText1(self.actionMessage)
			setupUpdates = self.NumStages
		end
	else
		if C_DurationUtil and self.barFrame.SetTimerDuration then
			local duration = C_DurationUtil.CreateDuration()
			---@diagnostic disable-next-line: undefined-field
			duration:SetTimeFromEnd(GetTime(), GetTime())
			self.barFrame:SetTimerDuration(duration)
		end

		self.actionDuration = 1 -- instants/failures
	end

	self:Show(true)
	if setupUpdates then
		self:ConditionalSetupUpdate()
	end
end

function IceCastBar.prototype:SetTimerDuration(duration, action)
	local direction = action == IceCastBar.Actions.ReverseChannel and Enum.StatusBarTimerDirection.RemainingTime or Enum.StatusBarTimerDirection.ElapsedTime

	local invert = IceCastBar.super.prototype.ShouldReverseFill(self)
	if invert then
		direction = direction == Enum.StatusBarTimerDirection.RemainingTime and Enum.StatusBarTimerDirection.ElapsedTime or Enum.StatusBarTimerDirection.RemainingTime
	end

	self.barFrame:SetTimerDuration(duration, Enum.StatusBarInterpolation.Immediate, direction)
end

function IceCastBar.prototype:StopBar()
	IceHUD:Debug("  StopBar", self.action)
	self.current = nil
	self.action = IceCastBar.Actions.None
	self.actionStartTime = nil
	self.actionDuration = nil
	self.lastSentGuid = nil

	self:SetBottomText1()
	self:SetBottomText2()
	self:SetScale(0)
	self:Show(false)
end

function IceCastBar.prototype:GetShortRank(rank)
	if IceHUD.SpellFunctionsReturnRank and rank then
		local _, _, sRank = string.find(rank, "(%d+)")
		if (sRank) then
			return " (" .. sRank .. ")"
		end
	end
	return ""
end



-------------------------------------------------------------------------------
-- NORMAL SPELLS                                                             --
-------------------------------------------------------------------------------

function IceCastBar.prototype:SpellCastSent(event, unit, target, castGuid, spellId)
	if (unit ~= self.unit) then return end
	IceHUD:Debug("SpellCastSent", unit, target, castGuid, spellId, C_Spell.GetSpellName(spellId))
	self.lastSentGuid = castGuid
end

function IceCastBar.prototype:SpellCastChanged(event, cancelled)
	-- IceHUD:Debug("SpellCastChanged", cancelled)
end

function IceCastBar.prototype:SpellCastStart(event, unit, castGuid, spellId, castBarId)
	if (unit ~= self.unit) then return end
	IceHUD:Debug("SpellCastStart", unit, castGuid, spellId, castBarId, C_Spell.GetSpellName(spellId), "|", self.action, self.current)

	self.lastCastGuid = castGuid
	self:StartBar(IceCastBar.Actions.Cast, nil, spellId)
end

function IceCastBar.prototype:SpellCastStop(event, unit, castGuid, spellId, castBarId)
	if (unit ~= self.unit) then return end
	IceHUD:Debug("SpellCastStop", unit, castGuid, spellId, castBarId, C_Spell.GetSpellName(spellId), "|", self.action, self.current)

	-- ignore if not coming from current spell
	if IceHUD.IsSecretEnv() and self.current and self.current ~= castBarId then
		return
	end
	-- fall back to pre-Midnight guid check
	if not IceHUD.IsSecretEnv() and self.current and castGuid and self.current ~= castGuid then
		return
	end

	if self.action ~= IceCastBar.Actions.Success and
		self.action ~= IceCastBar.Actions.Failure and
		self.action ~= IceCastBar.Actions.Instant and
		self.action ~= IceCastBar.Actions.Channel and
		self.action ~= IceCastBar.Actions.ReverseChannel
	then
		self:StopBar()
	end
end


function IceCastBar.prototype:SpellCastFailed(event, unit, castGuid, spellId, castBarId)
	if (unit ~= self.unit) then return end
	IceHUD:Debug("SpellCastFailed", unit, castGuid, spellId, castBarId, C_Spell.GetSpellName(spellId), "|", self.action, self.current)

	-- ignore if not coming from current spell
	if IceHUD.IsSecretEnv() and self.current and self.current ~= castBarId then
		return
	end
	-- fall back to pre-Midnight guid check
	if not IceHUD.IsSecretEnv() and self.current and castGuid and self.current ~= castGuid then
		return
	end

	-- channeled spells will call ChannelStop, not cast failed
	if self.action == IceCastBar.Actions.Channel or self.action == IceCastBar.Actions.ReverseChannel then
		return
	end

	-- determine if we want to show failed casts
	if (self.moduleSettings.flashFailures == "Never") then
		return
	elseif (self.moduleSettings.flashFailures == "Caster") then
		if (UnitPowerType("player") ~= SPELL_POWER_MANA) then
			return
		end
	end

	self:StartBar(IceCastBar.Actions.Failure, L["Failed"], spellId)
end

function IceCastBar.prototype:SpellCastInterrupted(event, unit, castGuid, spellId, interruptedBy, castBarId)
	if (unit ~= self.unit) then return end
	IceHUD:Debug("SpellCastInterrupted", unit, castGuid, spellId, castBarId, C_Spell.GetSpellName(spellId), "|", self.action, self.current)

	-- ignore if not coming from current spell
	if IceHUD.IsSecretEnv() and self.current and self.current ~= castBarId then
		return
	end
	-- fall back to pre-Midnight guid check
	if not IceHUD.IsSecretEnv() and self.current and castGuid and self.current ~= castGuid then
		return
	end

	self:StartBar(IceCastBar.Actions.Failure, L["Interrupted"], spellId)
end

function IceCastBar.prototype:SpellCastDelayed(event, unit, castGuid, spellId, castBarId)
	if (unit ~= self.unit) then return end
	IceHUD:Debug("SpellCastDelayed", unit, castGuid, spellId, C_Spell.GetSpellName(spellId), "|", self.action, self.current)

	-- ignore if not coming from current spell
	if IceHUD.IsSecretEnv() and self.current and self.current ~= castBarId then
		return
	end
	-- fall back to pre-Midnight guid check
	if not IceHUD.IsSecretEnv() and self.current and castGuid and self.current ~= castGuid then
		return
	end

	if UnitCastingDuration then
		self:StartBar(IceCastBar.Actions.Cast, nil, spellId)
		return
	end

	local endTime = select(IceHUD.SpellFunctionsReturnRank and 6 or 5, UnitCastingInfo(self.unit))
	-- apparently this check is needed, got nils during a horrible lag spike
	if endTime and self.actionStartTime then
		self.actionDuration = endTime/1000 - self.actionStartTime
	end
end


function IceCastBar.prototype:SpellCastSucceeded(event, unit, castGuid, spellId, castBarId)
	if (unit ~= self.unit) then return end
	IceHUD:Debug("SpellCastSucceeded", unit, castGuid, spellId, castBarId, C_Spell.GetSpellName(spellId), "|", self.action, self.current)

	-- never show on channeled (why on earth does this event even fire when channeling starts?)
	if (self.action == IceCastBar.Actions.Channel or self.action == IceCastBar.Actions.ReverseChannel) then
		return
	end

	-- ignore if not coming from current spell
	if IceHUD.IsSecretEnv() and self.current and self.current ~= castBarId then
		return
	end
	-- fall back to pre-Midnight guid check
	if not IceHUD.IsSecretEnv() and self.current and castGuid and self.current ~= castGuid then
		return
	end

	local spell = GetSpellName(spellId)

	-- show after normal successfull cast
	if (self.action == IceCastBar.Actions.Cast) then
		self:StartBar(IceCastBar.Actions.Success, spell, spellId)
		return
	end

	-- determine if we want to show instant casts
	if (self.moduleSettings.flashInstants == "Never") then
		return
	elseif (self.moduleSettings.flashInstants == "Caster") then
		if (UnitPowerType("player") ~= SPELL_POWER_MANA) then
			return
		end
	end

	-- handle special spells that are used for quests or other things that don't apply to the player
	if self.unit == "player" and not IceHUD.IsPlayerSpell(spellId) then
		return
	end

	self:StartBar(IceCastBar.Actions.Success, spell, spellId)
end



-------------------------------------------------------------------------------
-- CHANNELING SPELLS                                                         --
-------------------------------------------------------------------------------

function IceCastBar.prototype:SpellCastChannelStart(event, unit, castGuid, spellId, castBarId)
	if (unit ~= self.unit) then return end
	IceHUD:Debug("SpellCastChannelStart", unit, castGuid, spellId, castBarId, C_Spell.GetSpellName(spellId), "|", self.action, self.current)

	self.lastCastGuid = castGuid
	if not self.lastCastGuid then
		self.lastCastGuid = self.lastSentGuid
	end
	self:StartBar(IceCastBar.Actions.Channel)
	if not IceHUD.IsSecretEnv() then
		self.current = castGuid
	end
end

function IceCastBar.prototype:SpellCastChannelUpdate(event, unit, castGuid, spellId, castBarId)
	if (unit ~= self.unit or not self.actionStartTime) then return end
	IceHUD:Debug("SpellCastChannelUpdate", unit, UnitChannelInfo(unit), C_Spell.GetSpellName(spellId), "|", self.action, self.current)

	-- ignore if not coming from current spell
	if IceHUD.IsSecretEnv() and self.current and self.current ~= castBarId then
		return
	end
	-- fall back to pre-Midnight guid check
	if not IceHUD.IsSecretEnv() and self.current and castGuid and self.current ~= castGuid then
		return
	end

	if UnitChannelDuration then
		self:StartBar(IceCastBar.Actions.Channel, nil, spellId)
		return
	end

	local spell, rank, displayName, icon, startTime, endTime
	if IceHUD.SpellFunctionsReturnRank then
		spell, rank, displayName, icon, startTime, endTime = UnitChannelInfo(unit)
	else
		spell, displayName, icon, startTime, endTime = UnitChannelInfo(unit)
	end
    if not spell then
        self.actionDuration = 0
    else
		self.actionDuration = endTime/1000 - self.actionStartTime
    end
end

function IceCastBar.prototype:SpellCastChannelStop(event, unit, castGuid, spellId, interruptedBy, castBarId)
	if (unit ~= self.unit) then return end
	IceHUD:Debug("SpellCastChannelStop", unit, castGuid, spellId, castBarId, C_Spell.GetSpellName(spellId), "|", self.action, self.current)

	-- ignore if not coming from current spell
	if IceHUD.IsSecretEnv() and self.current and self.current ~= castBarId then
		return
	end
	-- fall back to pre-Midnight guid check
	if not IceHUD.IsSecretEnv() and self.current and castGuid and self.current ~= castGuid then
		return
	end

	if self.action ~= IceCastBar.Actions.Success and
		self.action ~= IceCastBar.Actions.Failure and
		self.action ~= IceCastBar.Actions.Instant and
		self.action ~= IceCastBar.Actions.Cast
	then
		self:StopBar()
	end
end

function IceCastBar.prototype:SpellCastEmpowerStop(event, unit, castGuid, spellId, complete, interruptedBy, castBarId)
	if unit ~= self.unit then
		return
	end

	self:SpellCastChannelStop(event, unit, castGuid, spellId, interruptedBy, castBarId)
end
