local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB
local UF = E:GetModule('UnitFrames')
local LSM = E.Libs.LSM

local ElvUF = E.oUF
assert(ElvUF, 'ElvUI was unable to locate oUF.')

local _G = _G
local select, type, unpack, assert, tostring = select, type, unpack, assert, tostring
local min, pairs, ipairs, tinsert, strsub = min, pairs, ipairs, tinsert, strsub
local strfind, gsub, format = strfind, gsub, format

local CompactRaidFrameManager_SetSetting = CompactRaidFrameManager_SetSetting
local CreateFrame = CreateFrame
local GetInstanceInfo = GetInstanceInfo
local hooksecurefunc = hooksecurefunc
local IsReplacingUnit = IsReplacingUnit
local IsAddOnLoaded = IsAddOnLoaded
local RegisterStateDriver = RegisterStateDriver
local SetCVar = SetCVar
local UnitExists = UnitExists
local UnitIsEnemy = UnitIsEnemy
local UnitIsFriend = UnitIsFriend
local UnitFrame_OnEnter = UnitFrame_OnEnter
local UnitFrame_OnLeave = UnitFrame_OnLeave
local UnregisterStateDriver = UnregisterStateDriver
local PlaySound = PlaySound

local C_NamePlate_GetNamePlateForUnit = C_NamePlate.GetNamePlateForUnit
local SOUNDKIT_IG_CREATURE_AGGRO_SELECT = SOUNDKIT.IG_CREATURE_AGGRO_SELECT
local SOUNDKIT_IG_CHARACTER_NPC_SELECT = SOUNDKIT.IG_CHARACTER_NPC_SELECT
local SOUNDKIT_IG_CREATURE_NEUTRAL_SELECT = SOUNDKIT.IG_CREATURE_NEUTRAL_SELECT
local SOUNDKIT_INTERFACE_SOUND_LOST_TARGET_UNIT = SOUNDKIT.INTERFACE_SOUND_LOST_TARGET_UNIT
local ALTERNATE_POWER_INDEX = Enum.PowerType.Alternate or 10

-- GLOBALS: ElvUF_Parent, Arena_LoadUI
local hiddenParent = CreateFrame('Frame', nil, _G.UIParent)
hiddenParent:SetAllPoints()
hiddenParent:Hide()

UF.headerstoload = {}
UF.unitgroupstoload = {}
UF.unitstoload = {}

UF.groupPrototype = {}
UF.headerPrototype = {}
UF.headers = {}
UF.groupunits = {}
UF.units = {}

UF.statusbars = {}
UF.fontstrings = {}
UF.badHeaderPoints = {
	TOP = 'BOTTOM',
	LEFT = 'RIGHT',
	BOTTOM = 'TOP',
	RIGHT = 'LEFT',
}

UF.headerFunctions = {}
UF.classMaxResourceBar = {
	DEATHKNIGHT = 6,
	PALADIN = 5,
	WARLOCK = 5,
	MONK = 6,
	MAGE = 4,
	ROGUE = 6,
	DRUID = 5
}

UF.instanceMapIDs = {
	[30]   = 40, -- Alterac Valley
	[489]  = 10, -- Classic Warsong Gulch
	[529]  = 15, -- Classic Arathi Basin
	[566]  = 15, -- Eye of the Storm
	[607]  = 15, -- Strand of the Ancients
	[628]  = 40, -- Isle of Conquest
	[726]  = 10, -- Twin Peaks
	[727]  = 10, -- Silvershard Mines
	[761]  = 10, -- The Battle for Gilneas
	[968]  = 10, -- Rated Eye of the Storm
	[998]  = 10, -- Temple of Kotmogu
	[1191] = 40, -- Ashran
	[1280] = 40, -- Southshore vs Tarren Mill
	[1681] = 15, -- Arathi Basin Winter
	[1803] = 10, -- Seething Shore
	[2106] = 10, -- Warsong Gulch
	[2107] = 15, -- Arathi Basin
	[2118] = 40, -- Battle for Wintergrasp
	[2245] = 15, -- Deepwind Gorge
	[3358] = 15, -- Arathi Basin (NEW - Only Brawl?)
}

UF.headerGroupBy = {
	CLASS = function(header)
		header:SetAttribute('groupingOrder', 'DEATHKNIGHT,DEMONHUNTER,DRUID,HUNTER,MAGE,PALADIN,PRIEST,ROGUE,SHAMAN,WARLOCK,WARRIOR,MONK')
		header:SetAttribute('sortMethod', 'NAME')
		header:SetAttribute('groupBy', 'CLASS')
	end,
	MTMA = function(header)
		header:SetAttribute('groupingOrder', 'MAINTANK,MAINASSIST,NONE')
		header:SetAttribute('sortMethod', 'NAME')
		header:SetAttribute('groupBy', 'ROLE')
	end,
	ROLE = function(header)
		header:SetAttribute('groupingOrder', 'TANK,HEALER,DAMAGER,NONE')
		header:SetAttribute('sortMethod', 'NAME')
		header:SetAttribute('groupBy', 'ASSIGNEDROLE')
	end,
	ROLE2 = function(header)
		header:SetAttribute('groupingOrder', 'TANK,DAMAGER,HEALER,NONE')
		header:SetAttribute('sortMethod', 'NAME')
		header:SetAttribute('groupBy', 'ASSIGNEDROLE')
	end,
	NAME = function(header)
		header:SetAttribute('groupingOrder', '1,2,3,4,5,6,7,8')
		header:SetAttribute('sortMethod', 'NAME')
		header:SetAttribute('groupBy', nil)
	end,
	GROUP = function(header)
		header:SetAttribute('groupingOrder', '1,2,3,4,5,6,7,8')
		header:SetAttribute('sortMethod', 'INDEX')
		header:SetAttribute('groupBy', 'GROUP')
	end,
	CLASSROLE = function(header)
		header:SetAttribute('groupingOrder', 'DEATHKNIGHT,WARRIOR,DEMONHUNTER,ROGUE,MONK,PALADIN,DRUID,SHAMAN,HUNTER,PRIEST,MAGE,WARLOCK')
		header:SetAttribute('sortMethod', 'NAME')
		header:SetAttribute('groupBy', 'CLASS')
	end,
	PETNAME = function(header)
		header:SetAttribute('groupingOrder', '1,2,3,4,5,6,7,8')
		header:SetAttribute('sortMethod', 'NAME')
		header:SetAttribute('groupBy', nil)
		header:SetAttribute('filterOnPet', true) --This is the line that matters. Without this, it sorts based on the owners name
	end,
	INDEX = function(header)
		header:SetAttribute('groupingOrder', '1,2,3,4,5,6,7,8')
		header:SetAttribute('sortMethod', 'INDEX')
		header:SetAttribute('groupBy', nil)
	end,
}

local POINT_COLUMN_ANCHOR_TO_DIRECTION = {
	TOPTOP = 'UP_RIGHT',
	BOTTOMBOTTOM = 'TOP_RIGHT',
	LEFTLEFT = 'RIGHT_UP',
	RIGHTRIGHT = 'LEFT_UP',
	RIGHTTOP = 'LEFT_DOWN',
	LEFTTOP = 'RIGHT_DOWN',
	LEFTBOTTOM = 'RIGHT_UP',
	RIGHTBOTTOM = 'LEFT_UP',
	BOTTOMRIGHT = 'UP_LEFT',
	BOTTOMLEFT = 'UP_RIGHT',
	TOPRIGHT = 'DOWN_LEFT',
	TOPLEFT = 'DOWN_RIGHT'
}

local DIRECTION_TO_POINT = {
	DOWN_RIGHT = 'TOP',
	DOWN_LEFT = 'TOP',
	UP_RIGHT = 'BOTTOM',
	UP_LEFT = 'BOTTOM',
	RIGHT_DOWN = 'LEFT',
	RIGHT_UP = 'LEFT',
	LEFT_DOWN = 'RIGHT',
	LEFT_UP = 'RIGHT',
	UP = 'BOTTOM',
	DOWN = 'TOP'
}

