local E = select(2, ...) -- Engine
local BC,CO,L,UF,AB,LT,AUR,TT = E:LoadModules("Bar_Cast", "Config", "Locale", "Unitframes", "Actionbars", "Layout", "Auras", "Tooltip")

local _, ClassColor
	BC.Bars = {}

-- string.format usage:
-- string.format("CUI_%sCastbar%s", unit, index)
-- %s can be used an unlimited amount of times. Also do additional args
local BarBaseName = "CUI_%sCastbar%s"
local IconSize = 23


-- INTERRUPTED holds a localized "interrupted" string
local CASTBAR_BORDER_TEXTURE = [[]]

function BC:Register(B, BName)
	BC.Bars[BName] = B
end

function BC:Get(BName)
	return BC.Bars[BName]
end

function BC:GetIndex(unit)
	local i = 1
	for k, v in pairs(BC.Bars) do
		if v.unit == unit then
			i = i + 1
		end
	end
	
	return i
end

function BC:AddText(b, n, a, x, y)
	b[n] = b.Overlay:CreateFontString(nil, "ARTWORK")
	local f = b[n]
	
	E:InitializeFontFrame(b[n], "ARTWORK", CO.Media:Fetch("font", self.db["fontType"]), 11, {0.8,0.8,0.8}, 1, {0,0}, "", 0, 0, b.Overlay, "RIGHT", {1,1})
	f:SetFont(CO.Media:Fetch("font", self.db["fontType"]), 11, "")
	f:ClearAllPoints()
	f:SetAllPoints(b)
	f:SetJustifyH(a)
	f:SetJustifyV("MIDDLE")
	
	if x and y then E:PushFrame(f, x, y) end
end

function BC:ToggleMovers(s)
	for k, v in pairs(BC.Bars) do
		BC.Bars[k].moverEnabled = s
		
		if s == true then
			BC.Bars[k]:Show()
			BC.Bars[k]:SetAlpha(1)
		else
			BC.Bars[k]:Hide()
		end
	end
end

function BC:AddLagBar(b)
	b.LagBar = CreateFrame("Frame", nil, b.Overlay)
	--b.LagBar:SetAllPoints(b.Overlay)
	b.LagBar:SetSize(b.Overlay:GetWidth(), b.Overlay:GetHeight())
	b.LagBar:SetPoint("RIGHT", b.Overlay, "RIGHT")
	b.LagBarTex = b.LagBar:CreateTexture(nil)
	b.LagBarTex:SetAllPoints(b.LagBar)
	b.LagBarTex:SetPoint("RIGHT", b.LagBar)
	b.LagBarTex:SetColorTexture(0.65, 0, 0, 0.75)
	
	BC:UpdateLagBar(b, false)
end

function BC:UpdateLagBar(b, s)
	if s == false then
		b.LagBar:Hide()
		return
	else
		b.LagBar:Show()
	end
	
	local timePerPixel, lagBarWidth
	local LagWorld = select(4, GetNetStats())
	local min, max = b.GetMinMaxValues()
	
	-- We are always assuming the min max values are timings
	-- We use the delta value to determine the real needed width of the LagBar
	local delta = max - min
	timePerPixel = b.Overlay:GetWidth() / delta
	lagBarWidth = LagWorld * timePerPixel

	b.LagBar:SetWidth(lagBarWidth)
end

