local L = LibStub("AceLocale-3.0"):GetLocale("IceHUD", false)
IceHUD = LibStub("AceAddon-3.0"):NewAddon("IceHUD", "AceConsole-3.0", "AceHook-3.0")

local IceHUD = IceHUD

local SML = LibStub("LibSharedMedia-3.0")
local ACR = LibStub("AceConfigRegistry-3.0")
local ConfigDialog = LibStub("AceConfigDialog-3.0")
local icon = LibStub("LibDBIcon-1.0", true)

local pendingModuleLoads = {}
local bReadyToRegisterModules = false

IceHUD.CurrTagVersion = 3
IceHUD.debugging = false

IceHUD.WowVer = select(4, GetBuildInfo())
IceHUD.WowMain = not WOW_PROJECT_ID or WOW_PROJECT_ID == WOW_PROJECT_MAINLINE
if GetClassicExpansionLevel then
	IceHUD.WowClassic = GetClassicExpansionLevel() == 0
	IceHUD.WowClassicBC = GetClassicExpansionLevel() == 1
	IceHUD.WowClassicWrath = GetClassicExpansionLevel() == 2
else
	IceHUD.WowClassic = WOW_PROJECT_ID and WOW_PROJECT_ID == WOW_PROJECT_CLASSIC
	IceHUD.WowClassicBC = false
	IceHUD.WowClassicWrath = false
	if WOW_PROJECT_ID and WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC then
		if not LE_EXPANSION_LEVEL_CURRENT or LE_EXPANSION_LEVEL_CURRENT == LE_EXPANSION_BURNING_CRUSADE then
			IceHUD.WowClassicBC = true
		elseif LE_EXPANSION_LEVEL_CURRENT == LE_EXPANSION_WRATH_OF_THE_LICH_KING then
			IceHUD.WowClassicWrath = true
		end
	elseif WOW_PROJECT_WRATH_CLASSIC and WOW_PROJECT_ID == WOW_PROJECT_WRATH_CLASSIC then
		IceHUD.WowClassicWrath = true
	end
end

-- compatibility/feature flags
IceHUD.GetPlayerAuraBySpellID = _G["C_UnitAuras"] and C_UnitAuras.GetPlayerAuraBySpellID
IceHUD.SpellFunctionsReturnRank = IceHUD.WowMain and IceHUD.WowVer < 80000
IceHUD.EventExistsPlayerPetChanged = IceHUD.WowMain and IceHUD.WowVer < 80000
IceHUD.EventExistsPetBarChanged = IceHUD.WowMain and IceHUD.WowVer < 80000
IceHUD.EventExistsPlayerComboPoints = IceHUD.WowMain and IceHUD.WowVer < 30000
IceHUD.EventExistsUnitComboPoints = IceHUD.WowMain and IceHUD.WowVer < 70000
IceHUD.EventExistsUnitMaxPower = IceHUD.WowMain and IceHUD.WowVer >= 40000
IceHUD.EventExistsGroupRosterUpdate = IceHUD.WowVer >= 50000 or not IceHUD.WowMain
IceHUD.EventExistsUnitDynamicFlags = IceHUD.WowMain and IceHUD.WowVer < 80000
IceHUD.EventExistsUnitHealthFrequent = not IceHUD.WowMain or (IceHUD.WowVer >= 40000 and IceHUD.WowVer < 90000)
IceHUD.PerPowerEventsExist = IceHUD.WowMain and IceHUD.WowVer < 40000
IceHUD.PerTargetComboPoints = IceHUD.WowVer < 60000
IceHUD.CanTrackOtherUnitBuffs = not IceHUD.WowClassic
IceHUD.CanTrackGCD = not IceHUD.WowClassic
IceHUD.GetSpellInfoReturnsFunnel = IceHUD.WowMain and IceHUD.WowVer < 60000
IceHUD.CanHookDestroyTotem = IceHUD.WowClassic or IceHUD.WowClassicBC or IceHUD.WowClassicWrath
IceHUD.ShouldUpdateTargetHealthEveryTick = (IceHUD.WowClassic or IceHUD.WowClassicBC) and GetCVarBool("predictedHealth")
IceHUD.UsesUIPanelButtonTemplate = IceHUD.WowVer >= 50000 or not IceHUD.WowMain
IceHUD.EventExistsSpellcastInterruptible = IceHUD.WowVer >= 30200 and not IceHUD.WowClassicWrath
IceHUD.DeathKnightUnholyFrostRunesSwapped = IceHUD.WowVer < 70300 and not IceHUD.WowClassicWrath
IceHUD.SupportsHealPrediction = IceHUD.WowVer >= 40000 or IceHUD.WowClassicWrath
IceHUD.UnitGroupRolesReturnsRoleString = IceHUD.WowVer >= 40000 or IceHUD.WowClassicWrath
IceHUD.ShellGameSpellID = 271571
IceHUD.HasShellGame = GetSpellInfo(IceHUD.ShellGameSpellID)
IceHUD.CatalogingSpellIDs = {366290, 372817, 385025, 385635, 386070, 386504, 400043, 403115}
IceHUD.HasCataloging = GetSpellInfo(366290)

