-- ------------------------------------------------------------------------------ --
--                                TradeSkillMaster                                --
--                          https://tradeskillmaster.com                          --
--    All Rights Reserved - Detailed license information included with addon.     --
-- ------------------------------------------------------------------------------ --

--- Theme Functions.
-- @module Theme

local _, TSM = ...
local Theme = TSM.Init("Util.Theme")
local FontPaths = TSM.Include("Data.FontPaths")
local Table = TSM.Include("Util.Table")
local Color = TSM.Include("Util.Color")
local FontObject = TSM.Include("Util.FontObject")
local private = {
	callbacks = {},
	names = {},
	colorSets = {},
	currentColorSet = nil,
	fontFrame = nil,
	currentFontSet = nil,
}
local THEME_COLOR_KEYS = {
	FRAME_BG = true,
	PRIMARY_BG = true,
	PRIMARY_BG_ALT = true,
	ACTIVE_BG = true,
	ACTIVE_BG_ALT = true,
}
local STATIC_COLORS = {
	INDICATOR = Color.NewFromHex("#ffd839"),
	INDICATOR_ALT = Color.NewFromHex("#79a2ff"),
	INDICATOR_DISABLED = Color.NewFromHex("#6f5819"),

	TEXT = Color.NewFromHex("#ffffff"),
	TEXT_ALT = Color.NewFromHex("#e2e2e2"),
	TEXT_DISABLED = Color.NewFromHex("#424242"),
}
local FEEDBACK_COLORS = {
	RED = Color.NewFromHex("#f72d20"),
	YELLOW = Color.NewFromHex("#e1f720"),
	GREEN = Color.NewFromHex("#4ff720"),
	BLUE = Color.NewFromHex("#2076f7"),
	ORANGE = Color.NewFromHex("#f77a20"),
}
local BLIZZARD_COLOR = Color.NewFromHex("#00b4ff")
local GROUP_COLORS = {
	Color.NewFromHex("#fcf141"),
	Color.NewFromHex("#bdaec6"),
	Color.NewFromHex("#06a2cb"),
	Color.NewFromHex("#ffb85c"),
	Color.NewFromHex("#51b599"),
}
local PROFESSION_DIFFICULTY_COLORS = {
	optimal = Color.NewFromHex("#ff8040"),
	medium = Color.NewFromHex("#ffff00"),
	easy = Color.NewFromHex("#40c040"),
	trivial = Color.NewFromHex("#808080"),
	header = Color.NewFromHex("#ffd100"),
	subheader = Color.NewFromHex("#ffd100"),
	nodifficulty = Color.NewFromHex("#f5f5f5"),
}
-- NOTE: there is a global ITEM_QUALITY_COLORS so we need to use another name
local TSM_ITEM_QUALITY_COLORS = {
	[0] = Color.NewFromHex("#9d9d9d"),
	[1] = Color.NewFromHex("#ffffff"),
	[2] = Color.NewFromHex("#1eff00"),
	[3] = Color.NewFromHex("#0070dd"),
	[4] = Color.NewFromHex("#a334ee"),
	[5] = Color.NewFromHex("#ff8000"),
	[6] = Color.NewFromHex("#e6cc80"),
	[7] = Color.NewFromHex("#00ccff"),
	[8] = Color.NewFromHex("#00ccff"),
}
local AUCTION_PCT_COLORS = {
	{ -- blue
		color = "BLUE",
		value = 50,
	},
	{ -- green
		color = "GREEN",
		value = 80,
	},
	{ -- yellow
		color = "YELLOW",
		value = 110,
	},
	{ -- orange
		color = "ORANGE",
		value = 135,
	},
	{ -- red
		color = "RED",
		value = math.huge,
	},
	default = "TEXT",
	bid = "TEXT_ALT",
}
local CONSTANTS = {
	COL_SPACING = 8,
	SCROLLBAR_MARGIN = 4,
	SCROLLBAR_WIDTH = 4,
	MOUSE_WHEEL_SCROLL_AMOUNT = 60,
}