local DIRECTION_TO_GROUP_ANCHOR_POINT = {
	DOWN_RIGHT = 'TOPLEFT',
	DOWN_LEFT = 'TOPRIGHT',
	UP_RIGHT = 'BOTTOMLEFT',
	UP_LEFT = 'BOTTOMRIGHT',
	RIGHT_DOWN = 'TOPLEFT',
	RIGHT_UP = 'BOTTOMLEFT',
	LEFT_DOWN = 'TOPRIGHT',
	LEFT_UP = 'BOTTOMRIGHT',
	OUT_RIGHT_UP = 'BOTTOM',
	OUT_LEFT_UP = 'BOTTOM',
	OUT_RIGHT_DOWN = 'TOP',
	OUT_LEFT_DOWN = 'TOP',
	OUT_UP_RIGHT = 'LEFT',
	OUT_UP_LEFT = 'RIGHT',
	OUT_DOWN_RIGHT = 'LEFT',
	OUT_DOWN_LEFT = 'RIGHT',
}

local INVERTED_DIRECTION_TO_COLUMN_ANCHOR_POINT = {
	DOWN_RIGHT = 'RIGHT',
	DOWN_LEFT = 'LEFT',
	UP_RIGHT = 'RIGHT',
	UP_LEFT = 'LEFT',
	RIGHT_DOWN = 'BOTTOM',
	RIGHT_UP = 'TOP',
	LEFT_DOWN = 'BOTTOM',
	LEFT_UP = 'TOP',
	UP = 'TOP',
	DOWN = 'BOTTOM'
}

local DIRECTION_TO_COLUMN_ANCHOR_POINT = {
	DOWN_RIGHT = 'LEFT',
	DOWN_LEFT = 'RIGHT',
	UP_RIGHT = 'LEFT',
	UP_LEFT = 'RIGHT',
	RIGHT_DOWN = 'TOP',
	RIGHT_UP = 'BOTTOM',
	LEFT_DOWN = 'TOP',
	LEFT_UP = 'BOTTOM',
}

local DIRECTION_TO_HORIZONTAL_SPACING_MULTIPLIER = {
	DOWN_RIGHT = 1,
	DOWN_LEFT = -1,
	UP_RIGHT = 1,
	UP_LEFT = -1,
	RIGHT_DOWN = 1,
	RIGHT_UP = 1,
	LEFT_DOWN = -1,
	LEFT_UP = -1,
}

local DIRECTION_TO_VERTICAL_SPACING_MULTIPLIER = {
	DOWN_RIGHT = -1,
	DOWN_LEFT = -1,
	UP_RIGHT = 1,
	UP_LEFT = 1,
	RIGHT_DOWN = -1,
	RIGHT_UP = 1,
	LEFT_DOWN = -1,
	LEFT_UP = 1,
}

function UF:ConvertGroupDB(group)
	local db = self.db.units[group.groupName]
	if db.point and db.columnAnchorPoint then
		db.growthDirection = POINT_COLUMN_ANCHOR_TO_DIRECTION[db.point..db.columnAnchorPoint];
		db.point = nil;
		db.columnAnchorPoint = nil;
	end

	if db.growthDirection == 'UP' then
		db.growthDirection = 'UP_RIGHT'
	end

	if db.growthDirection == 'DOWN' then
		db.growthDirection = 'DOWN_RIGHT'
	end
end

function UF:Construct_UF(frame, unit)
	frame:SetScript('OnEnter', UnitFrame_OnEnter)
	frame:SetScript('OnLeave', UnitFrame_OnLeave)

	frame.SHADOW_SPACING = 3
	frame.CLASSBAR_YOFFSET = 0 --placeholder
	frame.BOTTOM_OFFSET = 0 --placeholder

	frame.RaisedElementParent = CreateFrame('Frame', nil, frame)
	frame.RaisedElementParent.TextureParent = CreateFrame('Frame', nil, frame.RaisedElementParent)
	frame.RaisedElementParent:SetFrameLevel(frame:GetFrameLevel() + 100)

	if not UF.groupunits[unit] then
		UF['Construct_'..gsub(E:StringTitle(unit), 't(arget)', 'T%1')..'Frame'](UF, frame, unit)
	else
		UF['Construct_'..E:StringTitle(UF.groupunits[unit])..'Frames'](UF, frame, unit)
	end

	return frame
end

function UF:GetObjectAnchorPoint(frame, point)
	if point == 'Frame' then
		return frame
	end

	local place = frame[point]
	if place and place:IsShown() then
		return place
	else
		return frame
	end
end

function UF:GetPositionOffset(position, offset)
	if not offset then offset = 2; end
	local x, y = 0, 0
	if strfind(position, 'LEFT') then
		x = offset
	elseif strfind(position, 'RIGHT') then
		x = -offset
	end

	if strfind(position, 'TOP') then
		y = -offset
	elseif strfind(position, 'BOTTOM') then
		y = offset
	end

	return x, y
end

function UF:GetAuraOffset(p1, p2)
	local x, y = 0, 0
	if p1 == 'RIGHT' and p2 == 'LEFT' then
		x = -3
	elseif p1 == 'LEFT' and p2 == 'RIGHT' then
		x = 3
	end

	if strfind(p1, 'TOP') and strfind(p2, 'BOTTOM') then
		y = -1
	elseif strfind(p1, 'BOTTOM') and strfind(p2, 'TOP') then
		y = 1
	end

	return x, y
end

function UF:GetAuraAnchorFrame(frame, attachTo)
	if attachTo == 'FRAME' then
		return frame
	elseif attachTo == 'BUFFS' and frame.Buffs then
		return frame.Buffs
	elseif attachTo == 'DEBUFFS' and frame.Debuffs then
		return frame.Debuffs
	elseif attachTo == 'HEALTH' and frame.Health then
		return frame.Health
	elseif attachTo == 'POWER' and frame.Power then
		return frame.Power
	elseif attachTo == 'TRINKET' and (frame.Trinket or frame.PVPSpecIcon) then
		local _, instanceType = GetInstanceInfo()
		return (instanceType == 'arena' and frame.Trinket) or frame.PVPSpecIcon
	else
		return frame
	end
end

function UF:ClearChildPoints(...)
	for i=1, select('#', ...) do
		local child = select(i, ...)
		child:ClearAllPoints()
	end
end

