local E = select(2, ...) -- Engine
local CO,L,UF,BA,TT = E:LoadModules("Config", "Locale", "Unitframes", "Bar_Auras", "Tooltip")

--[[-------------------------------------------------------------------------

	We are caching globals, since making those functions local,
	results in a slight performance boost and therefore
	in less CPU time. Exactly what we are aiming for.

-------------------------------------------------------------------------]]--
local _
local format 					= string.format
local tremove 					= table.remove
local tsort 					= table.sort
local CreateFrame 				= CreateFrame
local DebuffTypeColor 			= DebuffTypeColor
local UnitExists 				= UnitExists
local UnitCanAttack 			= UnitCanAttack
local UnitAura 					= UnitAura
-----------------------------------------------------------------------------

BA.E = CreateFrame("Frame")
BA.Bars = {}
BA.Auras = {}

local BAR_NUM, BAR_GAP_X, BAR_GAP_Y, BAR_SIZE_X, BAR_SIZE_Y = 15, 0, 2, 295, 18


BA.BAR_NUM = BAR_NUM

local function SortByExpiration(a,b)
	if a and b then
		if a[7] < b[7] then
			return true
		elseif a[7] > b[7] then
			return false
		end
	end
end

-- Blizz already provides a list of possible colors
function BA:GetAuraColor(DType, Unit, AuraType)
	if AuraType == "Debuff" then
		if DType then
			return DebuffTypeColor[DType]
		else
			return DebuffTypeColor["none"]
		end
	else
		return E:GetUnitReactionColor(Unit)
	end
end

function BA:UpdateHeader(Header)
	local SizeX, SizeY = 0, 0
	
	for i=1,BAR_NUM do
		SizeX = Header[i]:GetWidth() + CO.db.profile.auras.units[Header.Unit].aurabars.iconSize
		SizeY = SizeY + Header[i]:GetHeight() + CO.db.profile.auras.units[Header.Unit].aurabars.gapY
	end
	
	Header:SetSize(SizeX, SizeY)
	E:UpdateMoverDimensions(Header)
end

function BA:ToggleBars(Unit)
	if BA.Bars[Unit].ForceShow then
		BA.Bars[Unit].ForceShow = nil
	else
		BA.Bars[Unit].ForceShow = true
	end
end

function BA:UpdateName(Object, Aura)
	if Aura[4] and Aura[4] > 1 then
		Object:SetText(format("%s [%s]", Aura[1], Aura[4]))
	else
		if Object:GetText() ~= Aura[1] then
			Object:SetText(Aura[1])
		end
	end
end

function BA:UpdateTime(Object, TimeLeft)
	if TimeLeft > 10 then self.UpdateTimePlaces = 0; else self.UpdateTimePlaces = 1; end
	
	Object:SetText(E:FormatTime(TimeLeft, self.UpdateTimePlaces))
end

function BA:UpdateTexture(Object, Texture)
	if Object.CurrentTexture ~= Texture then
		Object:SetTexture(Texture)
		Object.CurrentTexture = Texture
	end
end

function BA:UpdateBarValues(Object, TimeLeft, Duration)
	Object:SetValue(TimeLeft)
	if Object.CurrentDuration ~= Duration then
		Object:SetMinMaxValues(0, Duration)
		Object.CurrentDuration = Duration
	end
end

function BA:UpdateBarColor(Object, RGB)
	
	if Object.Overlay.RGB ~= RGB then
		Object.Overlay:GetStatusBarTexture():SetVertexColor(RGB.r, RGB.g, RGB.b, 1)
		
		Object.Overlay.RGB = RGB
	end
	if Object.Border.RGB ~= RGB then
		Object.Border:SetBackdropBorderColor(RGB.r, RGB.g, RGB.b, 1)
		
		Object.Border.RGB = RGB
	end
end

