--[[--------------------------------------------------------------------------
	Boom - Modern Healing & Support Raid Frames
	Data/ClassData.lua

	Class, specialization, power and debuff-type reference data for 12.0.7.

	Two things here are easy to get wrong on this patch:

	  * Demon Hunter has a FOURTH specialization in Midnight - Devourer (1480),
	    a ranged Intellect DPS spec. Any table that assumes three specs per class
	    silently drops it.

	  * DebuffTypeColor no longer exists. It was removed from the client UI and
	    replaced by AuraUtil's DEBUFF_DISPLAY_INFO. Addons still indexing the old
	    global error outright, so Boom asks AuraUtil when it can and falls back to
	    its own palette when it cannot.
----------------------------------------------------------------------------]]

local ADDON_NAME, Boom = ...

local ClassData = Boom:NewModule("ClassData")

local pairs, ipairs = pairs, ipairs

--------------------------------------------------------------------------------
-- Classes and specializations
--
-- The "Initial" specs are the placeholder a character carries before choosing.
-- They are listed so a low level character never falls through a nil lookup.
--------------------------------------------------------------------------------

ClassData.classes = {
	WARRIOR     = { id = 1,  specs = { { 71, "Arms", "DAMAGER" }, { 72, "Fury", "DAMAGER" }, { 73, "Protection", "TANK" }, { 1446, "Initial", "DAMAGER" } } },
	PALADIN     = { id = 2,  specs = { { 65, "Holy", "HEALER" }, { 66, "Protection", "TANK" }, { 70, "Retribution", "DAMAGER" }, { 1451, "Initial", "DAMAGER" } } },
	HUNTER      = { id = 3,  specs = { { 253, "Beast Mastery", "DAMAGER" }, { 254, "Marksmanship", "DAMAGER" }, { 255, "Survival", "DAMAGER" }, { 1448, "Initial", "DAMAGER" } } },
	ROGUE       = { id = 4,  specs = { { 259, "Assassination", "DAMAGER" }, { 260, "Outlaw", "DAMAGER" }, { 261, "Subtlety", "DAMAGER" }, { 1453, "Initial", "DAMAGER" } } },
	PRIEST      = { id = 5,  specs = { { 256, "Discipline", "HEALER" }, { 257, "Holy", "HEALER" }, { 258, "Shadow", "DAMAGER" }, { 1452, "Initial", "DAMAGER" } } },
	DEATHKNIGHT = { id = 6,  specs = { { 250, "Blood", "TANK" }, { 251, "Frost", "DAMAGER" }, { 252, "Unholy", "DAMAGER" }, { 1455, "Initial", "DAMAGER" } } },
	SHAMAN      = { id = 7,  specs = { { 262, "Elemental", "DAMAGER" }, { 263, "Enhancement", "DAMAGER" }, { 264, "Restoration", "HEALER" }, { 1444, "Initial", "DAMAGER" } } },
	MAGE        = { id = 8,  specs = { { 62, "Arcane", "DAMAGER" }, { 63, "Fire", "DAMAGER" }, { 64, "Frost", "DAMAGER" }, { 1449, "Initial", "DAMAGER" } } },
	WARLOCK     = { id = 9,  specs = { { 265, "Affliction", "DAMAGER" }, { 266, "Demonology", "DAMAGER" }, { 267, "Destruction", "DAMAGER" }, { 1454, "Initial", "DAMAGER" } } },
	MONK        = { id = 10, specs = { { 268, "Brewmaster", "TANK" }, { 269, "Windwalker", "DAMAGER" }, { 270, "Mistweaver", "HEALER" }, { 1450, "Initial", "DAMAGER" } } },
	DRUID       = { id = 11, specs = { { 102, "Balance", "DAMAGER" }, { 103, "Feral", "DAMAGER" }, { 104, "Guardian", "TANK" }, { 105, "Restoration", "HEALER" }, { 1447, "Initial", "DAMAGER" } } },
	DEMONHUNTER = { id = 12, specs = { { 577, "Havoc", "DAMAGER" }, { 581, "Vengeance", "TANK" }, { 1480, "Devourer", "DAMAGER" }, { 1456, "Initial", "DAMAGER" } } },
	EVOKER      = { id = 13, specs = { { 1467, "Devastation", "DAMAGER" }, { 1468, "Preservation", "HEALER" }, { 1473, "Augmentation", "DAMAGER" }, { 1465, "Initial", "DAMAGER" } } },
}

-- Flat lookups, built once at load.
ClassData.specs       = {}   -- specID -> { class, classID, name, role }
ClassData.classSpecs  = {}   -- classToken -> array of specIDs
ClassData.healerSpecs = {}
ClassData.tankSpecs   = {}