function UF:UpdateColors()
	local db = self.db.colors

	ElvUF.colors.tapped = E:SetColorTable(ElvUF.colors.tapped, db.tapped)
	ElvUF.colors.disconnected = E:SetColorTable(ElvUF.colors.disconnected, db.disconnected)
	ElvUF.colors.health = E:SetColorTable(ElvUF.colors.health, db.health)
	ElvUF.colors.power.MANA = E:SetColorTable(ElvUF.colors.power.MANA, db.power.MANA)
	ElvUF.colors.power.RAGE = E:SetColorTable(ElvUF.colors.power.RAGE, db.power.RAGE)
	ElvUF.colors.power.FOCUS = E:SetColorTable(ElvUF.colors.power.FOCUS, db.power.FOCUS)
	ElvUF.colors.power.ENERGY = E:SetColorTable(ElvUF.colors.power.ENERGY, db.power.ENERGY)
	ElvUF.colors.power.RUNIC_POWER = E:SetColorTable(ElvUF.colors.power.RUNIC_POWER, db.power.RUNIC_POWER)
	ElvUF.colors.power.PAIN = E:SetColorTable(ElvUF.colors.power.PAIN, db.power.PAIN)
	ElvUF.colors.power.FURY = E:SetColorTable(ElvUF.colors.power.FURY, db.power.FURY)
	ElvUF.colors.power.LUNAR_POWER = E:SetColorTable(ElvUF.colors.power.LUNAR_POWER, db.power.LUNAR_POWER)
	ElvUF.colors.power.INSANITY = E:SetColorTable(ElvUF.colors.power.INSANITY, db.power.INSANITY)
	ElvUF.colors.power.MAELSTROM = E:SetColorTable(ElvUF.colors.power.MAELSTROM, db.power.MAELSTROM)
	ElvUF.colors.power[ALTERNATE_POWER_INDEX] = E:SetColorTable(ElvUF.colors.power[ALTERNATE_POWER_INDEX], db.power.ALT_POWER)

	ElvUF.colors.threat[0] = E:SetColorTable(ElvUF.colors.threat[0], db.threat[0])
	ElvUF.colors.threat[1] = E:SetColorTable(ElvUF.colors.threat[1], db.threat[1])
	ElvUF.colors.threat[2] = E:SetColorTable(ElvUF.colors.threat[2], db.threat[2])
	ElvUF.colors.threat[3] = E:SetColorTable(ElvUF.colors.threat[3], db.threat[3])

	ElvUF.colors.selection[0] = E:SetColorTable(ElvUF.colors.selection[0], db.selection[0])
	ElvUF.colors.selection[1] = E:SetColorTable(ElvUF.colors.selection[1], db.selection[1])
	ElvUF.colors.selection[2] = E:SetColorTable(ElvUF.colors.selection[2], db.selection[2])
	ElvUF.colors.selection[3] = E:SetColorTable(ElvUF.colors.selection[3], db.selection[3])
	ElvUF.colors.selection[5] = E:SetColorTable(ElvUF.colors.selection[5], db.selection[5])
	ElvUF.colors.selection[6] = E:SetColorTable(ElvUF.colors.selection[6], db.selection[6])
	ElvUF.colors.selection[7] = E:SetColorTable(ElvUF.colors.selection[7], db.selection[7])
	ElvUF.colors.selection[8] = E:SetColorTable(ElvUF.colors.selection[8], db.selection[8])
	ElvUF.colors.selection[9] = E:SetColorTable(ElvUF.colors.selection[9], db.selection[9])
	ElvUF.colors.selection[13] = E:SetColorTable(ElvUF.colors.selection[13], db.selection[13])

	if not ElvUF.colors.ComboPoints then ElvUF.colors.ComboPoints = {} end
	ElvUF.colors.ComboPoints[1] = E:SetColorTable(ElvUF.colors.ComboPoints[1], db.classResources.comboPoints[1])
	ElvUF.colors.ComboPoints[2] = E:SetColorTable(ElvUF.colors.ComboPoints[2], db.classResources.comboPoints[2])
	ElvUF.colors.ComboPoints[3] = E:SetColorTable(ElvUF.colors.ComboPoints[3], db.classResources.comboPoints[3])

	--Monk, Mage, Paladin and Warlock, Death Knight
	if not ElvUF.colors.ClassBars then ElvUF.colors.ClassBars = {} end
	if not ElvUF.colors.ClassBars.MONK then ElvUF.colors.ClassBars.MONK = {} end
	ElvUF.colors.ClassBars.PALADIN = E:SetColorTable(ElvUF.colors.ClassBars.PALADIN, db.classResources.PALADIN)
	ElvUF.colors.ClassBars.MAGE = E:SetColorTable(ElvUF.colors.ClassBars.MAGE, db.classResources.MAGE)
	ElvUF.colors.ClassBars.MONK[1] = E:SetColorTable(ElvUF.colors.ClassBars.MONK[1], db.classResources.MONK[1])
	ElvUF.colors.ClassBars.MONK[2] = E:SetColorTable(ElvUF.colors.ClassBars.MONK[2], db.classResources.MONK[2])
	ElvUF.colors.ClassBars.MONK[3] = E:SetColorTable(ElvUF.colors.ClassBars.MONK[3], db.classResources.MONK[3])
	ElvUF.colors.ClassBars.MONK[4] = E:SetColorTable(ElvUF.colors.ClassBars.MONK[4], db.classResources.MONK[4])
	ElvUF.colors.ClassBars.MONK[5] = E:SetColorTable(ElvUF.colors.ClassBars.MONK[5], db.classResources.MONK[5])
	ElvUF.colors.ClassBars.MONK[6] = E:SetColorTable(ElvUF.colors.ClassBars.MONK[6], db.classResources.MONK[6])
	ElvUF.colors.ClassBars.DEATHKNIGHT = E:SetColorTable(ElvUF.colors.ClassBars.DEATHKNIGHT, db.classResources.DEATHKNIGHT)
	ElvUF.colors.ClassBars.WARLOCK = E:SetColorTable(ElvUF.colors.ClassBars.WARLOCK, db.classResources.WARLOCK)

	-- these are just holders.. to maintain and update tables
	if not ElvUF.colors.reaction.good then ElvUF.colors.reaction.good = {} end
	if not ElvUF.colors.reaction.bad then ElvUF.colors.reaction.bad = {} end
	if not ElvUF.colors.reaction.neutral then ElvUF.colors.reaction.neutral = {} end
	ElvUF.colors.reaction.good = E:SetColorTable(ElvUF.colors.reaction.good, db.reaction.GOOD)
	ElvUF.colors.reaction.bad = E:SetColorTable(ElvUF.colors.reaction.bad, db.reaction.BAD)
	ElvUF.colors.reaction.neutral = E:SetColorTable(ElvUF.colors.reaction.neutral, db.reaction.NEUTRAL)

	if not ElvUF.colors.smoothHealth then ElvUF.colors.smoothHealth = {} end
	ElvUF.colors.smoothHealth = E:SetColorTable(ElvUF.colors.smoothHealth, db.health)

	if not ElvUF.colors.smooth then ElvUF.colors.smooth = {1, 0, 0,	1, 1, 0} end
	-- end

	ElvUF.colors.reaction[1] = ElvUF.colors.reaction.bad
	ElvUF.colors.reaction[2] = ElvUF.colors.reaction.bad
	ElvUF.colors.reaction[3] = ElvUF.colors.reaction.bad
	ElvUF.colors.reaction[4] = ElvUF.colors.reaction.neutral
	ElvUF.colors.reaction[5] = ElvUF.colors.reaction.good
	ElvUF.colors.reaction[6] = ElvUF.colors.reaction.good
	ElvUF.colors.reaction[7] = ElvUF.colors.reaction.good
	ElvUF.colors.reaction[8] = ElvUF.colors.reaction.good
	ElvUF.colors.smooth[7] = ElvUF.colors.smoothHealth[1]
	ElvUF.colors.smooth[8] = ElvUF.colors.smoothHealth[2]
	ElvUF.colors.smooth[9] = ElvUF.colors.smoothHealth[3]

	ElvUF.colors.castColor = E:SetColorTable(ElvUF.colors.castColor, db.castColor)
	ElvUF.colors.castNoInterrupt = E:SetColorTable(ElvUF.colors.castNoInterrupt, db.castNoInterrupt)

	if not ElvUF.colors.DebuffHighlight then ElvUF.colors.DebuffHighlight = {} end
	ElvUF.colors.DebuffHighlight.Magic = E:SetColorTable(ElvUF.colors.DebuffHighlight.Magic, db.debuffHighlight.Magic)
	ElvUF.colors.DebuffHighlight.Curse = E:SetColorTable(ElvUF.colors.DebuffHighlight.Curse, db.debuffHighlight.Curse)
	ElvUF.colors.DebuffHighlight.Disease = E:SetColorTable(ElvUF.colors.DebuffHighlight.Disease, db.debuffHighlight.Disease)
	ElvUF.colors.DebuffHighlight.Poison = E:SetColorTable(ElvUF.colors.DebuffHighlight.Poison, db.debuffHighlight.Poison)
end

function UF:Update_StatusBars()
	local statusBarTexture = LSM:Fetch('statusbar', self.db.statusbar)
	for statusbar in pairs(UF.statusbars) do
		if statusbar then
			local useBlank = statusbar.isTransparent
			if statusbar.parent then
				useBlank = statusbar.parent.isTransparent
			end
			if statusbar:IsObjectType('StatusBar') then
				if not useBlank then
					statusbar:SetStatusBarTexture(statusBarTexture)
				end
			elseif statusbar:IsObjectType('Texture') then
				statusbar:SetTexture(statusBarTexture)
			end

			UF:Update_StatusBar(statusbar.bg or statusbar.BG, (not useBlank and statusBarTexture) or E.media.blankTex)
		end
	end