local TooltipUnit, TooltipRealIndex, TooltipParent
function BA:BuildTooltip(self)
	TooltipParent = self:GetParent()
	TooltipUnit = TooltipParent:GetParent().Unit
	if not BA.Auras[TooltipUnit] or not BA.Auras[TooltipUnit][TooltipParent.Index] then return end
	
	-- We have to retrieve the real aura index, since we do remove and sort auras from the table
	for k, v in pairs(BA.Auras[TooltipUnit]) do
		-- Because of the way how we assign the tables, we can do a direct comparison
		if v == BA.Auras[TooltipUnit][TooltipParent.Index] then
			TooltipRealIndex = v.RealIndex
		end
	end	
	
	if TooltipRealIndex then
		GameTooltip:SetOwner(self, "ANCHOR_TOP")
		GameTooltip:SetUnitAura(TooltipUnit, TooltipRealIndex, BA.Auras[TooltipUnit].AuraType == "Buff" and "HELPFUL" or "HARMFUL")
		
		GameTooltip:Show()
	end
end

local HideAllFrame
function BA:HideAll(Unit)
	HideAllFrame = self.Bars[Unit]
		for i=1, BAR_NUM do
			if not HideAllFrame[i].IsHidden then
				HideAllFrame[i]:Hide()
				HideAllFrame[i].IsHidden = true
			end
		end
		
	HideAllFrame.AllHidden = true
end

function BA:CreateBars(Unit)
	local BarColor
	
	self.Bars[Unit] = CreateFrame("Frame", format("AuraBarContainer%s", Unit))
	self.Bars[Unit]:SetPoint("CENTER", E.Parent, "CENTER")
	self.Bars[Unit]:SetSize(BAR_SIZE_X, BAR_SIZE_Y * BAR_NUM)
	
	self.Bars[Unit].Unit = Unit
	
	E:CreateMover(self.Bars[Unit], format("%s %s", L[Unit], L["AuraBars"]), "BOTTOMLEFT")
	
	for i=1,BAR_NUM do
		self.Bars[Unit][i] = CreateFrame("Frame", format("AuraBar%s%s", Unit, i)) -- Acts as a parent
		self.Bars[Unit][i]:SetPoint("BOTTOMLEFT", self.Bars[Unit], "BOTTOMLEFT", BAR_GAP_X, (CO.db.profile.auras.units[Unit].aurabars.gapY + BAR_SIZE_Y) * (i - 1))
		self.Bars[Unit][i]:SetSize(BAR_SIZE_X, BAR_SIZE_Y)
		self.Bars[Unit][i]:SetParent(self.Bars[Unit])
		
		self:CreateIcon(self.Bars[Unit][i], format("AuraBar%s%sIcon", Unit, i))
		self.Bars[Unit][i].Icon:SetScript("OnEnter", function(self) BA:BuildTooltip(self) end)
		self.Bars[Unit][i].Icon:SetScript("OnLeave", function(self) GameTooltip:Hide() end)
		
		self.Bars[Unit][i].Bar = UF:CreateBar(format("AuraBar%s%sOverlay", Unit, i), "LOW", BAR_SIZE_X - BAR_SIZE_Y, BAR_SIZE_Y, {"LEFT", self.Bars[Unit][i].Icon, "RIGHT", 0, 0}, self.Bars[Unit][i].Icon, false, false, false)
		self.Bars[Unit][i].Bar:SetParent(self.Bars[Unit][i].Icon)
		
		-- We don't need this here
		E.LibSmooth:ResetBar(self.Bars[Unit][i].Bar.Overlay)
		
		BarColor = E:GetUnitReactionColor(Unit)
		self.Bars[Unit][i].Bar.Overlay:GetStatusBarTexture():SetVertexColor(BarColor.r, BarColor.g, BarColor.b, 1)
		
		self:InitFonts(self.Bars[Unit][i].Bar.Overlay)
		
		self.Bars[Unit][i].Index = i
		
		self.Bars[Unit][i]:Hide()
	end
	
	-- Post script to prevent issues
	self.Bars[Unit]:SetScript("OnUpdate", function(self, elapsed)
		
		if self.ForceShow then
			for i=1, BAR_NUM do
				self[i]:Show()
				self[i].Icon.Tex:SetTexture(134400)
				self[i].Bar.Overlay.name:SetText(format("Placeholder Aura %s", i))
				self[i].Bar.Overlay.time:SetText(E:Round((i / BAR_NUM) * BAR_NUM, 2) .. "s")
				BA:UpdateBarValues(self[i].Bar.Overlay, i, BAR_NUM)
			end
			return
		end
		
		if not (BA.Auras[Unit] and BA.Auras[Unit][1]) and not self.AllHidden then BA:HideAll(Unit); return end
		
		-- Execute post-sort when needed
		if not self.UpdateInProgress and self.QueueAuraSort then
			tsort(BA.Auras[Unit], SortByExpiration)
			
			self.QueueAuraSort = nil
		end
		
		
		for i=1, BAR_NUM do
			-- If we don't have any auras of the unit, return
			if UnitExists(self.Unit) and BA.Auras[Unit] and BA.Auras[Unit][i] and BA.Auras[Unit][i][6] > 0 then
				
				self.CurrentBar = self[i]
				self.CurrentAura = BA.Auras[Unit][i]
				
				if self.CurrentAura then
					self.CurrentBar.duration = self.CurrentAura[6]
					self.CurrentBar.timeLeft = self.CurrentAura[7] - GetTime()
					
					if self.CurrentBar.timeLeft < 0 then self.CurrentAura[i] = nil else
						
						BA:UpdateBarValues(BA.Bars[Unit][i].Bar.Overlay, self.CurrentBar.timeLeft, self.CurrentBar.duration)
						
						-- Determine color
						self.AuraColor = BA:GetAuraColor(self.CurrentAura[5], Unit, BA.Auras[Unit].AuraType)
						
						-- Perform updates
						BA:UpdateBarColor(self.CurrentBar.Bar, self.AuraColor)
						BA:UpdateTexture(self.CurrentBar.Icon.Tex, self.CurrentAura[3])
						BA:UpdateName(self.CurrentBar.Bar.Overlay.name, self.CurrentAura)
						BA:UpdateTime(self.CurrentBar.Bar.Overlay.time, self.CurrentBar.timeLeft)
					end
				end
				if not self.CurrentBar:IsVisible() then self.CurrentBar:Show(); self.CurrentBar.IsHidden = nil; self.AllHidden = nil end
			else
				if self[i]:IsVisible() then self[i]:Hide() end
			end
		end
	end)
	
	BA:UpdateAuras(Unit)