IceHUD.UnitPowerEvent = "UNIT_POWER_UPDATE"

IceHUD.validBarList = { "Bar", "HiBar", "RoundBar", "ColorBar", "RivetBar", "RivetBar2", "CleanCurves", "GlowArc",
	"BloodGlaives", "ArcHUD", "FangRune", "DHUD", "CleanCurvesOut", "CleanTank", "PillTank", "GemTank" }
IceHUD.validCustomModules = {Bar="Buff/Debuff watcher", Counter="Buff/Debuff stack counter", CD="Cooldown bar", Health="Health bar", Mana="Mana bar", CounterBar="Stack count bar"}

IceHUD_UnitFrame_DropDown = CreateFrame("Frame", "IceHUD_UnitFrame_DropDown", UIParent, "UIDropDownMenuTemplate")

--[==[@debug@
IceHUD.optionsLoaded = true
--@end-debug@]==]

local function deepcopy(object)
	local lookup_table = {}
	local function _copy(object)
		if type(object) ~= "table" then
			return object
		elseif lookup_table[object] then
			return lookup_table[object]
		end
		local new_table = {}
		lookup_table[object] = new_table
		for index, value in pairs(object) do
			new_table[_copy(index)] = _copy(value)
		end
		return setmetatable(new_table, getmetatable(object))
	end
	return _copy(object)
end

IceHUD.deepcopy = deepcopy

function IceHUD:removeDefaults(db, defaults, blocker)
	-- remove all metatables from the db, so we don't accidentally create new sub-tables through them
	setmetatable(db, nil)
	-- loop through the defaults and remove their content
	for k,v in pairs(defaults) do
		if type(v) == "table" and type(db[k]) == "table" then
			-- if a blocker was set, dive into it, to allow multi-level defaults
			self:removeDefaults(db[k], v, blocker and blocker[k])
			if next(db[k]) == nil then
				db[k] = nil
			end
		else
			-- check if the current value matches the default, and that its not blocked by another defaults table
			if db[k] == defaults[k] and (not blocker or blocker[k] == nil) then
				db[k] = nil
			end
		end
	end
end

function IceHUD:populateDefaults(db, defaults, blocker)
	-- remove all metatables from the db, so we don't accidentally create new sub-tables through them
	setmetatable(db, nil)
	-- loop through the defaults and add their content
	for k,v in pairs(defaults) do
		if type(v) == "table" and type(db[k]) == "table" then
			-- if a blocker was set, dive into it, to allow multi-level defaults
			self:populateDefaults(db[k], v, blocker and blocker[k])
		else
			-- check if the current value matches the default, and that its not blocked by another defaults table
			if db[k] == nil then
				db[k] = defaults[k]
			end
		end
	end
end

IceHUD.Location = "Interface\\AddOns\\IceHUD"

StaticPopupDialogs["ICEHUD_CUSTOM_BAR_CREATED"] =
{
	text = L["A custom bar has been created and can be configured through Module Settings => MyCustomBar. It is highly recommended that you change the bar name of this module so that it's easier to identify."],
	button1 = OKAY,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
}

StaticPopupDialogs["ICEHUD_CUSTOM_COUNTER_CREATED"] =
{
	text = L["A custom counter has been created and can be configured through Module Settings => MyCustomCounter. It is highly recommended that you change the bar name of this module so that it's easier to identify."],
	button1 = OKAY,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
}

StaticPopupDialogs["ICEHUD_CUSTOM_COUNTER_BAR_CREATED"] =
{
	text = L["A custom counter bar has been created and can be configured through Module Settings => MyCustomCounterBar. It is highly recommended that you change the bar name of this module so that it's easier to identify."],
	button1 = OKAY,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
}