end

function UF:Update_StatusBar(statusbar, texture)
	if not statusbar then return end
	if not texture then texture = LSM:Fetch('statusbar', self.db.statusbar) end

	if statusbar:IsObjectType('StatusBar') then
		statusbar:SetStatusBarTexture(texture)
	elseif statusbar:IsObjectType('Texture') then
		statusbar:SetTexture(texture)
	end
end

function UF:Update_FontString(object)
	object:FontTemplate(LSM:Fetch('font', self.db.font), self.db.fontSize, self.db.fontOutline)
end

function UF:Update_FontStrings()
	local font, size, outline = LSM:Fetch('font', self.db.font), self.db.fontSize, self.db.fontOutline
	for obj in pairs(UF.fontstrings) do
		obj:FontTemplate(font, size, outline)
	end
end

function UF:Construct_Fader()
	return { UpdateRange = UF.UpdateRange }
end

function UF:Configure_Fader(frame)
	if frame.db and frame.db.enable and (frame.db.fader and frame.db.fader.enable) then
		if not frame:IsElementEnabled('Fader') then
			frame:EnableElement('Fader')
		end

		frame.Fader:SetOption('Hover', frame.db.fader.hover)
		frame.Fader:SetOption('Combat', frame.db.fader.combat)
		frame.Fader:SetOption('PlayerTarget', frame.db.fader.playertarget)
		frame.Fader:SetOption('Focus', frame.db.fader.focus)
		frame.Fader:SetOption('Health', frame.db.fader.health)
		frame.Fader:SetOption('Power', frame.db.fader.power)
		frame.Fader:SetOption('Vehicle', frame.db.fader.vehicle)
		frame.Fader:SetOption('Casting', frame.db.fader.casting)
		frame.Fader:SetOption('MinAlpha', frame.db.fader.minAlpha)
		frame.Fader:SetOption('MaxAlpha', frame.db.fader.maxAlpha)

		if frame ~= _G.ElvUF_Player then
			frame.Fader:SetOption('Range', frame.db.fader.range)
			frame.Fader:SetOption('UnitTarget', frame.db.fader.unittarget)
		end

		frame.Fader:SetOption('Smooth', (frame.db.fader.smooth > 0 and frame.db.fader.smooth) or nil)
		frame.Fader:SetOption('Delay', (frame.db.fader.delay > 0 and frame.db.fader.delay) or nil)

		frame.Fader:ClearTimers()
		frame.Fader.configTimer = E:ScheduleTimer(frame.Fader.ForceUpdate, 0.25, frame.Fader, true)
	elseif frame:IsElementEnabled('Fader') then
		frame:DisableElement('Fader')
		E:UIFrameFadeIn(frame, 1, frame:GetAlpha(), 1)
	end
end

function UF:Configure_FontString(obj)
	UF.fontstrings[obj] = true
	obj:FontTemplate() --This is temporary.
end

function UF:Update_AllFrames()
	if not E.private.unitframe.enable then return end
	UF:UpdateColors()
	UF:Update_FontStrings()
	UF:Update_StatusBars()

	for unit in pairs(UF.units) do
		if UF.db.units[unit].enable then
			UF[unit]:Update()
			UF[unit]:Enable()
			E:EnableMover(UF[unit].mover:GetName())
		else
			UF[unit]:Update()
			UF[unit]:Disable()
			E:DisableMover(UF[unit].mover:GetName())
		end
	end

	for unit, group in pairs(UF.groupunits) do
		if UF.db.units[group].enable then
			UF[unit]:Enable()
			UF[unit]:Update()
			E:EnableMover(UF[unit].mover:GetName())
		else
			UF[unit]:Disable()
			E:DisableMover(UF[unit].mover:GetName())
		end

		if UF[unit].isForced then
			UF:ForceShow(UF[unit])
		end
	end

	if UF.db.smartRaidFilter then
		UF:HandleSmartVisibility()
	else
		UF:UpdateAllHeaders()
	end
end

function UF:CreateAndUpdateUFGroup(group, numGroup)
	for i=1, numGroup do
		local unit = group..i
		local frameName = gsub(E:StringTitle(unit), 't(arget)', 'T%1')
		local frame = self[unit]

		if not frame then
			self.groupunits[unit] = group;
			frame = ElvUF:Spawn(unit, 'ElvUF_'..frameName)
			frame.index = i
			frame:SetParent(ElvUF_Parent)
			frame:SetID(i)
			self[unit] = frame
		end

		frameName = gsub(E:StringTitle(group), 't(arget)', 'T%1')
		frame.Update = function()
			UF['Update_'..E:StringTitle(frameName)..'Frames'](self, frame, self.db.units[group])
		end

		if self.db.units[group].enable then
			frame:Enable()

			if group == 'arena' then
				frame:SetAttribute('oUF-enableArenaPrep', true)
			end

			frame.Update()

			E:EnableMover(frame.mover:GetName())
		else
			frame:Disable()

			if group == 'arena' then
				frame:SetAttribute('oUF-enableArenaPrep', false)
			end

			-- for some reason the boss/arena 'uncheck disable' doesnt fire this, we need to so putting it here.
			if group == 'boss' or group == 'arena' then
				UF:Configure_Fader(frame)
			end

			E:DisableMover(frame.mover:GetName())
		end

		if frame.isForced then
			self:ForceShow(frame)
		end
	end
end

function UF:HeaderUpdateSpecificElement(group, elementName)
	local Header = self[group]
	assert(Header, 'Invalid group specified.')

	for i=1, Header:GetNumChildren() do
		local frame = select(i, Header:GetChildren())
		if frame and frame.Health then
			frame:UpdateElement(elementName)
		end
	end
end

--Keep an eye on this one, it may need to be changed too
--Reference: http://www.tukui.org/forums/topic.php?id=35332
function UF.groupPrototype:GetAttribute(name)
	return self.groups[1]:GetAttribute(name)
end

