--[[
# Element: Runes

Handles the visibility and updating of Death Knight's runes.

## Widget

Runes - An `table` holding `StatusBar`s.

## Notes

A default texture will be applied if the sub-widgets are StatusBars and don't have a texture set.

## Options

.colorSpec - Use `self.colors.runes[specID]` to color the bar based on player's spec. `specID` is defined by the return
             value of [C_SpecializationInfo.GetSpecialization](https://warcraft.wiki.gg/wiki/API_C_SpecializationInfo.GetSpecialization) (boolean)
.sortOrder - Sorting order. Sorts by the remaining cooldown time, 'asc' - from the least cooldown time remaining (fully
             charged) to the most (fully depleted), 'desc' - the opposite (string?)['asc', 'desc']

## Examples

    local Runes = {}
    for index = 1, 6 do
        -- Position and size of the rune bar indicators
        local Rune = CreateFrame('StatusBar', nil, self)
        Rune:SetSize(120 / 6, 20)
        Rune:SetPoint('TOPLEFT', self, 'BOTTOMLEFT', index * 120 / 6, 0)

        Runes[index] = Rune
    end

    -- Register with oUF
    self.Runes = Runes
--]]

local _, ns = ...
local oUF = ns.oUF

local runemap = { 1, 2, 3, 4, 5, 6 }
local hasSortOrder = false

local function onUpdate(self, elapsed)
	local duration = self.duration + elapsed
	self.duration = duration
	self:SetValue(duration)
end

local function ascSort(runeAID, runeBID)
	local runeAStart, _, runeARuneReady = GetRuneCooldown(runeAID)
	local runeBStart, _, runeBRuneReady = GetRuneCooldown(runeBID)
	if runeARuneReady ~= runeBRuneReady then
		return runeARuneReady
	elseif runeAStart ~= runeBStart then
		return runeAStart < runeBStart
	else
		return runeAID < runeBID
	end
end

local function descSort(runeAID, runeBID)
	local runeAStart, _, runeARuneReady = GetRuneCooldown(runeAID)
	local runeBStart, _, runeBRuneReady = GetRuneCooldown(runeBID)
	if runeARuneReady ~= runeBRuneReady then
		return runeBRuneReady
	elseif runeAStart ~= runeBStart then
		return runeAStart > runeBStart
	else
		return runeAID > runeBID
	end
end

local function UpdateColor(self, event)
	local element = self.Runes

	local spec = C_SpecializationInfo.GetSpecialization() or 0

	local color
	if spec > 0 and spec < 4 and element.colorSpec then
		color = self.colors.runes[spec]
	else
		color = self.colors.power.RUNES
	end

	if color then
		for index = 1, #element do
			element[index]:SetStatusBarColor(color:GetRGB())
		end
	end

	--[[ Callback: Runes:PostUpdateColor(color)
	Called after the element color has been updated.

	* self - the Runes element
	* color - the used ColorMixin-based object (table?)
	--]]
	if element.PostUpdateColor then
		element:PostUpdateColor(color)
	end
end

local function ColorPath(self, ...)
	--[[ Override: Runes.UpdateColor(self, event, ...)
	Used to completely override the internal function for updating the widgets' colors.

	* self  - the parent object
	* event - the event triggering the update (string)
	* ...   - the arguments accompanying the event
	--]]
	(self.Runes.UpdateColor or UpdateColor)(self, ...)
end

local function Update(self, event)
	local element = self.Runes

	if element.sortOrder == 'asc' then
		table.sort(runemap, ascSort)
		hasSortOrder = true
	elseif element.sortOrder == 'desc' then
		table.sort(runemap, descSort)
		hasSortOrder = true
	elseif hasSortOrder then
		table.sort(runemap)
		hasSortOrder = false
	end

	local currentTime = GetTime()
	local rune, start, duration, runeReady
	for index, runeID in next, runemap do
		rune = element[index]
		if not rune then
			break
		end

		if UnitHasVehicleUI('player') then
			rune:Hide()
		else
			start, duration, runeReady = GetRuneCooldown(runeID)
			if runeReady then
				rune:SetMinMaxValues(0, 1)
				rune:SetValue(1)
				rune:SetScript('OnUpdate', nil)
			elseif start then
				rune.duration = currentTime - start
				rune:SetMinMaxValues(0, duration)
				rune:SetValue(0)
				rune:SetScript('OnUpdate', onUpdate)
			end

			rune:Show()
		end
	end

	--[[ Callback: Runes:PostUpdate(runemap)
	Called after the element has been updated.

	* self    - the Runes element
	* runemap - the ordered list of runes' indices (table)
	--]]
	if element.PostUpdate then
		return element:PostUpdate(runemap)
	end
end

local function Path(self, ...)
	--[[ Override: Runes.Override(self, event, ...)
	Used to completely override the internal update function.

	* self  - the parent object
	* event - the event triggering the update (string)
	* ...   - the arguments accompanying the event
	--]]
	(self.Runes.Override or Update)(self, ...)
end

local function AllPath(...)
	Path(...)
	ColorPath(...)
end

local function ForceUpdate(element)
	Path(element.__owner, 'ForceUpdate')
	ColorPath(element.__owner, 'ForceUpdate')
end

local function Disable(self)
	local element = self.Runes
	if element then
		for i = 1, #element do
			element[i]:Hide()
		end

		self:UnregisterEvent('PLAYER_SPECIALIZATION_CHANGED', ColorPath)
		self:UnregisterEvent('RUNE_POWER_UPDATE', Path)
	end
end

local function Enable(self, unit)
	if UnitClassBase('player') ~= 'DEATHKNIGHT' then
		Disable(self)

		return false
	end

	local element = self.Runes
	if element and C_Secrets.CanCompareUnitTokens(unit, 'player') and UnitIsUnit(unit, 'player') then
		element.__owner = self
		element.ForceUpdate = ForceUpdate

		for i = 1, #element do
			local rune = element[i]
			if rune:IsObjectType('StatusBar') and not rune:GetStatusBarTexture() then
				rune:SetStatusBarTexture([[Interface\TargetingFrame\UI-StatusBar]])
			end
		end

		self:RegisterEvent('PLAYER_SPECIALIZATION_CHANGED', ColorPath)
		self:RegisterEvent('RUNE_POWER_UPDATE', Path, true)

		return true
	end
end

oUF:AddElement('Runes', AllPath, Enable, Disable)