StaticPopupDialogs["ICEHUD_CUSTOM_CD_CREATED"] =
{
	text = L["A custom cooldown bar has been created and can be configured through Module Settings => MyCustomCD. It is highly recommended that you change the bar name of this module so that it's easier to identify."],
	button1 = OKAY,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
}

StaticPopupDialogs["ICEHUD_CUSTOM_HEALTH_CREATED"] =
{
	text = L["A custom health bar has been created and can be configured through Module Settings => MyCustomHealth. It is highly recommended that you change the bar name of this module so that it's easier to identify."],
	button1 = OKAY,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
}

StaticPopupDialogs["ICEHUD_CUSTOM_MANA_CREATED"] =
{
	text = L["A custom mana bar has been created and can be configured through Module Settings => MyCustomMana. It is highly recommended that you change the bar name of this module so that it's easier to identify."],
	button1 = OKAY,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
}

StaticPopupDialogs["ICEHUD_DELETE_CUSTOM_MODULE"] =
{
	text = L["Are you sure you want to delete this module? This will remove all settings associated with it and cannot be un-done."],
	button1 = YES,
	button2 = NO,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
	OnAccept = function(self)
		IceHUD.IceCore:DeleteDynamicModule(self.data)
		self.data = nil
	end,
}

StaticPopupDialogs["ICEHUD_CHANGED_DOGTAG"] = {
	text = L["This option requires the UI to be reloaded. Do you wish to reload it now?"],
	button1 = YES,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
	OnAccept = function()
		ReloadUI()
	end,
	button2 = NO,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0
}

StaticPopupDialogs["ICEHUD_CHANGED_PROFILE_COMBAT"] = {
	text = L["You have changed IceHUD profiles while in combat. This can cause problems due to Blizzard's secure frame policy. You may need to reload your UI to repair IceHUD."],
	button1 = OKAY,
	OnShow = function(self)
		self:SetFrameStrata("TOOLTIP")
	end,
	OnHide = function(self)
		self:SetFrameStrata("DIALOG")
	end,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 0
}

function IceHUD:OnInitialize()
	self:SetDebugging(false)
	self:Debug("IceHUD:OnInitialize()")

	self.IceCore = IceCore:new()
	self:RegisterPendingModules()
	self.IceCore:SetupDefaults()
	bReadyToRegisterModules = true

	self.db = LibStub("AceDB-3.0"):New("IceCoreDB", self.IceCore.defaults, true)
	if not self.db or not self.db.global or not self.db.profile then
		print(L["Error: IceHUD database not loaded correctly.  Please exit out of WoW and delete the database file (IceHUD.lua) found in: \\World of Warcraft\\WTF\\Account\\<Account Name>>\\SavedVariables\\"])
		return
	end

	self.db.RegisterCallback(self, "OnProfileShutdown", "PreProfileChanged")
	self.db.RegisterCallback(self, "OnProfileChanged", "PostProfileChanged")
	self.db.RegisterCallback(self, "OnProfileReset", "ProfileReset")
	self.db.RegisterCallback(self, "OnProfileCopied", "ProfileCopied")

	self:NotifyNewDb()

	ConfigDialog:SetDefaultSize("IceHUD", 750, 650)
	self:RegisterChatCommand("icehud", function()
			if not UnitAffectingCombat("player") then
				IceHUD:OpenConfig()
			else
				DEFAULT_CHAT_FRAME:AddMessage(L["|cff8888ffIceHUD|r: Combat lockdown restriction. Leave combat and try again."])
			end
		end)
	self:RegisterChatCommand("rl", function() ReloadUI() end)

	-- hack to allow /icehudcl to continue to function by loading the LoD options module and then re-calling the command
	--@non-debug@
	self:RegisterChatCommand("icehudcl", function(arg)
		self:UnregisterChatCommand("icehudcl")
		self:LoadOptions()
		LibStub("AceConfigCmd-3.0"):HandleCommand("icehudcl", "IceHUD", arg)
		end)
	--@end-non-debug@

	self:SyncSettingsVersions()

	self:InitLDB()
	if SML then
		SML.RegisterCallback(self, "LibSharedMedia_Registered", "UpdateMedia")
	end
end


function IceHUD:NotifyNewDb()
	self.IceCore.accountSettings = self.db.global
	self.IceCore.settings = self.db.profile
	self.IceCore:SetModuleDatabases()

	self.IceCore:CheckDisplayUpdateMessage()
end


function IceHUD:NotifyOptionsChange()
	if ACR then
		ACR:NotifyChange("IceHUD")
	end
end