function UF.groupPrototype:Configure_Groups(Header)
	local db = UF.db.units[Header.groupName]

	local width, height, newCols, newRows = 0, 0, 0, 0
	local direction, dbWidth, dbHeight = db.growthDirection, db.width, db.height
	local xMult, yMult = DIRECTION_TO_HORIZONTAL_SPACING_MULTIPLIER[direction], DIRECTION_TO_VERTICAL_SPACING_MULTIPLIER[direction]
	local UNIT_HEIGHT = dbHeight + (db.infoPanel and db.infoPanel.enable and db.infoPanel.height or 0)

	local groupBy = db.groupBy
	local groupSpacing = db.groupSpacing
	local groupsPerRowCol = db.groupsPerRowCol
	local horizontalSpacing = db.horizontalSpacing
	local invertGroupingOrder = db.invertGroupingOrder
	local raidWideSorting = db.raidWideSorting
	local showPlayer = db.showPlayer
	local sortDir = db.sortDir
	local startFromCenter = db.startFromCenter
	local verticalSpacing = db.verticalSpacing

	local numGroups = Header.numGroups
	for i = 1, numGroups do
		local group = Header.groups[i]

		if group then
			UF:ConvertGroupDB(group)
			group:ClearAllPoints()
			group:ClearChildPoints()

			local point = DIRECTION_TO_POINT[direction]
			group:SetAttribute('point', point)

			if point == 'LEFT' or point == 'RIGHT' then
				group:SetAttribute('xOffset', horizontalSpacing * DIRECTION_TO_HORIZONTAL_SPACING_MULTIPLIER[direction])
				group:SetAttribute('yOffset', 0)
				group:SetAttribute('columnSpacing', verticalSpacing)
			else
				group:SetAttribute('xOffset', 0)
				group:SetAttribute('yOffset', verticalSpacing * DIRECTION_TO_VERTICAL_SPACING_MULTIPLIER[direction])
				group:SetAttribute('columnSpacing', horizontalSpacing)
			end

			if not group.isForced then
				if not group.initialized then
					group:SetAttribute('startingIndex', raidWideSorting and (-min(numGroups * (groupsPerRowCol * 5), _G.MAX_RAID_MEMBERS) + 1) or -4)
					group:Show()
					group.initialized = true
				end
				group:SetAttribute('startingIndex', 1)
			end

			if raidWideSorting and invertGroupingOrder then
				group:SetAttribute('columnAnchorPoint', INVERTED_DIRECTION_TO_COLUMN_ANCHOR_POINT[direction])
			else
				group:SetAttribute('columnAnchorPoint', DIRECTION_TO_COLUMN_ANCHOR_POINT[direction])
			end

			if not group.isForced then
				group:SetAttribute('maxColumns', raidWideSorting and numGroups or 1)
				group:SetAttribute('unitsPerColumn', raidWideSorting and (groupsPerRowCol * 5) or 5)
				group:SetAttribute('sortDir', sortDir)
				group:SetAttribute('showPlayer', showPlayer)
				UF.headerGroupBy[groupBy](group)
			end

			local groupWide = i == 1 and raidWideSorting and strsub('1,2,3,4,5,6,7,8', 1, numGroups + numGroups-1)
			group:SetAttribute('groupFilter', groupWide or tostring(i))
		end

		--MATH!! WOOT
		local point = DIRECTION_TO_GROUP_ANCHOR_POINT[direction]
		if raidWideSorting and startFromCenter then
			point = DIRECTION_TO_GROUP_ANCHOR_POINT['OUT_'..direction]
		end

		if (i - 1) % groupsPerRowCol == 0 then
			if DIRECTION_TO_POINT[direction] == 'LEFT' or DIRECTION_TO_POINT[direction] == 'RIGHT' then
				if group then group:Point(point, Header, point, 0, height * yMult) end
				height = height + UNIT_HEIGHT + verticalSpacing + groupSpacing
				newRows = newRows + 1
			else
				if group then group:Point(point, Header, point, width * xMult, 0) end
				width = width + dbWidth + horizontalSpacing + groupSpacing
				newCols = newCols + 1
			end
		else
			if DIRECTION_TO_POINT[direction] == 'LEFT' or DIRECTION_TO_POINT[direction] == 'RIGHT' then
				if newRows == 1 then
					if group then group:Point(point, Header, point, width * xMult, 0) end
					width = width + ((dbWidth + horizontalSpacing) * 5) + groupSpacing
					newCols = newCols + 1
				elseif group then
					group:Point(point, Header, point, ((((dbWidth + horizontalSpacing) * 5) * ((i-1) % groupsPerRowCol))+((i-1) % groupsPerRowCol)*groupSpacing) * xMult, (((UNIT_HEIGHT + verticalSpacing+groupSpacing) * (newRows - 1))) * yMult)
				end
			else
				if newCols == 1 then
					if group then group:Point(point, Header, point, 0, height * yMult) end
					height = height + ((UNIT_HEIGHT + verticalSpacing) * 5) + groupSpacing
					newRows = newRows + 1
				elseif group then
					group:Point(point, Header, point, (((dbWidth + horizontalSpacing +groupSpacing) * (newCols - 1))) * xMult, ((((UNIT_HEIGHT + verticalSpacing) * 5) * ((i-1) % groupsPerRowCol))+((i-1) % groupsPerRowCol)*groupSpacing) * yMult)
				end
			end
		end

		if height == 0 then
			height = height + ((UNIT_HEIGHT + verticalSpacing) * 5) +groupSpacing
		elseif width == 0 then
			width = width + ((dbWidth + horizontalSpacing) * 5) +groupSpacing
		end
	end

	Header:Size(width - horizontalSpacing - groupSpacing, height - verticalSpacing - groupSpacing)
end

function UF.groupPrototype:Update(Header)
	local group = Header.groupName

	UF[group].db = UF.db.units[group]

	for _, Group in ipairs(Header.groups) do
		Group.db = UF.db.units[group]
		Group:Update()
	end
end

function UF.groupPrototype:AdjustVisibility(Header)
	if not Header.isForced then
		local numGroups = Header.numGroups
		for i=1, #Header.groups do
			local group = Header.groups[i]
			if i <= numGroups and ((Header.db.raidWideSorting and i <= 1) or not Header.db.raidWideSorting) then
				group:Show()
			else
				if group.forceShow then
					group:Hide()
					group:SetAttribute('startingIndex', 1)
					UF:UnshowChildUnits(group, group:GetChildren())
				else
					group:Reset()
				end
			end
		end
	end
end

function UF.headerPrototype:ClearChildPoints()
	for i=1, self:GetNumChildren() do
		select(i, self:GetChildren()):ClearAllPoints()
	end
end

function UF.headerPrototype:UpdateChild(func, child, db)
	func(UF, child, db)

	local name = child:GetName()

	local target = name..'Target'
	if _G[target] then func(UF, _G[target], db) end

	local pet = name..'Pet'
	if _G[pet] then func(UF, _G[pet], db) end
end

function UF.headerPrototype:Update(isForced)
	local group = self.groupName
	local db = UF.db.units[group]
	local groupName = E:StringTitle(group)

	UF['Update_'..groupName..'Header'](UF, self, db, isForced)

	local i = 1
	local child = self:GetAttribute('child' .. i)
	local func = UF['Update_'..groupName..'Frames']

	while child do
		self:UpdateChild(func, child, db)

		i = i + 1
		child = self:GetAttribute('child' .. i)
	end
end

function UF.headerPrototype:Reset()
	self:SetAttribute('showPlayer', true)
	self:SetAttribute('showSolo', true)
	self:SetAttribute('showParty', true)
	self:SetAttribute('showRaid', true)
	self:SetAttribute('columnSpacing', nil)
	self:SetAttribute('columnAnchorPoint', nil)
	self:SetAttribute('groupBy', nil)
	self:SetAttribute('groupFilter', nil)
	self:SetAttribute('groupingOrder', nil)
	self:SetAttribute('maxColumns', nil)
	self:SetAttribute('nameList', nil)
	self:SetAttribute('point', nil)
	self:SetAttribute('sortDir', nil)
	self:SetAttribute('sortMethod', 'NAME')
	self:SetAttribute('startingIndex', nil)
	self:SetAttribute('strictFiltering', nil)
	self:SetAttribute('unitsPerColumn', nil)
	self:SetAttribute('xOffset', nil)
	self:SetAttribute('yOffset', nil)
	self:Hide()
end

UF.SmartSettings = {
	raid = {},
	raid40 = { numGroups = 8 },
	raidpet = { enable = false }
}

function UF:HandleSmartVisibility(skip)
	local sv = UF.SmartSettings
	sv.raid.numGroups = 6

	local _, instanceType, _, _, maxPlayers, _, _, instanceID = GetInstanceInfo()
	if instanceType == 'raid' or instanceType == 'pvp' then
		local maxInstancePlayers = UF.instanceMapIDs[instanceID]
		if maxInstancePlayers then
			maxPlayers =  maxInstancePlayers
		elseif not maxPlayers or maxPlayers == 0 then
			maxPlayers = 40
		end

		sv.raid.visibility = '[@raid6,noexists] hide;show'
		sv.raid40.visibility = '[@raid6,noexists] hide;show'
		sv.raid.enable = maxPlayers < 40
		sv.raid40.enable = maxPlayers == 40

		if sv.raid.enable then
			local maxGroups = E:Round(maxPlayers/5)
			if sv.raid.numGroups ~= maxGroups and maxGroups > 0 then
				sv.raid.numGroups = maxGroups
			end
		end
	else
		sv.raid.visibility = '[@raid6,noexists][@raid31,exists] hide;show'
		sv.raid40.visibility = '[@raid31,noexists] hide;show'
		sv.raid.enable = true
		sv.raid40.enable = true
	end

	UF:UpdateAllHeaders(true, skip)
end