-- ============================================================================
-- Module Loading
-- ============================================================================

Theme:OnModuleLoad(function()
	Table.SetReadOnly(STATIC_COLORS)
	Table.SetReadOnly(FEEDBACK_COLORS)
	Table.SetReadOnly(GROUP_COLORS)
	Table.SetReadOnly(PROFESSION_DIFFICULTY_COLORS)
	Table.SetReadOnly(TSM_ITEM_QUALITY_COLORS)
	Table.SetReadOnly(CONSTANTS)

	-- create a frame to load fonts
	private.fontFrame = CreateFrame("Frame", nil, UIParent)
	private.fontFrame.texts = {}
	private.fontFrame:SetAllPoints()
	private.fontFrame:SetScript("OnUpdate", private.FontFrameOnUpdate)

	-- TODO: eventually allow for different font sets?
	private.currentFontSet = {
		HEADING_H5 = FontObject.New(FontPaths.GetBodyRegular(), 20, 28),
		BODY_BODY1 = FontObject.New(FontPaths.GetBodyRegular(), 16, 24),
		BODY_BODY1_BOLD = FontObject.New(FontPaths.GetBodyBold(), 16, 24),
		BODY_BODY2 = FontObject.New(FontPaths.GetBodyRegular(), 14, 20),
		BODY_BODY2_MEDIUM = FontObject.New(FontPaths.GetBodyMedium(), 14, 20),
		BODY_BODY2_BOLD = FontObject.New(FontPaths.GetBodyBold(), 14, 20),
		BODY_BODY3 = FontObject.New(FontPaths.GetBodyRegular(), 12, 20),
		BODY_BODY3_MEDIUM = FontObject.New(FontPaths.GetBodyMedium(), 12, 20),
		ITEM_BODY1 = FontObject.New(FontPaths.GetItem(), 16, 24),
		ITEM_BODY2 = FontObject.New(FontPaths.GetItem(), 14, 20),
		ITEM_BODY3 = FontObject.New(FontPaths.GetItem(), 12, 20),
		TABLE_TABLE1 = FontObject.New(FontPaths.GetTable(), 12, 20),
	}

	-- load the fonts
	for _, obj in pairs(private.currentFontSet) do
		local fontPath = obj:GetWowFont()
		private.QueueFontLoad(fontPath)
	end
end)


-- ============================================================================
-- Module Functions
-- ============================================================================

--- Registers a callback when the theme changes.
-- @tparam function callback The callback function
function Theme.RegisterChangeCallback(callback)
	assert(type(callback) == "function")
	tinsert(private.callbacks, callback)
end

--- Registers a new color set.
-- @tparam string key The key which represents the color set
-- @tparam string name The name of the color set
-- @tparam table colorSet The colors which make up the color set (with keys specified in `THEME_COLOR_KEYS`)
function Theme.RegisterColorSet(key, name, colorSet)
	assert(not private.colorSets[key])
	for k in pairs(THEME_COLOR_KEYS) do
		assert(Color.IsInstance(colorSet[k]))
	end
	private.names[key] = name
	private.colorSets[key] = colorSet
end

--- Sets the active color set.
-- @tparam string key The key which represents the color set
function Theme.SetActiveColorSet(key)
	assert(private.colorSets[key])
	if private.currentColorSet == private.colorSets[key] then
		return
	end
	private.currentColorSet = private.colorSets[key]
	for _, callback in ipairs(private.callbacks) do
		callback()
	end
end

function Theme.GetThemeName(key)
	return private.names[key]
end