function IceHUD:OnEnable(isFirst)
--	if isFirst then
		self:SetDebugging(self.IceCore:GetDebug())
		self.debugFrame = ChatFrame1
--	end
	self:Debug("IceHUD:OnEnable()")

	if self.db.profile.enable then
		self.IceCore:Enable()
	end

	-- Add dual-spec support
	local LibDualSpec = LibStub('LibDualSpec-1.0', true)
	if LibDualSpec then
		LibDualSpec:EnhanceDatabase(self.db, "IceHUD")
	end

	--[==[@debug@
	IceHUD_Options:OnLoad()
	--@end-debug@]==]
end

-- add settings changes/updates here so that existing users don't lose their settings
function IceHUD:SyncSettingsVersions()
	if not self.IceCore.settings.updatedOocNotFull then
		self.IceCore.settings.updatedOocNotFull = true
		self.IceCore.settings.alphaNotFull = self.IceCore.settings.alphaTarget
		self.IceCore.settings.alphaNotFullbg = self.IceCore.settings.alphaTargetbg
	end
end


function IceHUD:InitLDB()
	local LDB = LibStub and LibStub("LibDataBroker-1.1", true)

	if (LDB) then
		local ldbButton = LDB:NewDataObject("IceHUD", {
			type = "launcher",
			text = L["IceHUD"],
			label = L["IceHUD"],
			icon = "Interface\\Icons\\Spell_Frost_Frost",
			OnClick = function(button, msg)
				if not UnitAffectingCombat("player") then
					IceHUD:OpenConfig()
				else
					DEFAULT_CHAT_FRAME:AddMessage(L["|cff8888ffIceHUD|r: Combat lockdown restriction. Leave combat and try again."])
				end
			end,
		})

		if icon then
			icon:Register("IceHUD", ldbButton, self.db.profile.minimap)
		end

		if ldbButton then
			function ldbButton:OnTooltipShow()
				self:AddLine(L["IceHUD"] .. " v1.14.18")
				self:AddLine(L["Click to open IceHUD options."], 1, 1, 1)
			end
		end
	end
end

-- blizzard interface options
local blizOptionsPanel = CreateFrame("FRAME", "IceHUDConfigPanel", UIParent)
blizOptionsPanel.name = "IceHUD"
blizOptionsPanel.button = CreateFrame("BUTTON", "IceHUDOpenConfigButton", blizOptionsPanel, IceHUD.UsesUIPanelButtonTemplate and "UIPanelButtonTemplate" or "UIPanelButtonTemplate2")
blizOptionsPanel.button:SetText("Open IceHUD configuration")
blizOptionsPanel.button:SetWidth(240)
blizOptionsPanel.button:SetHeight(30)
blizOptionsPanel.button:SetScript("OnClick", function(self) HideUIPanel(InterfaceOptionsFrame) HideUIPanel(GameMenuFrame) IceHUD:OpenConfig() end)
blizOptionsPanel.button:SetPoint('TOPLEFT', blizOptionsPanel, 'TOPLEFT', 20, -20)
InterfaceOptions_AddCategory(blizOptionsPanel)

function IceHUD:OpenConfig()
	if not ConfigDialog then return end

	if not self:LoadOptions() then
		return
	end

	if ConfigDialog.OpenFrames["IceHUD"] ~= nil then
		ConfigDialog:Close("IceHUD")
	else
		ConfigDialog:Open("IceHUD")
	end
end

function IceHUD:LoadOptions()
	if not self.optionsLoaded then
		local loaded, reason = LoadAddOn("IceHUD_Options")
		if not loaded then
			print("Failed to load options module. Reason: " .. reason)
			return false
		else
			self.optionsLoaded = true
		end
	end

	return true
end

function IceHUD:Debug(...)
	if self.debugging then
		local msg = ""
		for n=1,select('#', ...) do
			msg = msg .. tostring(select(n, ...)) .. " "
		end
		if self.debugFrame then
			self.debugFrame:AddMessage(msg)
		else
			print(msg)
		end
	end
end

function IceHUD:SetDebugging(bIsDebugging)
	self.debugging = bIsDebugging
end

-- rounding stuff
function IceHUD:MathRound(num, idp)
	if not num then
		return nil
	end

	local mult = 10^(idp or 0)
	return math.floor(num  * mult + 0.5) / mult
end

function IceHUD:GetBuffCount(unit, ability, onlyMine, matchByName)
	return IceHUD:GetAuraCount("HELPFUL", unit, ability, onlyMine, matchByName)
end