function UF:ZONE_CHANGED_NEW_AREA()
	if UF.db.smartRaidFilter then
		UF:HandleSmartVisibility(true)
	end
end

function UF:PLAYER_ENTERING_WORLD(_, initLogin, isReload)
	UF:RegisterRaidDebuffIndicator()

	if initLogin then
		UF:Update_AllFrames()
	elseif isReload then
		UF:Update_AllFrames()
	elseif UF.db.smartRaidFilter then
		UF:HandleSmartVisibility(true)
	end
end

function UF:CreateHeader(parent, groupFilter, overrideName, template, groupName, headerTemplate)
	local group = parent.groupName or groupName
	local db = UF.db.units[group]
	ElvUF:SetActiveStyle('ElvUF_'..E:StringTitle(group))

	local header = ElvUF:SpawnHeader(overrideName, headerTemplate, nil,
		'oUF-initialConfigFunction', format('self:SetWidth(%d); self:SetHeight(%d);', db.width, db.height),
		'groupFilter', groupFilter, 'showParty', true, 'showRaid', true, 'showSolo', true,
		template and 'template', template
	)

	header.groupName = group
	header:SetParent(parent)
	header:Show()

	for k, v in pairs(UF.headerPrototype) do
		header[k] = v
	end

	return header
end

function UF:GetSmartVisibilitySetting(setting, group, smart, db)
	if smart then
		local options = UF.SmartSettings[group]
		local value = options and options[setting]

		if value ~= nil then
			return value
		end
	end

	return db[setting]
end

