--[[

	oUF Element: .AuraBars

	Options regarding visual layout:
	 -	<element>.auraBarHeight
			Sets the height of the statusbars and icons.
	 -	<element>.auraBarWidth
			Sets the width of the statusbars (excluding icon). Will use the
			framewidth of <element> by default.
	 -	<element>.auraBarTexture
			Sets the statusbar texture.
	 -	<element>.fgalpha
			Foreground alpha.
	 -	<element>.bgalpha
			Background alpha.
	 -	<element>.spellTimeObject, <element>.spellNameObject
			Objects passed by CreateFontObject(). These will ignore the
			following options:
			<element>.spellTimeFont, <element>.spellTimeSize
			<element>.spellNameFont, <element>.spellNameSize
	 -	<element>.spellTimeFont, <element>.spellTimeSize,
		<element>.spellNameFont, <element>.spellNameSize
			Options to control the texts on the statusbars.
	 -	<element>.gap
			Will add space between the statusbars and icons by amount of .gap
			in pixels.
	 -	<element>.spacing
			Will add space between statusbars by amount of .spacing in pixels.

	Options regarding functionality:
	 -	<element>.down
			Will let the aurabars grow downwards.
	 -	<element>.filter(name, rank, icon, count, debuffType, duration, expirationTime, unitCaster, isStealable)
			Use this to filter out specific casts.
	 -	<element>.sort
			Will enable sorting if set to true or 1 (or whatever). See
			functions for info on how to override the sort function.
	 -	<element>.scaleTime
			Will add time-scaling (bar widths according to the total duration
			of the aura assigned to it). Will use the minimum of
			<element>.scaleTime and the total aura duration to determine the
			width in percent.

	Functions that can be overridden from within a layout:
	 -	<element>.PostCreateBar(bar)
			To do stuff to a bar once it has been created, such as set a
			backdrop, etc, use this function. Use bar:GetParent() to get the
			<element> object.
	 -	<element>.sort(a, b)
			Custom compare function to sort aura's before the bars are being
			updated. Is being called every UNIT_AURA for the <element>'s unit.
--]]

local _, ns = ...
local oUF = ns.oUF or oUF
assert(oUF, 'oUF_AuraBars was unable to locate oUF install.')

--[[
		Shortens an spell name to a given length
]]--
local function ShortenedSpellName(spellName, length)
	return string.len(spellName) > length and string.gsub(spellName, '%s?(.)%S+%s', '%1. ') or spellName
end

--[[
		Rounds a number to a given number of decimal places.
]]--
local function Round(number, decimalPlaces)
	if decimalPlaces and decimalPlaces > 0 then
		local mult = 10^decimalPlaces
		return math.floor(number * mult + .5) / mult
	end
	return math.floor(num + .5)
end

--[[
		Formats time in seconds to either
		h:m
		m:s
		s
]]--
local function FormatTime(timeInSec)
	local h = math.floor(timeInSec / 3600)
	local m = math.floor((timeInSec - (3600 * h)) / 60)
	local s = math.floor(timeInSec - ((3600 * h) + (60 * m)))
	if h > 0 then
		return h .. ":" .. m .. "h"
	elseif m > 0 then
		return m .. "m"
	else
		return s .. "s"
	end
end

--[[
		Tooltip functions
]]--
local UpdateTooltip = function(self)
	GameTooltip:SetUnitAura(self.unit, self:GetID(), self.filter)
end

local OnEnter = function(self)
	if(not self:IsVisible()) then return end

	GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT")
	self:UpdateTooltip()
end

local OnLeave = function()
	GameTooltip:Hide()
end