function IceHUD:GetDebuffCount(unit, ability, onlyMine, matchByName)
	return IceHUD:GetAuraCount("HARMFUL", unit, ability, onlyMine, matchByName)
end

function IceHUD:GetAuraCount(auraType, unit, ability, onlyMine, matchByName)
	if not unit or not ability then
		return 0
	end

	if unit == "main hand weapon" or unit == "off hand weapon" then
		local hasMainHandEnchant, mainHandExpiration, mainHandCharges, mainHandEnchantID, hasOffHandEnchant, offHandExpiration, offHandCharges, offHandEnchantID
			= GetWeaponEnchantInfo()

		if unit == "main hand weapon" and hasMainHandEnchant then
			return mainHandCharges
		elseif unit == "off hand weapon" and hasOffHandEnchant then
			return offHandCharges
		end

		return 0
	end

	-- Support for Spell IDs
	if (IceHUD.GetPlayerAuraBySpellID and tonumber(ability) ~= nil) then
		local aura = C_UnitAuras.GetPlayerAuraBySpellID(ability)
		if aura ~= nil then
			return aura.applications
		else
			return 0
		end
	end

	local i = 1
	local name, _, texture, applications
	if IceHUD.SpellFunctionsReturnRank then
		name, _, texture, applications = UnitAura(unit, i, auraType..(onlyMine and "|PLAYER" or ""))
	else
		name, texture, applications = UnitAura(unit, i, auraType..(onlyMine and "|PLAYER" or ""))
	end
	while name do
		if (not matchByName and string.match(texture:upper(), ability:upper()))
			or (matchByName and string.match(name:upper(), ability:upper())) then
			return applications
		end

		i = i + 1
		if IceHUD.SpellFunctionsReturnRank then
			name, _, texture, applications = UnitAura(unit, i, auraType..(onlyMine and "|PLAYER" or ""))
		else
			name, texture, applications = UnitAura(unit, i, auraType..(onlyMine and "|PLAYER" or ""))
		end
	end

	return 0
end

do
	local retval = {}

	function IceHUD:HasBuffs(unit, spellIDs, filter)
		for i=1, #spellIDs do
			retval[i] = false
		end

		local i = 1
		local name, _, texture, applications, _, _, _, _, _, _, auraID
		if IceHUD.SpellFunctionsReturnRank then
			name, _, texture, applications, _, _, _, _, _, _, auraID = UnitAura(unit, i, filter)
		else
			name, texture, applications, _, _, _, _, _, _, auraID = UnitAura(unit, i, filter)
		end
		while name do
			for i=1, #spellIDs do
				if spellIDs[i] == auraID then
					retval[i] = applications == 0 and true or applications
					break
				end
			end

			i = i + 1
			if IceHUD.SpellFunctionsReturnRank then
				name, _, texture, applications, _, _, _, _, _, _, auraID = UnitAura(unit, i, filter)
			else
				name, texture, applications, _, _, _, _, _, _, auraID = UnitAura(unit, i, filter)
			end
		end

		return retval
	end

	function IceHUD:HasDebuffs(unit, spellIDs, filter)
		return IceHUD:HasBuffs(unit, spellIDs, filter and filter.."|HARMFUL" or "HARMFUL")
	end

	function IceHUD:HasAnyBuff(unit, spellIDs, filter)
		local buffs = IceHUD:HasBuffs(unit, spellIDs, filter)

		for i=1, #buffs do
			if buffs[i] then
				return true
			end
		end

		return false
	end

	function IceHUD:HasAnyDebuff(unit, spellIDs, filter)
		local debuffs = IceHUD:HasDebuffs(unit, spellIDs, filter)

		for i=1, #debuffs do
			if debuffs[i] then
				return true
			end
		end

		return false
	end
end

function IceHUD:OnDisable()
	IceHUD.IceCore:Disable()
end

function IceHUD:PreProfileChanged(db)
	if UnitAffectingCombat("player") then
		StaticPopup_Show("ICEHUD_CHANGED_PROFILE_COMBAT")
	end
	self.IceCore:Disable()
end

function IceHUD:PostProfileChanged(db, newProfile)
	self:NotifyNewDb()
	if self.db.profile.enable then
		self.IceCore:Enable()
	end
end

function IceHUD:ProfileReset()
	ReloadUI()
end
function IceHUD:ProfileCopied()
	ReloadUI()
end

function IceHUD:Clamp(value, min, max)
	if value < min then
		value = min
	elseif value > max then
		value = max
	elseif value ~= value or not (value >= min and value <= max) then -- check for nan...
		value = min
	end

	return value