function UF:CreateAndUpdateHeaderGroup(group, groupFilter, template, headerTemplate, smart, skip)
	local db = UF.db.units[group]
	local Header = UF[group]

	local numGroups = UF:GetSmartVisibilitySetting('numGroups', group, smart, db)
	local visibility = UF:GetSmartVisibilitySetting('visibility', group, smart, db)
	local enable = UF:GetSmartVisibilitySetting('enable', group, smart, db)
	local name = E:StringTitle(group)

	if not Header then
		ElvUF:RegisterStyle('ElvUF_'..name, UF['Construct_'..name..'Frames'])
		ElvUF:SetActiveStyle('ElvUF_'..name)

		if numGroups then
			Header = CreateFrame('Frame', 'ElvUF_'..name, ElvUF_Parent, 'SecureHandlerStateTemplate');
			Header.groups = {}
			Header.groupName = group
			Header.template = Header.template or template
			Header.headerTemplate = Header.headerTemplate or headerTemplate
			if not UF.headerFunctions[group] then UF.headerFunctions[group] = {} end
			for k, v in pairs(self.groupPrototype) do
				UF.headerFunctions[group][k] = v
			end
		else
			Header = self:CreateHeader(ElvUF_Parent, groupFilter, 'ElvUF_'..name, template, group, headerTemplate)
		end

		Header:Show()

		self[group] = Header
		self.headers[group] = Header
	end

	local groupsChanged = (Header.numGroups ~= numGroups)
	local stateChanged = (Header.enableState ~= enable)
	Header.enableState = enable
	Header.numGroups = numGroups
	Header.db = db

	if numGroups then
		if db.raidWideSorting then
			if not Header.groups[1] then
				Header.groups[1] = self:CreateHeader(Header, nil, 'ElvUF_'..name..'Group1', template or Header.template, nil, headerTemplate or Header.headerTemplate)
			end
		else
			while numGroups > #Header.groups do
				local index = tostring(#Header.groups + 1)
				tinsert(Header.groups, self:CreateHeader(Header, index, 'ElvUF_'..name..'Group'..index, template or Header.template, nil, headerTemplate or Header.headerTemplate))
			end
		end

		if groupsChanged or not skip then
			UF.headerFunctions[group]:AdjustVisibility(Header)
			UF.headerFunctions[group]:Configure_Groups(Header)
		end
	else
		if not UF.headerFunctions[group] then UF.headerFunctions[group] = {} end
		if not UF.headerFunctions[group].Update then
			UF.headerFunctions[group].Update = function()
				local func = UF['Update_'..name..'Frames']
				UF['Update_'..name..'Header'](UF, Header, Header.db)

				for i = 1, Header:GetNumChildren() do
					Header:UpdateChild(func, select(i, Header:GetChildren()), Header.db)
				end
			end
		end
	end

	if stateChanged or not skip then
		UF.headerFunctions[group]:Update(Header)
	end

	if enable then
		if not Header.isForced then
			RegisterStateDriver(Header, 'visibility', visibility)
		end
		if Header.mover then
			E:EnableMover(Header.mover:GetName())
		end
	else
		UnregisterStateDriver(Header, 'visibility')
		Header:Hide()
		if Header.mover then
			E:DisableMover(Header.mover:GetName())
		end
	end
end

function UF:CreateAndUpdateUF(unit)
	assert(unit, 'No unit provided to create or update.')

	local frameName = gsub(E:StringTitle(unit), 't(arget)', 'T%1')
	if not self[unit] then
		self[unit] = ElvUF:Spawn(unit, 'ElvUF_'..frameName)
		self.units[unit] = unit
	end

	self[unit].Update = function()
		UF['Update_'..frameName..'Frame'](self, self[unit], self.db.units[unit])
	end

	if self[unit]:GetParent() ~= ElvUF_Parent then
		self[unit]:SetParent(ElvUF_Parent)
	end

	if self.db.units[unit].enable then
		self[unit]:Enable()
		self[unit].Update()
		E:EnableMover(self[unit].mover:GetName())
	else
		self[unit].Update()
		self[unit]:Disable()
		E:DisableMover(self[unit].mover:GetName())
	end
end

function UF:LoadUnits()
	for _, unit in pairs(UF.unitstoload) do
		UF:CreateAndUpdateUF(unit)
	end
	UF.unitstoload = nil

	for group, groupOptions in pairs(UF.unitgroupstoload) do
		local numGroup, template = unpack(groupOptions)
		UF:CreateAndUpdateUFGroup(group, numGroup, template)
	end
	UF.unitgroupstoload = nil

	for group, groupOptions in pairs(UF.headerstoload) do
		local groupFilter, template, headerTemplate
		if type(groupOptions) == 'table' then
			groupFilter, template, headerTemplate = unpack(groupOptions)
		end

		UF:CreateAndUpdateHeaderGroup(group, groupFilter, template, headerTemplate)
	end
	UF.headerstoload = nil
end

function UF:RegisterRaidDebuffIndicator()
	local ORD = E.oUF_RaidDebuffs or _G.oUF_RaidDebuffs
	if ORD then
		ORD:ResetDebuffData()

		local _, instanceType = GetInstanceInfo()
		if instanceType == 'party' or instanceType == 'raid' then
			local instance = E.global.unitframe.raidDebuffIndicator.instanceFilter
			local instanceSpells = ((E.global.unitframe.aurafilters[instance] and E.global.unitframe.aurafilters[instance].spells) or E.global.unitframe.aurafilters.RaidDebuffs.spells)
			ORD:RegisterDebuffs(instanceSpells)
		else
			local other = E.global.unitframe.raidDebuffIndicator.otherFilter
			local otherSpells = ((E.global.unitframe.aurafilters[other] and E.global.unitframe.aurafilters[other].spells) or E.global.unitframe.aurafilters.CCDebuffs.spells)
			ORD:RegisterDebuffs(otherSpells)
		end
	end
end

function UF:UpdateAllHeaders(smart, skip)
	if E.private.unitframe.disabledBlizzardFrames.party then
		ElvUF:DisableBlizzard('party')
	end

	for group in pairs(UF.headers) do
		UF:CreateAndUpdateHeaderGroup(group, nil, nil, nil, smart, skip)
	end
end

function UF:DisableBlizzard()
	if (not E.private.unitframe.disabledBlizzardFrames.raid) and (not E.private.unitframe.disabledBlizzardFrames.party) then return; end
	if not CompactRaidFrameManager_SetSetting then
		E:StaticPopup_Show('WARNING_BLIZZARD_ADDONS')
	else
		CompactRaidFrameManager_SetSetting('IsShown', '0')
		_G.UIParent:UnregisterEvent('GROUP_ROSTER_UPDATE')
		_G.CompactRaidFrameManager:UnregisterAllEvents()
		_G.CompactRaidFrameManager:SetParent(hiddenParent)
	end
end

local function insecureOnShow(self)
	self:Hide()
end

local HandleFrame = function(baseName, doNotReparent)
	local frame
	if type(baseName) == 'string' then
		frame = _G[baseName]
	else
		frame = baseName
	end

	if frame then
		frame:UnregisterAllEvents()
		frame:Hide()

		if not doNotReparent then
			frame:SetParent(hiddenParent)
		end

		local health = frame.healthBar or frame.healthbar
		if health then
			health:UnregisterAllEvents()
		end

		local power = frame.manabar
		if power then
			power:UnregisterAllEvents()
		end

		local spell = frame.castBar or frame.spellbar
		if spell then
			spell:UnregisterAllEvents()
		end

		local altpowerbar = frame.powerBarAlt
		if altpowerbar then
			altpowerbar:UnregisterAllEvents()
		end

		local buffFrame = frame.BuffFrame
		if buffFrame then
			buffFrame:UnregisterAllEvents()
		end
	end
end

function ElvUF:DisableBlizzard(unit)
	if not unit then return end

	if (unit == 'player') and E.private.unitframe.disabledBlizzardFrames.player then
		local PlayerFrame = _G.PlayerFrame
		HandleFrame(PlayerFrame)

		-- For the damn vehicle support:
		PlayerFrame:RegisterEvent('PLAYER_ENTERING_WORLD')
		PlayerFrame:RegisterEvent('UNIT_ENTERING_VEHICLE')
		PlayerFrame:RegisterEvent('UNIT_ENTERED_VEHICLE')
		PlayerFrame:RegisterEvent('UNIT_EXITING_VEHICLE')
		PlayerFrame:RegisterEvent('UNIT_EXITED_VEHICLE')

		-- User placed frames don't animate
		PlayerFrame:SetMovable(true)
		PlayerFrame:SetUserPlaced(true)
		PlayerFrame:SetDontSavePosition(true)
	elseif (unit == 'pet') and E.private.unitframe.disabledBlizzardFrames.player then
		HandleFrame(_G.PetFrame)
	elseif (unit == 'target') and E.private.unitframe.disabledBlizzardFrames.target then
		HandleFrame(_G.TargetFrame)
		HandleFrame(_G.ComboFrame)
	elseif (unit == 'focus') and E.private.unitframe.disabledBlizzardFrames.focus then
		HandleFrame(_G.FocusFrame)
		HandleFrame(_G.TargetofFocusFrame)
	elseif (unit == 'targettarget') and E.private.unitframe.disabledBlizzardFrames.target then
		HandleFrame(_G.TargetFrameToT)
	elseif (unit:match('boss%d?$')) and E.private.unitframe.disabledBlizzardFrames.boss then
		local id = unit:match('boss(%d)')
		if id then
			HandleFrame('Boss' .. id .. 'TargetFrame')
		else
			for i = 1, _G.MAX_BOSS_FRAMES do
				HandleFrame(('Boss%dTargetFrame'):format(i))
			end
		end
	elseif (unit:match('party%d?$')) and E.private.unitframe.disabledBlizzardFrames.party then
		local id = unit:match('party(%d)')
		if id then
			HandleFrame('PartyMemberFrame' .. id)
		else
			for i=1, 4 do
				HandleFrame(('PartyMemberFrame%d'):format(i))
			end
		end
		HandleFrame(_G.PartyMemberBackground)
	elseif (unit:match('arena%d?$')) and E.private.unitframe.disabledBlizzardFrames.arena then
		local id = unit:match('arena(%d)')
		if id then
			HandleFrame('ArenaEnemyFrame' .. id)
		else
			for i = 1, _G.MAX_ARENA_ENEMIES do
				HandleFrame(format('ArenaEnemyFrame%d', i))
			end
		end

		-- Blizzard_ArenaUI should not be loaded
		Arena_LoadUI = E.noop
		SetCVar('showArenaEnemyFrames', '0', 'SHOW_ARENA_ENEMY_FRAMES_TEXT')
	elseif unit:match('nameplate%d+$') then
		local frame = C_NamePlate_GetNamePlateForUnit(unit)
		if frame and frame.UnitFrame then
			if not frame.UnitFrame.isHooked then
				frame.UnitFrame:HookScript('OnShow', insecureOnShow)
				frame.UnitFrame.isHooked = true
			end

			HandleFrame(frame.UnitFrame, true)
		end
	end
end

function UF:ADDON_LOADED(_, addon)
	if addon ~= 'Blizzard_ArenaUI' then return; end
	ElvUF:DisableBlizzard('arena')
	self:UnregisterEvent('ADDON_LOADED');
end

function UF:UnitFrameThreatIndicator_Initialize(_, unitFrame)
	unitFrame:UnregisterAllEvents() --Arena Taint Fix
end

function UF:ResetUnitSettings(unit)
	E:CopyTable(self.db.units[unit], P.unitframe.units[unit]);

	if self.db.units[unit].buffs and self.db.units[unit].buffs.sizeOverride then
		self.db.units[unit].buffs.sizeOverride = P.unitframe.units[unit].buffs.sizeOverride or 0
	end

	if self.db.units[unit].debuffs and self.db.units[unit].debuffs.sizeOverride then
		self.db.units[unit].debuffs.sizeOverride = P.unitframe.units[unit].debuffs.sizeOverride or 0
	end

	self:Update_AllFrames()
end

function UF:ToggleForceShowGroupFrames(unitGroup, numGroup)
	for i=1, numGroup do
		if self[unitGroup..i] and not self[unitGroup..i].isForced then
			UF:ForceShow(self[unitGroup..i])
		elseif self[unitGroup..i] then
			UF:UnforceShow(self[unitGroup..i])
		end
	end
end

local Blacklist = {
	arena = {
		enable = true,
		fader = true,
	},
	assist = {
		enable = true,
		fader = true,
	},
	boss = {
		enable = true,
		fader = true,
	},
	focus = {
		enable = true,
		fader = true,
	},
	focustarget = {
		enable = true,
		fader = true,
	},
	party = {
		enable = true,
		visibility = true,
		fader = true,
	},
	pet = {
		enable = true,
		fader = true,
	},
	pettarget = {
		enable = true,
		fader = true,
	},
	player = {
		enable = true,
		aurabars = true,
		fader = true,
		buffs = {
			priority = true,
			minDuration = true,
			maxDuration = true,
		},
		debuffs = {
			priority = true,
			minDuration = true,
			maxDuration = true,
		},
	},
	raid = {
		enable = true,
		fader = true,
		visibility = true,
	},
	raid40 = {
		enable = true,
		fader = true,
		visibility = true,
	},
	raidpet = {
		enable = true,
		fader = true,
		visibility = true,
	},
	tank = {
		fader = true,
		enable = true,
	},
	target = {
		fader = true,
		enable = true,
	},
	targettarget = {
		fader = true,
		enable = true,
	},
	targettargettarget = {
		fader = true,
		enable = true,
	},
}

function UF:MergeUnitSettings(from, to)
	if from == to then
		E:Print(L["You cannot copy settings from the same unit."])
		return
	end

	E:CopyTable(UF.db.units[to], E:FilterTableFromBlacklist(UF.db.units[from], Blacklist[to]))

	UF:Update_AllFrames()
end

function UF:UpdateBackdropTextureColor(r, g, b)
	local m = 0.35
	local n = self.isTransparent and (m * 2) or m

	if self.invertColors then
		local nn = n;n=m;m=nn
	end

	if self.isTransparent then
		if self.backdrop then
			local _, _, _, a = self.backdrop:GetBackdropColor()
			self.backdrop:SetBackdropColor(r * n, g * n, b * n, a)
		else
			local parent = self:GetParent()
			if parent and parent.template then
				local _, _, _, a = parent:GetBackdropColor()
				parent:SetBackdropColor(r * n, g * n, b * n, a)
			end
		end
	end

	local bg = self.bg or self.BG
	if bg and bg:IsObjectType('Texture') and not bg.multiplier then
		if self.custom_backdrop then
			bg:SetVertexColor(self.custom_backdrop.r, self.custom_backdrop.g, self.custom_backdrop.b, self.custom_backdrop.a or 1)
		else
			bg:SetVertexColor(r * m, g * m, b * m, 1)
		end
	end
end

function UF:SetStatusBarBackdropPoints(statusBar, statusBarTex, backdropTex, statusBarOrientation, reverseFill)
	backdropTex:ClearAllPoints()
	if statusBarOrientation == 'VERTICAL' then
		if reverseFill then
			backdropTex:Point('BOTTOMRIGHT', statusBar, 'BOTTOMRIGHT')
			backdropTex:Point('TOPRIGHT', statusBarTex, 'BOTTOMRIGHT')
			backdropTex:Point('TOPLEFT', statusBarTex, 'BOTTOMLEFT')
		else
			backdropTex:Point('TOPLEFT', statusBar, 'TOPLEFT')
			backdropTex:Point('BOTTOMLEFT', statusBarTex, 'TOPLEFT')
			backdropTex:Point('BOTTOMRIGHT', statusBarTex, 'TOPRIGHT')
		end
	else
		if reverseFill then
			backdropTex:Point('TOPRIGHT', statusBarTex, 'TOPLEFT')
			backdropTex:Point('BOTTOMRIGHT', statusBarTex, 'BOTTOMLEFT')
			backdropTex:Point('BOTTOMLEFT', statusBar, 'BOTTOMLEFT')
		else
			backdropTex:Point('TOPLEFT', statusBarTex, 'TOPRIGHT')
			backdropTex:Point('BOTTOMLEFT', statusBarTex, 'BOTTOMRIGHT')
			backdropTex:Point('BOTTOMRIGHT', statusBar, 'BOTTOMRIGHT')
		end
	end
end

function UF:ToggleTransparentStatusBar(isTransparent, statusBar, backdropTex, adjustBackdropPoints, invertColors, reverseFill)
	statusBar.isTransparent = isTransparent
	statusBar.invertColors = invertColors
	statusBar.backdropTex = backdropTex

	if not statusBar.hookedColor then
		hooksecurefunc(statusBar, 'SetStatusBarColor', UF.UpdateBackdropTextureColor)
		statusBar.hookedColor = true
	end

	local parent = statusBar:GetParent()
	local orientation = statusBar:GetOrientation()
	if isTransparent then
		if statusBar.backdrop then
			statusBar.backdrop:SetTemplate('Transparent', nil, nil, nil, true)
		elseif parent.template then
			parent:SetTemplate('Transparent', nil, nil, nil, true)
		end

		statusBar:SetStatusBarTexture(0, 0, 0, 0)
		UF:Update_StatusBar(statusBar.bg or statusBar.BG, E.media.blankTex)

		local barTexture = statusBar:GetStatusBarTexture()
		barTexture:SetInside(nil, 0, 0) --This fixes Center Pixel offset problem

		UF:SetStatusBarBackdropPoints(statusBar, barTexture, backdropTex, orientation, reverseFill)
	else
		if statusBar.backdrop then
			statusBar.backdrop:SetTemplate(nil, nil, nil, nil, true)
		elseif parent.template then
			parent:SetTemplate(nil, nil, nil, nil, true)
		end

		local texture = LSM:Fetch('statusbar', self.db.statusbar)
		statusBar:SetStatusBarTexture(texture)
		UF:Update_StatusBar(statusBar.bg or statusBar.BG, texture)

		local barTexture = statusBar:GetStatusBarTexture()
		barTexture:SetInside(nil, 0, 0)

		if adjustBackdropPoints then
			UF:SetStatusBarBackdropPoints(statusBar, barTexture, backdropTex, orientation, reverseFill)
		end
	end
end

function UF:TargetSound(unit)
	if UnitExists(unit) and not IsReplacingUnit() then
		if UnitIsEnemy(unit, 'player') then
			PlaySound(SOUNDKIT_IG_CREATURE_AGGRO_SELECT)
		elseif UnitIsFriend('player', unit) then
			PlaySound(SOUNDKIT_IG_CHARACTER_NPC_SELECT)
		else
			PlaySound(SOUNDKIT_IG_CREATURE_NEUTRAL_SELECT)
		end
	else
		PlaySound(SOUNDKIT_INTERFACE_SOUND_LOST_TARGET_UNIT)
	end
end

function UF:PLAYER_FOCUS_CHANGED()
	if E.db.unitframe.targetSound then
		UF:TargetSound('focus')
	end
end

function UF:PLAYER_TARGET_CHANGED()
	if E.db.unitframe.targetSound then
		UF:TargetSound('target')
	end
end

function UF:AfterStyleCallback()
	-- this will wait until after ouf pushes `EnableElement` onto the newly spawned frames
	-- calling an update onto assist or tank in the styleFunc is before the `EnableElement`
	-- that would cause the auras to be shown when a new frame is spawned (tank2, assist2)
	-- even when they are disabled. this makes sure the update happens after so its proper.
	if self.unitframeType == 'tank' or self.unitframeType == 'tanktarget' then
		UF:Update_TankFrames(self, E.db.unitframe.units.tank)
		UF:Update_FontStrings()
	elseif self.unitframeType == 'assist' or self.unitframeType == 'assisttarget' then
		UF:Update_AssistFrames(self, E.db.unitframe.units.assist)
		UF:Update_FontStrings()
	end
end

function UF:Initialize()
	UF.db = E.db.unitframe
	UF.thinBorders = UF.db.thinBorders

	UF.SPACING = (UF.thinBorders or E.twoPixelsPlease) and 0 or 1
	UF.BORDER = (UF.thinBorders and not E.twoPixelsPlease) and 1 or 2

	if E.private.unitframe.enable ~= true then return end
	UF.Initialized = true

	E.ElvUF_Parent = CreateFrame('Frame', 'ElvUF_Parent', E.UIParent, 'SecureHandlerStateTemplate');
	E.ElvUF_Parent:SetFrameStrata('LOW')
	RegisterStateDriver(E.ElvUF_Parent, 'visibility', '[petbattle] hide; show')

	UF:UpdateColors()
	ElvUF:RegisterInitCallback(UF.AfterStyleCallback)
	ElvUF:RegisterStyle('ElvUF', function(frame, unit)
		UF:Construct_UF(frame, unit)
	end)
	ElvUF:SetActiveStyle('ElvUF')
	UF:LoadUnits()

	UF:RegisterEvent('PLAYER_ENTERING_WORLD')
	UF:RegisterEvent('ZONE_CHANGED_NEW_AREA')
	UF:RegisterEvent('PLAYER_TARGET_CHANGED')
	UF:RegisterEvent('PLAYER_FOCUS_CHANGED')

	if E.private.unitframe.disabledBlizzardFrames.party and E.private.unitframe.disabledBlizzardFrames.raid then
		UF:DisableBlizzard()
	end

	if (not E.private.unitframe.disabledBlizzardFrames.party) and (not E.private.unitframe.disabledBlizzardFrames.raid) then
		E.RaidUtility.Initialize = E.noop
	end

	if E.private.unitframe.disabledBlizzardFrames.arena then
		UF:SecureHook('UnitFrameThreatIndicator_Initialize')

		if not IsAddOnLoaded('Blizzard_ArenaUI') then
			UF:RegisterEvent('ADDON_LOADED')
		else
			ElvUF:DisableBlizzard('arena')
		end
	end

	UF:UpdateRangeCheckSpells()

	local ORD = E.oUF_RaidDebuffs or _G.oUF_RaidDebuffs
	if not ORD then return end
	ORD.ShowDispellableDebuff = true
	ORD.FilterDispellableDebuff = true
	ORD.MatchBySpellName = false
end

E:RegisterInitialModule(UF:GetName())