local SpellName, SpellText, SpellTexture, SpellStartTime, SpellEndTime, SpellIsTradeSkill, SpellCastID, SpellNotInterruptible
function BC:Create(unit)
	local i = BC:GetIndex(unit) -- Get index
	local name = string.format("CUI_%sCastbar%s", unit, i)
	local bar
	
	bar = UF:CreateBar(name, "LOW", 235, 25, {"CENTER", E.Parent, "CENTER"}, E.Parent)
	E.LibSmooth:ResetBar(bar.Overlay) -- Leaving the smooth anim on somehow causes the bar to not go at a 100%. This results in the LagBar simply being useless and just looks weird
	bar.SetValue(0)
	bar.Background.SetAlpha(0.95)
	
	bar.Unit = unit
	
	bar.Icon = bar:CreateTexture(nil, "OVERLAY")
	bar.Icon:SetSize(IconSize,IconSize)
	bar.Icon:SetPoint("LEFT", bar, "LEFT", -IconSize, 0)
	
	bar.Icon:SetTexCoord(0.06,0.94,0.06,0.94)
	
	BC:AddText(bar, "Time", "RIGHT", -10, 0)
	BC:AddText(bar, "Name", "LEFT", 30, 0)
	
	BC:AddLagBar(bar)
	
	E:CreateMover(bar, string.format("%s-%s%s", L[unit], L["castbar"], i))
	bar.moverEnabled = nil
	
	bar.Interruptor = nil
	
	-- Register a bunch (all) of spellcast events (all we need)
	bar:RegisterUnitEvent("UNIT_SPELLCAST_START", unit)
	bar:RegisterUnitEvent("UNIT_SPELLCAST_STOP", unit)
	bar:RegisterUnitEvent("UNIT_SPELLCAST_FAILED", unit)
	bar:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", unit)
	bar:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTED", unit)
	bar:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTIBLE", unit)
	bar:RegisterUnitEvent("UNIT_SPELLCAST_DELAYED", unit)
	bar:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", unit)
	bar:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", unit)
	bar:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_UPDATE", unit)
	bar:RegisterEvent("UNIT_TARGET")
	if unit == "target" then
		bar:RegisterEvent("PLAYER_TARGET_CHANGED")
	elseif unit == "focus" then
		bar:RegisterEvent("PLAYER_FOCUS_CHANGED")
	end
	
	-- Interruptor
	bar:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
	
	bar:SetScript("OnEvent", function(self, event, ...)
		local eventUnit = ...
		
		-- Interruptor handler START
		----------------------------
		if event == "COMBAT_LOG_EVENT_UNFILTERED" then
			-- Probably the most efficient way we can go
			if select(2, ...) == "SPELL_INTERRUPT" and select(8, ...) == UnitGUID(self.Unit) then
				self.Interruptor = select(5, ...)
				
				-- Check if the interruptor name is valid. Environmental effects like quaking leave this at nil, a.e.
				if self.Interruptor then
					self.Name:SetText(string.format("%s [%s]", INTERRUPTED, self.Interruptor))
				end
			end
			
			-- End call directly, since we do not want the script to iterate through everything else. This does get fired REALLY rapidly in combat.
			return
		end
		-- Interruptor handler END
		----------------------------
		
		-- print(event)
		
		if eventUnit ~= unit and not event == "PLAYER_TARGET_CHANGED" then return end
		
		if event == "UNIT_SPELLCAST_START" or event == "PLAYER_TARGET_CHANGED" or event == "PLAYER_FOCUS_CHANGED" then
			-- BfA version
			-- local name, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible = UnitCastingInfo(unit);
			-- Legion version
			
			if E:IsLegionClient() then
				SpellName, _, SpellText, SpellTexture, SpellStartTime, SpellEndTime, SpellIsTradeSkill, SpellCastID, SpellNotInterruptible  = UnitCastingInfo(unit)
			else
				SpellName, SpellText, SpellTexture, SpellStartTime, SpellEndTime, SpellIsTradeSkill, SpellCastID, SpellNotInterruptible  = UnitCastingInfo(unit);
			end
			if SpellName then
				CastingBarFrame_ApplyAlpha(self, 1.0)
				self.holdTime = 0
				self.casting = true
				self.channeling = nil
				self.fadeOut = nil
			
				self.SetMinMaxValues(SpellStartTime, SpellEndTime)
				self.SetValue(SpellStartTime)
				self.SpellName = SpellName
				self.startTime = SpellStartTime
				self.endTime = SpellEndTime
				self.Name:SetText(SpellName)
				self.Icon:SetTexture(SpellTexture)
				
				if SpellNotInterruptible then
					bar.SetOverlayColor(1,0.772,0.380, 0.95)
				else
					bar.SetOverlayColor(0.85,0.85,0.85, 0.95)
				end
				
				local enableLagBar = true
				-- Keep lagbar enabled for non-player units, when the cast is interruptible to help with interrupting it on high latency
				if unit ~= "player" and SpellNotInterruptible then enableLagBar = false end
				
				BC:UpdateLagBar(self, enableLagBar)
				
				self:Show()
				self:SetAlpha(1)
				
				-- self.holdTime = GetTime() + CASTING_BAR_HOLD_TIME
			else
				self.casting = nil
				self.channeling = nil
				self.fadeOut = true
				self.holdTime = 0
				
				self:Hide()
			end
			
		
			
		elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
			if self.Name:GetText() == INTERRUPTED or self.channeling then return end
			
			-- If still casting [Fix for passive auras that also trigger this event]
			if UnitCastingInfo(unit) then return end
			
			self.casting = nil
			self.channeling = nil
			self.fadeOut = true
			self.holdTime = CASTING_BAR_HOLD_TIME
			
			
			self.SetValue(select(2, self.Overlay:GetMinMaxValues()))
			self.Time:SetText("")
			
			bar.SetOverlayColor(0,0.85,0, 0.95)
			--local name, rank, displayName, texture, startTime, endTime, isTradeSkill, castID, notInterruptible = UnitCastingInfo(unit)
			--if not name then
								
			--end

		elseif event == "UNIT_SPELLCAST_CHANNEL_STOP" then
			-- If still casting [Fix for passive auras that also trigger this event]
			if not self.channeling or UnitChannelInfo(unit) then return end
			
			self.casting = nil
			self.channeling = nil
			self.fadeOut = true
			self.holdTime = CASTING_BAR_HOLD_TIME
			
			self.SetValue(select(2, self.Overlay:GetMinMaxValues()))
			self.Time:SetText("")
			
			bar.SetOverlayColor(0,0.85,0, 0.95)
		
		elseif event == "UNIT_SPELLCAST_FAILED" then
			if UnitCastingInfo(unit) then return end
			
			if not self.casting then return end
				self.casting = nil
				self.channeling = nil
				self.fadeOut = true
				self.holdTime = 0
				
				self:Hide()
			
		elseif event == "UNIT_SPELLCAST_FAILED_QUIET" then
			
		elseif event == "UNIT_SPELLCAST_INTERRUPTED" then
			if not self.casting then return end
			-- If still casting [Fix for passive auras that also trigger this event]
			--if UnitCastingInfo(unit) then return end
			
			self.SetValue(select(2, self.Overlay:GetMinMaxValues()))
			self.Time:SetText("")
			
			-- We set the interruptor name in the COMBAT_LOG_EVENT_UNFILTERED handler
			self.Name:SetText(INTERRUPTED)
			
			bar.SetOverlayColor(0.85,0,0, 0.95)
		
			self.casting = nil
			self.channeling = nil
			self.fadeOut = true
			self.holdTime = GetTime() + CASTING_BAR_HOLD_TIME
		elseif event == "UNIT_SPELLCAST_INTERRUPTIBLE" then
			
		elseif event == "UNIT_SPELLCAST_DELAYED" then
		
		-- Immediate interruption (Spellcast failed directly)
		elseif event == "UNIT_SPELLCAST_STOP" then
			if not self.casting then return end
				self.casting = nil
				self.channeling = nil
				self.fadeOut = true
				self.holdTime = 0
				
				self:Hide()
		
		elseif event == "UNIT_SPELLCAST_CHANNEL_START" or event == "PLAYER_TARGET_CHANGED" or event == "PLAYER_FOCUS_CHANGED"  then
			-- BfA version
			-- local name, text, texture, startTime, endTime, isTradeSkill, notInterruptible, spellID = UnitChannelInfo(unit)
			--Legion version
			if E:IsLegionClient() then
				SpellName, _, SpellText, SpellTexture, SpellStartTime, SpellEndTime, SpellIsTradeSkill, SpellNotInterruptible  = UnitChannelInfo(unit)
			else
				SpellName, SpellText, SpellTexture, SpellStartTime, SpellEndTime, SpellIsTradeSkill, SpellNotInterruptible, SpellCastID = UnitChannelInfo(unit)
			end
			if ( not SpellName or (not self.showTradeSkills and SpellIsTradeSkill)) then
				-- if there is no name, there is no bar
				self:Hide()
				return
			end

			bar.SetOverlayColor(ClassColor[1],ClassColor[2],ClassColor[3], 0.95)
				
			self:Show()
			self:SetAlpha(1)

			self.Name:SetText(SpellName)
			self.Icon:SetTexture(SpellTexture)
			
			
			self.maxValue = (SpellEndTime - SpellStartTime) / 1000
			self.value = self.maxValue
			self.minValue = 0
			self.SetMinMaxValues(0, self.maxValue)
			self.SetValue(self.value)
			self.casting = nil
			self.channeling = true
			
			BC:UpdateLagBar(self, false)
			
			self.holdTime = GetTime() + CASTING_BAR_HOLD_TIME
			
		elseif event == "UNIT_SPELLCAST_CHANNEL_UPDATE" then
			
		end
	end)
	
	bar:HookScript("OnUpdate", function(self, elapsed)
					
		if self.moverEnabled then return end
		
		if ( self.casting ) then
			
			if GetTime() * 1000 > self.endTime then self.casting = nil; self.fadeOut = true; return end
			
			self.SetValue(GetTime() * 1000)
			self.Time:SetText(E:Round(((self.endTime - GetTime() * 1000) / 1000), 1))
			
		elseif ( self.channeling ) then
			self.value = self.value - elapsed;
			if ( self.value <= self.minValue ) then
				-- CastingBarFrame_FinishSpell(self, self.Spark, self.Flash);
				return;
			end
			
			self.SetValue(self.value);
			self.Time:SetText(E:Round(self.value, 1))
			if ( self.Flash ) then
				self.Flash:Hide();
			end
		elseif ( GetTime() < self.holdTime ) then
			return;
		elseif ( self.flash ) then
			local alpha = 0;
			if ( self.Flash ) then
				alpha = self.Flash:GetAlpha() + CASTING_BAR_FLASH_STEP;
			end
			if ( alpha < 1 ) then
				if ( self.Flash ) then
					self.Flash:SetAlpha(alpha);
				end
			else
				if ( self.Flash ) then
					self.Flash:SetAlpha(1.0);
				end
				self.flash = nil;
			end
		elseif ( self.fadeOut ) then
			local alpha = self:GetAlpha() - 0.015;
			if ( alpha > 0 ) then
				-- CastingBarFrame_ApplyAlpha(self, alpha);
				self:SetAlpha(alpha)
			else
				self.fadeOut = nil;
				self:Hide();
			end
		end
	end)
	
	bar:Hide()
	BC:Register(bar, name)
end

function BC:Init()
	CO = E:GetModule("Config")
	UF = E:LoadModules("Unitframes")
	self.db = CO.db.profile.castbar
	self.moverdb = CO.db.profile.movers
	
	ClassColor = E:GetUnitClassColor("player")
	
	BC:Create("player")
	BC:Create("target")
	BC:Create("focus")
end

E:AddModule("Bar_Cast", BC)