end

function BA:CreateIcon(F, Name)
	F.Icon = CreateFrame("Frame", Name)
	F.Icon:SetPoint("LEFT", F, "LEFT")
	F.Icon:SetSize(BAR_SIZE_Y, BAR_SIZE_Y)
	F.Icon:SetParent(F)
	
	F.Icon:EnableMouse(true)
	
	F.Icon.Tex = F.Icon:CreateTexture(nil, "OVERLAY")
	F.Icon.Tex:SetAllPoints(F.Icon)
	
	E:SkinButtonIcon(F.Icon.Tex)
end

function BA:InitFonts(F)
	local FontType = "FRIZQT__.TTF"
	local Fonts = {["time"] = {"RIGHT", 100, 18, -5}, ["name"] = {"LEFT", 150, 18, 5}} -- Alignment, Width, Height, XOffset
	
	for n,v in pairs(Fonts) do
		F[n] = F:CreateFontString(nil, "ARTWORK")
		E:InitializeFontFrame(F[n], "ARTWORK", font, 11, {1,0.96,0.41}, 1, {0,0}, "", v[2], v[3], F, v[1], {1,1})
		F[n]:ClearAllPoints()
		F[n]:SetParent(F)
		F[n]:SetJustifyH(v[1])
		F[n]:SetPoint(v[1], F, v[1], v[4], 0)
	end
end