--[[
		Creates a bar to represent an aura
]]--
local function CreateAuraBar(oUF, anchor)
	local auraBarParent = oUF.AuraBars

	-- the main bar
	local statusBar = CreateFrame("StatusBar", nil, auraBarParent)
	statusBar:SetHeight(auraBarParent.auraBarHeight or 20)
	statusBar:SetWidth((auraBarParent.auraBarWidth or auraBarParent:GetWidth()) - (statusBar:GetHeight() + (auraBarParent.gap or 0)))
	statusBar:SetStatusBarTexture(auraBarParent.auraBarTexture or [[Interface\TargetingFrame\UI-StatusBar]])
	statusBar:SetStatusBarColor(0, .5, 0)
	statusBar:SetAlpha(auraBarParent.fgalpha or 1)

	--Tooltip
	statusBar.UpdateTooltip = UpdateTooltip
	statusBar:SetScript("OnEnter", OnEnter)
	statusBar:SetScript("OnLeave", OnLeave)
	
	-- the background
	statusBar.bg = statusBar:CreateTexture(nil, "BORDER")
	statusBar.bg:SetAllPoints(statusBar)
	statusBar.bg:SetTexture(auraBarParent.auraBarTexture or [[Interface\TargetingFrame\UI-StatusBar]])
	statusBar.bg:SetVertexColor(.5, 1, .5)
	statusBar.bg:SetAlpha(auraBarParent.bgalpha or 1)

	if auraBarParent.down == true then
		if auraBarParent == anchor then -- Root frame so indent for icon
			statusBar:SetPoint('TOPLEFT', anchor, 'BOTTOMLEFT', (statusBar:GetHeight() + (auraBarParent.gap or 0) ), 0)
		else
			statusBar:SetPoint('TOPLEFT', anchor, 'BOTTOMLEFT', 0, (-auraBarParent.spacing or 0))
		end
	else
		if auraBarParent == anchor then -- Root frame so indent for icon
			statusBar:SetPoint('BOTTOMLEFT', anchor, 'TOPLEFT', (statusBar:GetHeight() + (auraBarParent.gap or 0) ), 0)
		else
			statusBar:SetPoint('BOTTOMLEFT', anchor, 'TOPLEFT', 0, (auraBarParent.spacing or 0))
		end
	end

	statusBar.icon = statusBar:CreateTexture(nil, 'BACKGROUND')
	statusBar.icon:SetHeight(statusBar:GetHeight())
	statusBar.icon:SetWidth(statusBar:GetHeight())
	statusBar.icon:SetTexCoord(.07, .93, .07, .93)
	statusBar.icon:SetPoint'TOP'
	statusBar.icon:SetPoint('LEFT', auraBarParent)
	
	statusBar.spelltime = statusBar:CreateFontString(nil, 'ARTWORK')
	if auraBarParent.spellTimeObject then
		statusBar.spelltime:SetFontObject(auraBarParent.spellTimeObject)
	else
		statusBar.spelltime:SetFont(auraBarParent.spellTimeFont or [[Fonts\FRIZQT__.TTF]], auraBarParent.spellTimeSize or 10)
	end
	statusBar.spelltime:SetTextColor(1 ,1, 1)
	statusBar.spelltime:SetJustifyH'RIGHT'
	statusBar.spelltime:SetJustifyV'CENTER'
	statusBar.spelltime:SetPoint'RIGHT'

	statusBar.spellname = statusBar:CreateFontString(nil, 'ARTWORK')
	if auraBarParent.spellNameObject then
		statusBar.spellname:SetFontObject(auraBarParent.spellNameObject)
	else
		statusBar.spellname:SetFont(auraBarParent.spellNameFont or [[Fonts\FRIZQT__.TTF]], auraBarParent.spellNameSize or 10)
	end
	statusBar.spellname:SetTextColor(1, 1, 1)
	statusBar.spellname:SetJustifyH'LEFT'
	statusBar.spellname:SetJustifyV'CENTER'
	statusBar.spellname:SetPoint'LEFT'
	statusBar.spellname:SetPoint('RIGHT', statusBar.spelltime, 'LEFT')

	if auraBarParent.PostCreateBar then
		auraBarParent.PostCreateBar(statusBar)
	end
	
	return statusBar
end

--[[
		Update all visable bars, if a bar's aura expires there is no need to remove expired
		auras, as the Update function will be triggered when the buff drops
]]--
local function UpdateBars(auraBars)
	local bars = auraBars.bars
	local timenow = GetTime()

	for index = 1, #bars do
		local bar = bars[index]
		if not bar:IsVisible() then
			break
		end
		if bar.aura.noTime then
			bar.spelltime:SetText()
		else
			local timeleft = bar.aura.expirationTime - timenow
			bar:SetValue(timeleft)
			bar.spelltime:SetText(FormatTime(timeleft))
		end
	end
end

--[[
		Default filter
]]--
local function DefaultFilter(name, rank, icon, count, debuffType, duration, expirationTime, unitCaster, isStealable, spellID)
	if unitCaster == 'player' then
		return true
	end
end

local sort = function(a, b)
	local compa, compb = a.noTime and math.huge or a.expirationTime, b.noTime and math.huge or b.expirationTime
	return compa > compb
end