end

function IceHUD:ShouldSwapToVehicle(...)
	local arg1, arg2 = ...

	if (arg1 == "player") then
		if (arg2) then
			return true
		end
	end

	if (UnitHasVehicleUI("player")) then
		return true
	end
end

function IceHUD:xor(val1, val2)
	return val1 and not val2 or val2 and not val1
end

function IceHUD:GetSelectValue(info, val)
	for k,v in pairs(info.option.values) do
		if v == val then
			return k
		end
	end

	return 1
end

function IceHUD:Register(element)
	assert(element, "Trying to register a nil module")
	if not bReadyToRegisterModules then
		pendingModuleLoads[#pendingModuleLoads+1] = element
	else
		self.IceCore:Register(element)
	end
end

function IceHUD:RegisterPendingModules()
	for i=1, #pendingModuleLoads do
		self.IceCore:Register(pendingModuleLoads[i])
	end
	pendingModuleLoads = {}
end

function IceHUD:UpdateMedia(event, mediatype, key)
	if not self.db.profile or not self.IceCore.enabled then
		return
	end

	if mediatype == "font" then
		if key == self.db.profile.fontFamily then
			IceHUD.IceCore:SetFontFamily(key)
		end
	elseif mediatype == "statusbar" then
		if self.TargetOfTarget and self.TargetOfTarget.moduleSettings.enabled and key == self.TargetOfTarget.moduleSettings.texture then
			self.TargetOfTarget:Redraw()
		end
	end
end

function IceHUD:CreateCustomModuleAndNotify(moduleKey, settings)
	local newMod = nil
	local popupMsg
	if moduleKey == "Bar" then -- custom bar
		newMod = IceCustomBar:new()
		popupMsg = "ICEHUD_CUSTOM_BAR_CREATED"
	elseif moduleKey == "Counter" then -- custom counter
		newMod = IceCustomCount:new()
		popupMsg = "ICEHUD_CUSTOM_COUNTER_CREATED"
	elseif moduleKey == "CounterBar" then -- custom counter bar
		newMod = IceCustomCounterBar:new()
		popupMsg = "ICEHUD_CUSTOM_COUNTER_BAR_CREATED"
	elseif moduleKey == "CD" then -- cooldown bar
		newMod = IceCustomCDBar:new()
		popupMsg = "ICEHUD_CUSTOM_CD_CREATED"
	elseif moduleKey == "Health" then -- custom health bar
		newMod = IceCustomHealth:new()
		popupMsg = "ICEHUD_CUSTOM_HEALTH_CREATED"
	elseif moduleKey == "Mana" then -- custom mana bar
		newMod = IceCustomMana:new()
		popupMsg = "ICEHUD_CUSTOM_MANA_CREATED"
	end

	if newMod ~= nil then
		IceHUD.IceCore:AddNewDynamicModule(newMod, settings)
		ConfigDialog:SelectGroup("IceHUD", "modules", newMod.elementName)
		StaticPopup_Show(popupMsg)
	end
end

local function CheckLFGMode(mode)
	return (mode ~= nil and mode ~= "abandonedInDungeon" and mode ~= "queued")
end

function IceHUD:GetIsInLFGGroup()
	if not GetLFGMode then
		return false
	end

	local mode, submode
	if LE_LFG_CATEGORY_LFD then
		mode, submode = GetLFGMode(LE_LFG_CATEGORY_LFD)
	else
		mode, submode = GetLFGMode()
	end
	local IsInLFGGroup = CheckLFGMode(mode)

	if not LE_LFG_CATEGORY_LFD then
		return IsInLFGGroup
	end

	if not IsInLFGGroup then
		mode, submode = GetLFGMode(LE_LFG_CATEGORY_RF)
		IsInLFGGroup = CheckLFGMode(mode)
	end
	if not IsInLFGGroup then
		mode, submode = GetLFGMode(LE_LFG_CATEGORY_SCENARIO)
		IsInLFGGroup = CheckLFGMode(mode)
	end
	if not IsInLFGGroup then
		mode, submode = GetLFGMode(LE_LFG_CATEGORY_LFR)
		IsInLFGGroup = CheckLFGMode(mode)
	end

	return IsInLFGGroup
end

local BLACKLISTED_UNIT_MENU_OPTIONS = {}
if UnitPopupButtons then
	BLACKLISTED_UNIT_MENU_OPTIONS = {
		SET_FOCUS = "ICEHUD_SET_FOCUS",
		CLEAR_FOCUS = "ICEHUD_CLEAR_FOCUS",
		LOCK_FOCUS_FRAME = true,
		UNLOCK_FOCUS_FRAME = true,
	}
	if select(2, UnitClass("player")) ~= "WARLOCK" then
		BLACKLISTED_UNIT_MENU_OPTIONS[PET_DISMISS] = "ICEHUD_PET_DISMISS"
	end

	UnitPopupButtons["ICEHUD_SET_FOCUS"] = {
		text = L["Type %s to set focus"]:format(SLASH_FOCUS1),
		tooltipText = L["Blizzard currently does not provide a proper way to right-click focus with custom unit frames."],
		dist = 0,
	}

	UnitPopupButtons["ICEHUD_CLEAR_FOCUS"] = {
		text = L["Type %s to clear focus"]:format(SLASH_CLEARFOCUS1),
		tooltipText = L["Blizzard currently does not provide a proper way to right-click focus with custom unit frames."],
		dist = 0,
	}

	UnitPopupButtons["ICEHUD_PET_DISMISS"] = {
		text = L["Use your Dismiss Pet spell to dismiss a pet"],
		tooltipText = L["Blizzard currently does not provide a proper way to right-click dismiss a pet with custom unit frames."],
		dist = 0,
	}
elseif UnitPopupSetFocusButtonMixin then
	IceHUDUnitPopupSetFocusButtonMixin = CreateFromMixins(UnitPopupButtonBaseMixin)
	function IceHUDUnitPopupSetFocusButtonMixin:GetText()
		return L["Type %s to set focus"]:format(SLASH_FOCUS1)
	end
	function IceHUDUnitPopupSetFocusButtonMixin:OnClick()
	end

	IceHUDUnitPopupClearFocusButtonMixin = CreateFromMixins(UnitPopupButtonBaseMixin)
	function IceHUDUnitPopupClearFocusButtonMixin:GetText()
		return L["Type %s to clear focus"]:format(SLASH_CLEARFOCUS1)
	end
	function IceHUDUnitPopupClearFocusButtonMixin:OnClick()
	end

	IceHUDUnitPopupPetDismissButtonMixin = CreateFromMixins(UnitPopupButtonBaseMixin)
	function IceHUDUnitPopupPetDismissButtonMixin:GetText()
		return L["Use your Dismiss Pet spell to dismiss a pet"]
	end
	function IceHUDUnitPopupPetDismissButtonMixin:CanShow()
		return UnitPopupPetDismissButtonMixin:CanShow()
	end
	function IceHUDUnitPopupPetDismissButtonMixin:OnClick()
	end

	BLACKLISTED_UNIT_MENU_OPTIONS[SET_FOCUS] = IceHUDUnitPopupSetFocusButtonMixin
	BLACKLISTED_UNIT_MENU_OPTIONS[CLEAR_FOCUS] = IceHUDUnitPopupClearFocusButtonMixin
	BLACKLISTED_UNIT_MENU_OPTIONS[LOCK_FOCUS_FRAME] = true
	BLACKLISTED_UNIT_MENU_OPTIONS[UNLOCK_FOCUS_FRAME] = true

	if select(2, UnitClass("player")) ~= "WARLOCK" then
		BLACKLISTED_UNIT_MENU_OPTIONS[PET_DISMISS] = IceHUDUnitPopupPetDismissButtonMixin
	end
end

local munged_unit_menus = {}
local function munge_unit_menu(menu)
	local result = munged_unit_menus[menu]
	if result then
		return result
	end

	if not UnitPopupMenus then
		munged_unit_menus[menu] = menu
		return menu
	end

	local data = UnitPopupMenus[menu]
	if not data then
		munged_unit_menus[menu] = menu
		return menu
	end

	local found = false
	if data.GetMenuButtons then
		local btns = data.GetMenuButtons()
		for i=1, #btns do
			if btns[i].IsMenu() then
				local subbtns = btns[i].GetMenuButtons()
				for j=1, #subbtns do
					if BLACKLISTED_UNIT_MENU_OPTIONS[subbtns[j]:GetText()] then
						found = true
						break
					end
				end
			else
				if BLACKLISTED_UNIT_MENU_OPTIONS[btns[i]:GetText()] then
					found = true
					break
				end
			end

			if found then
				break
			end
		end
	else
		for _, v in ipairs(data) do
			if BLACKLISTED_UNIT_MENU_OPTIONS[v] then
				found = true
				break
			end
		end
	end

	if not found then
		-- nothing to remove or add, we're all fine here.
		munged_unit_menus[menu] = menu
		return menu
	end

	local new_data = {}
	if data.GetMenuButtons then
		local new_buttons_list = {}
		local btns = data.GetMenuButtons()
		for i=1, #btns do
			if btns[i].IsMenu() then
				local subbtns = btns[i].GetMenuButtons()
				for j=1, #subbtns do
					local blacklisted = BLACKLISTED_UNIT_MENU_OPTIONS[subbtns[j]:GetText()]
					if not blacklisted then
						new_buttons_list[#new_buttons_list+1] = subbtns[j]
					elseif blacklisted ~= true then
						new_buttons_list[#new_buttons_list+1] = blacklisted
					end
				end
			else
				local blacklisted = BLACKLISTED_UNIT_MENU_OPTIONS[btns[i]:GetText()]
				if not blacklisted then
					new_buttons_list[#new_buttons_list+1] = btns[i]
				elseif blacklisted ~= true then
					new_buttons_list[#new_buttons_list+1] = blacklisted
				end
			end
		end

		new_data = data
		function new_data:GetMenuButtons()
			return new_buttons_list
		end
	else
		for _, v in ipairs(data) do
			local blacklisted = BLACKLISTED_UNIT_MENU_OPTIONS[v]
			if not blacklisted then
				new_data[#new_data+1] = v
			elseif blacklisted ~= true then
				new_data[#new_data+1] = blacklisted
			end
		end
	end
	local new_menu_name = "ICEHUD_" .. menu

	UnitPopupMenus[new_menu_name] = new_data
	munged_unit_menus[menu] = new_menu_name
	return new_menu_name
end
IceHUD.MungeUnitMenu = munge_unit_menu

local function figure_unit_menu(unit)
	if unit == "focus" then
		return "FOCUS"
	end

	if UnitIsUnit(unit, "player") then
		return "SELF"
	end

	if UnitIsUnit(unit, "vehicle") then
		-- NOTE: vehicle check must come before pet check for accuracy's sake because
		-- a vehicle may also be considered your pet
		return "VEHICLE"
	end

	if UnitIsUnit(unit, "pet") then
		return "PET"
	end

	if not UnitIsPlayer(unit) then
		return "TARGET"
	end

	local id = UnitInRaid(unit)
	if id then
		return "RAID_PLAYER", id
	end

	if UnitInParty(unit) then
		return "PARTY"
	end

	return "PLAYER"
end

if UnitPopupFrames then
	UnitPopupFrames[#UnitPopupFrames+1] = "IceHUD_UnitFrame_DropDown"
end

IceHUD.DropdownUnit = nil
UIDropDownMenu_Initialize(IceHUD_UnitFrame_DropDown, function()
	if not IceHUD.DropdownUnit then
		return
	end

	local menu, id = figure_unit_menu(IceHUD.DropdownUnit)
	if menu then
		menu = IceHUD.MungeUnitMenu(menu)
		UnitPopup_ShowMenu(IceHUD_UnitFrame_DropDown, menu, IceHUD.DropdownUnit, nil, id)
	end
end, "MENU", nil)

function IceHUD:OutOfCombatWrapper(func)
	return function(...)
		return IceHUD:RunOnLeaveCombat(func, ...)
	end
end

do
	local in_combat = false
	local in_lockdown = false
	local actions_to_perform = {}
	local pool = setmetatable({}, {__mode='k'})
	function IceHUD:PLAYER_REGEN_ENABLED()
		in_combat = false
		in_lockdown = false
		for i, t in ipairs(actions_to_perform) do
			t.f(unpack(t, 1, t.n))
			actions_to_perform[i] = nil
			wipe(t)
			pool[t] = true
		end
	end
	function IceHUD:PLAYER_REGEN_DISABLED()
		in_combat = true
	end
	function IceHUD:RunOnLeaveCombat(func, ...)
		if not in_combat then
			-- out of combat, call right away and return
			func(...)
			return
		end
		if not in_lockdown then
			in_lockdown = InCombatLockdown() -- still in PLAYER_REGEN_DISABLED
			if not in_lockdown then
				func(...)
				return
			end
		end
		local t = next(pool) or {}
		pool[t] = nil

		t.f = func
		local n = select('#', ...)
		t.n = n
		for i = 1, n do
			t[i] = select(i, ...)
		end
		actions_to_perform[#actions_to_perform+1] = t
	end
end