local AuraName, AuraSubName, AuraTexture, AuraCount, AuraDType, AuraDuration, AuraExpirationTime
local UnitAuraClass, CurrentAuraIndex
CurrentAuraIndex = 1
function BA:UpdateAuras(Unit)
	if not UnitExists(Unit) or not BA.Bars[Unit] then return end
	
	self.UpdateInProgress = true
	CurrentAuraIndex = 1
	
	-- Start with a clean table
	if BA.Auras[Unit] then wipe(BA.Auras[Unit]) end
	if not BA.Auras[Unit] then BA.Auras[Unit] = {} end
	
	if UnitCanAttack(Unit, "player") then UnitAuraClass = "HARMFUL"; else UnitAuraClass = "HELPFUL"; end
	
	-- Iterate until we reach the last auraID of the unit
	while true do
		
		if E:IsLegionClient() then
			AuraName, AuraSubName, AuraTexture, AuraCount, AuraDType, AuraDuration, AuraExpirationTime = UnitAura(Unit, CurrentAuraIndex, UnitAuraClass .. "|PLAYER")
		else
			AuraName, AuraTexture, AuraCount, AuraDType, AuraDuration, AuraExpirationTime = UnitAura(Unit, CurrentAuraIndex, UnitAuraClass .. "|PLAYER")
			AuraSubName = nil
		end
		if not AuraName then
			if BA.Auras[Unit] and BA.Auras[Unit][CurrentAuraIndex] then
				BA.Auras[Unit][CurrentAuraIndex] = nil
			end
			
			-- If aura would have been the first one
			if CurrentAuraIndex == 1 then
				wipe(BA.Auras[Unit])
			end
			
			break
		end
		
		if not BA.Auras[Unit][CurrentAuraIndex] then BA.Auras[Unit][CurrentAuraIndex] = {} end
		
		
		-- Used for tooltips
		BA.Auras[Unit][CurrentAuraIndex].RealIndex = CurrentAuraIndex
		
		-- Alphanumerical indexing for better sort results
		BA.Auras[Unit][CurrentAuraIndex][1] = AuraName
		BA.Auras[Unit][CurrentAuraIndex][2] = AuraSubName
		BA.Auras[Unit][CurrentAuraIndex][3] = AuraTexture
		BA.Auras[Unit][CurrentAuraIndex][4] = AuraCount
		BA.Auras[Unit][CurrentAuraIndex][5] = AuraDType
		BA.Auras[Unit][CurrentAuraIndex][6] = AuraDuration
		BA.Auras[Unit][CurrentAuraIndex][7] = AuraExpirationTime
				
		if UnitAuraClass == "HARMFUL" then BA.Auras[Unit].AuraType = "Debuff"; else BA.Auras[Unit].AuraType = "Buff"; end
		
		CurrentAuraIndex = CurrentAuraIndex + 1
	end
	
	self.UpdateInProgress = nil
	
	-- Remove uneccessary entries
	for i=#BA.Auras[Unit],1,-1 do
		-- Remove auras with a duration higher than 5 minutes or no duration at all
		if BA.Auras[Unit][i] and (BA.Auras[Unit][i][6] == 0 or BA.Auras[Unit][i][6] >= 300) then
			tremove(BA.Auras[Unit], i)
		end
	end
	
	-- Sort by expiration time
	if BA.Auras[Unit] and not self.UpdateInProgress then
		tsort(BA.Auras[Unit], SortByExpiration)
	elseif self.UpdateInProgress then
		self.QueueAuraSort = true
	end
end

function BA:Init()
	CO = E:GetModule("Config")
	self.db = CO.db.profile.auras
	
	BA.E:RegisterEvent("UNIT_AURA")
	BA.E:RegisterEvent("PLAYER_TARGET_CHANGED")
	BA.E:SetScript("OnEvent", function(self, event, ...)
		if event == "PLAYER_TARGET_CHANGED" then BA:UpdateAuras("target") else
			BA:UpdateAuras(...)
		end
	end)
	
	BA:CreateBars("player")
	BA:CreateBars("target")
end

E:AddModule("Bar_Auras", BA)