--[[
		Main update function, gathers the buffs/debuffs base on a filter.  Then for each buff/debuff a status bar is shown
		monitoring it's remaining time.
]]--
local function Update(self, event, unit)
	if self.unit ~= unit then return end
	--Default to Show All
	local helpOrHarm = UnitIsFriend('player', unit) and 'HELPFUL|PLAYER' or 'HARMFUL|PLAYER'
	
	if self.AuraBars.Buffs then helpOrHarm = UnitIsFriend('player', unit) and 'HELPFUL|PLAYER' end
	if self.AuraBars.Debuffs then helpOrHarm = 'HARMFUL|PLAYER' end
	
	-- Create a table of auras to display
	local auras = {}
	local lastAuraIndex = 0
	for index = 1, 40 do
		-- local name, rank, icon, count, debuffType, duration, expirationTime, unitCaster, isStealable = UnitAura(unit, index, helpOrHarm)
		local name, rank, icon, count, debuffType, duration, expirationTime, unitCaster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff = UnitAura(unit, index, helpOrHarm)
		if not name then break end
		
		if (self.AuraBars.filter or DefaultFilter)(name, rank, icon, count, debuffType, duration, expirationTime, unitCaster, isStealable, spellID) then
			lastAuraIndex = lastAuraIndex + 1
			auras[lastAuraIndex] = {}
			auras[lastAuraIndex].index = index
			auras[lastAuraIndex].name = name
			auras[lastAuraIndex].rank = rank
			auras[lastAuraIndex].icon = icon
			auras[lastAuraIndex].count = count
			auras[lastAuraIndex].debuffType = debuffType
			auras[lastAuraIndex].duration = duration
			auras[lastAuraIndex].expirationTime = expirationTime
			auras[lastAuraIndex].unitCaster = unitCaster
			auras[lastAuraIndex].isStealable = isStealable
			auras[lastAuraIndex].noTime = (duration == 0 and expirationTime == 0)
		end
	end

	if self.AuraBars.sort then
		table.sort(auras, type(self.AuraBars.sort) == 'function' and self.AuraBars.sort or sort)
	end
	
	-- Show and configure bars for buffs/debuffs.
	local bars = self.AuraBars.bars
	for index = 1 , lastAuraIndex do
		local aura = auras[index]
		local bar = bars[index]
		
		if not bar then
			bar = CreateAuraBar(self, index == 1 and self.AuraBars or bars[index - 1])
			bar.unit = unit
			bars[index] = bar
		end

		-- Backup the details of the aura onto the bar, so the OnUpdate function can use it
		bar.aura = aura
		--Tooltip information
		bar:EnableMouse(true)
		bar.filter = helpOrHarm
		bar:SetID(bar.aura.index)

		-- Configure
		if bar.aura.noTime then
			bar:SetMinMaxValues(0, 1)
			bar:SetValue(1)
		else
			if self.AuraBars.scaleTime then
				local maxvalue = math.min(self.AuraBars.scaleTime, bar.aura.duration)
				bar:SetMinMaxValues(0, maxvalue)
				bar:SetWidth(
					( maxvalue / self.AuraBars.scaleTime ) *
					(	( self.AuraBars.auraBarWidth or self.AuraBars:GetWidth() ) -
						( bar:GetHeight() + (self.AuraBars.gap or 0) ) ) ) 				-- icon size + gap
			else
				bar:SetMinMaxValues(0, bar.aura.duration)
			end
			bar:SetValue(bar.aura.expirationTime - GetTime())
		end

		bar.icon:SetTexture(bar.aura.icon)

		bar.spellname:SetText(bar.aura.count > 1 and string.format("%s [%d]", ShortenedSpellName(bar.aura.name, 20), bar.aura.count) or ShortenedSpellName(bar.aura.name, 20))
		bar.spelltime:SetText(not bar.noTime and FormatTime(bar.aura.expirationTime-GetTime()))

		-- Colour bars
		local r, g, b = .2, .6, 1 -- Colour for buffs (This is Magic Blue)
		local bgMod = 0.5
		if helpOrHarm == 'HARMFUL' then
			local debuffType = bar.aura.debuffType and bar.aura.debuffType or 'none'
			r, g, b = DebuffTypeColor[debuffType].r, DebuffTypeColor[debuffType].g, DebuffTypeColor[debuffType].b
		end
		bar:SetStatusBarColor(r, g, b)
		bar.bg:SetVertexColor(r * bgMod, g * bgMod, b * bgMod)
		bar:Show()
	end

	-- Hide unused bars.
	for index = lastAuraIndex + 1, #bars do
		bars[index]:Hide()
	end
end

--[[
	Enable function for oUF
]]--
local function Enable(self)
	if self.AuraBars then
		self:RegisterEvent('UNIT_AURA', Update)
		self.AuraBars.bars = self.AuraBars.bars or {}
		self.AuraBars:SetScript('OnUpdate', UpdateBars)
		return true -- Ensure we get an update when the parent frame is displayed
	end
end

--[[
	Disable function for oUF
]]--
local function Disable(self)
	local auraFrame = self.AuraBars
	if auraFrame then
		self:UnregisterEvent('UNIT_AURA', Update)
		auraFrame:SetScript'OnUpdate'
	end
end

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