for token, class in pairs(ClassData.classes) do
	local list = {}
	for _, spec in ipairs(class.specs) do
		local specID, name, role = spec[1], spec[2], spec[3]

		ClassData.specs[specID] = {
			class   = token,
			classID = class.id,
			name    = name,
			role    = role,
		}

		list[#list + 1] = specID
		if role == "HEALER" then ClassData.healerSpecs[specID] = true end
		if role == "TANK"   then ClassData.tankSpecs[specID]   = true end
	end
	ClassData.classSpecs[token] = list
end

--------------------------------------------------------------------------------
-- Queries
--------------------------------------------------------------------------------

function ClassData:GetSpecInfo(specID)
	return specID and self.specs[specID] or nil
end

function ClassData:GetPlayerSpecID()
	return Boom.Config:GetPlayerSpecID()
end

function ClassData:IsHealerSpec(specID)
	return specID and self.healerSpecs[specID] or false
end

function ClassData:IsTankSpec(specID)
	return specID and self.tankSpecs[specID] or false
end

function ClassData:GetSpecName(specID)
	local info = self.specs[specID]
	return info and info.name or nil
end

--- Localised spec name straight from the client, when one is available.
function ClassData:GetLocalizedSpecName(specID)
	if _G.GetSpecializationInfoByID then
		local _, name = _G.GetSpecializationInfoByID(specID)
		if name then return name end
	end
	return self:GetSpecName(specID)
end

--------------------------------------------------------------------------------
-- Power colours
--------------------------------------------------------------------------------

local P = Enum.PowerType

ClassData.powerColors = {
	[P and P.Mana        or 0]  = { 0.25, 0.45, 0.95 },
	[P and P.Rage        or 1]  = { 0.90, 0.15, 0.15 },
	[P and P.Focus       or 2]  = { 1.00, 0.50, 0.25 },
	[P and P.Energy      or 3]  = { 1.00, 0.90, 0.35 },
	[P and P.ComboPoints or 4]  = { 1.00, 0.96, 0.41 },
	[P and P.Runes       or 5]  = { 0.50, 0.50, 0.50 },
	[P and P.RunicPower  or 6]  = { 0.00, 0.82, 1.00 },
	[P and P.Insanity    or 13] = { 0.40, 0.00, 0.80 },
	[P and P.Maelstrom   or 11] = { 0.00, 0.50, 1.00 },
	[P and P.Fury        or 17] = { 0.79, 0.26, 0.99 },
	[P and P.Pain        or 18] = { 1.00, 0.61, 0.00 },
	[P and P.LunarPower  or 8]  = { 0.30, 0.52, 0.90 },
	[P and P.HolyPower   or 9]  = { 0.95, 0.90, 0.60 },
	[P and P.Chi         or 12] = { 0.71, 1.00, 0.92 },
	[P and P.SoulShards  or 7]  = { 0.50, 0.32, 0.55 },
	[P and P.ArcaneCharges or 16] = { 0.10, 0.10, 0.98 },
	[P and P.Essence     or 19] = { 0.35, 0.75, 0.80 },
}

function ClassData:GetPowerColor(powerType, classToken)
	local color = powerType and self.powerColors[powerType]
	if color then return color[1], color[2], color[3] end

	if classToken then
		return Boom.Util.GetClassColorByToken(classToken)
	end
	return 0.4, 0.4, 0.8
end

--------------------------------------------------------------------------------
-- Debuff types
--
-- Bleed became a real, dispellable type in Midnight, so there are five, not the
-- four every older addon assumes.
--------------------------------------------------------------------------------

ClassData.dispelTypes = { "Magic", "Curse", "Disease", "Poison", "Bleed" }

-- Fallback palette, used when AuraUtil is unavailable. Values chosen to match
-- the client's own debuff border tints closely enough to be unsurprising.
ClassData.debuffTypeColors = {
	Magic   = { 0.20, 0.60, 1.00 },
	Curse   = { 0.60, 0.00, 1.00 },
	Disease = { 0.60, 0.40, 0.00 },
	Poison  = { 0.00, 0.60, 0.00 },
	Bleed   = { 0.80, 0.13, 0.13 },
	None    = { 0.80, 0.00, 0.00 },
}

-- An aura with no dispel school reports nil, and different call sites spell the
-- absent case differently. Alias both so neither has to normalise.
ClassData.debuffTypeColors.none = ClassData.debuffTypeColors.None

--- Shared, cached colour table for a dispel type, seeded from the client's own
--- palette on first use. Returns the SAME table every call - it is read on the
--- indicator hot path, so it must never allocate.
local colorTableCache = {}

function ClassData:GetDebuffColorTable(dispelType)
	local key = (dispelType and dispelType ~= "") and dispelType or "None"

	local cached = colorTableCache[key]
	if cached then return cached end

	local r, g, b = self:GetDebuffTypeColor(key)
	cached = { r, g, b }
	colorTableCache[key] = cached
	return cached
end

--- Colour for a dispel type. Prefers the client's own table so Boom follows any
--- future retint (and the colourblind mode) automatically.
function ClassData:GetDebuffTypeColor(dispelType)
	local key = (dispelType and dispelType ~= "") and dispelType or "None"

	if AuraUtil and AuraUtil.GetDebuffDisplayInfoTable then
		local ok, info = pcall(AuraUtil.GetDebuffDisplayInfoTable)
		if ok and info and info[key] and info[key].color then
			local c = info[key].color
			return c.r, c.g, c.b
		end
	end

	local color = self.debuffTypeColors[key] or self.debuffTypeColors.None
	return color[1], color[2], color[3]
end

--------------------------------------------------------------------------------
-- Raid target markers
--------------------------------------------------------------------------------

--- Point a texture at raid marker `index`, or hide it when there is none.
--- SetRaidTargetIconTexture is still a live global in 12.0.7 and handles the
--- sprite sheet arithmetic for us.
function ClassData:SetRaidTargetTexture(texture, index)
	if not index then
		texture:Hide()
		return false
	end

	if _G.SetRaidTargetIconTexture then
		_G.SetRaidTargetIconTexture(texture, index)
	else
		texture:SetTexture(Boom.Media.ICONS.raidTargets)
		local left, right, top, bottom = Boom.Media:GetRaidTargetCoords(index)
		if left then texture:SetTexCoord(left, right, top, bottom) end
	end

	texture:Show()
	return true
end