--- Gets the color object from the current active color set.
-- @tparam string key The key of the color to get
-- @treturn Color The color object
function Theme.GetColor(key, themeKey)
	local colorKey, tintPct, opacityPct = strmatch(key, "^([A-Z_]+)([%-%+]?[0-9A-Z_]*)%%?([0-9A-Z_]*)$")
	tintPct = tonumber(tintPct) or (tintPct ~= "" and tintPct or nil)
	opacityPct = tonumber(opacityPct) or (opacityPct ~= "" and opacityPct or nil)
	assert(colorKey)
	local color = nil
	if THEME_COLOR_KEYS[colorKey] then
		color = themeKey and private.colorSets[themeKey][colorKey] or private.currentColorSet[colorKey]
	else
		color = STATIC_COLORS[colorKey]
	end
	assert(color)
	if tintPct then
		color = color:GetTint(tintPct)
	end
	if opacityPct then
		color = color:GetOpacity(opacityPct)
	end
	return color
end

--- Gets the color object for a given feedback color key.
-- @tparam string key The key of the feedback color to get
-- @treturn Color The color object
function Theme.GetFeedbackColor(key)
	return FEEDBACK_COLORS[key]
end

--- Gets the color object for Blizzard GMs.
-- @treturn Color The color object
function Theme.GetBlizzardColor()
	return BLIZZARD_COLOR
end

--- Gets the color object for a given group level.
-- @tparam number level The level of the group (1-based)
-- @treturn Color The color object
function Theme.GetGroupColor(level)
	level = ((level - 1) % #GROUP_COLORS) + 1
	return GROUP_COLORS[level]
end

function Theme.GetProfessionDifficultyColor(difficulty)
	return PROFESSION_DIFFICULTY_COLORS[difficulty]
end

function Theme.GetItemQualityColor(quality)
	return TSM_ITEM_QUALITY_COLORS[quality]
end

function Theme.GetAuctionPercentColor(pct)
	if pct == "BID" then
		return Theme.GetColor(AUCTION_PCT_COLORS.bid)
	end
	for _, info in ipairs(AUCTION_PCT_COLORS) do
		if pct < info.value then
			return Theme.GetFeedbackColor(info.color)
		end
	end
	return Theme.GetColor(AUCTION_PCT_COLORS.default)
end

--- Gets the font object from the current active font set.
-- @tparam string key The key of the font to get
-- @treturn FontObject The font object
function Theme.GetFont(key)
	local fontObj = private.currentFontSet[key]
	assert(fontObj)
	return fontObj
end

--- Gets the column spacing constant value.
-- @treturn number The column spacing
function Theme.GetColSpacing()
	return CONSTANTS.COL_SPACING
end

--- Gets the scrollbar margin constant value.
-- @treturn number The scrollbar margin
function Theme.GetScrollbarMargin()
	return CONSTANTS.SCROLLBAR_MARGIN
end

--- Gets the scrollbar width constant value.
-- @treturn number The scrollbar width
function Theme.GetScrollbarWidth()
	return CONSTANTS.SCROLLBAR_WIDTH
end

--- Gets the scrollbar width constant value.
-- @treturn number The scrollbar width
function Theme.GetMouseWheelScrollAmount()
	return CONSTANTS.MOUSE_WHEEL_SCROLL_AMOUNT
end



-- ============================================================================
-- Private Helper Functions
-- ============================================================================

function private.QueueFontLoad(path)
	if private.fontFrame.texts[path] then
		return
	end
	local fontString = private.fontFrame:CreateFontString()
	fontString:SetPoint("CENTER")
	fontString:SetWidth(10000)
	fontString:SetHeight(6)
	fontString:SetFont(path, 6)
	fontString:SetText("1")
	private.fontFrame.texts[path] = fontString
	private.fontFrame:Show()
end

function private.FontFrameOnUpdate(frame)
	for _, fontString in pairs(frame.texts) do
		if fontString:IsVisible() then
			assert(fontString:GetStringWidth() > 0, "Text not loaded: "..tostring(fontString:GetFont()))
			fontString:Hide()
		end
	end
	frame:Hide()
end
