local E = select(2, ...) -- Engine
local CO,L,UF,TT = E:LoadModules("Config", "Locale", "Unitframes", "Tooltip")

--[[-------------------------------------------------------------------------

	We are caching globals, since making those functions local,
	results in a slight performance boost and therefore
	in less CPU time. Exactly what we are aiming for.

-------------------------------------------------------------------------]]--
local _
local format 					= string.format
local select 					= select
local CreateFrame 				= CreateFrame
local RegisterUnitWatch 		= RegisterUnitWatch
local UnitExists 				= UnitExists
local UnitClass 				= UnitClass
local UnitName 					= UnitName
local UnitLevel 				= UnitLevel
local UnitIsConnected 			= UnitIsConnected
local UnitHealth 				= UnitHealth
local UnitHealthMax 			= UnitHealthMax
local UnitPower 				= UnitPower
local UnitPowerMax				= UnitPowerMax
local UnitStagger				= UnitStagger
local WarlockPowerBar_UnitPower	= WarlockPowerBar_UnitPower
local GetRuneCooldown			= GetRuneCooldown
local UnitGroupRolesAssigned 	= UnitGroupRolesAssigned
local GetRaidTargetIndex 		= GetRaidTargetIndex
local SetRaidTargetIconTexture 	= SetRaidTargetIconTexture
local UnitInParty 				= UnitInParty
local UnitInRaid 				= UnitInRaid
local UnitIsGroupLeader 		= UnitIsGroupLeader
local UnitIsGroupAssistant 		= UnitIsGroupAssistant
local GetSpecialization 		= GetSpecialization
local GetSpecializationInfoForClassID = GetSpecializationInfoForClassID
local RegisterStateDriver 		= RegisterStateDriver
local InCombatLockdown 			= InCombatLockdown
-----------------------------------------------------------------------------

UF.Update = CreateFrame("Frame")

-- Border width in px
UF.UNITFRAMES_BORDER_SIZE					= 1

UF.LAST_ALT_POWER_VALUE 					= -1
UF.ALTERNATE_MAX_VALUE 						= 100000

UF.UNITFRAMES_RANGE_UPDATE 					= 0.5 -- Range update frequency in seconds

UF.ROLE_TANK_TEXTURE 						= [[Interface\AddOns\CUI\Textures\icons\TANK]]
UF.ROLE_HEAL_TEXTURE 						= [[Interface\AddOns\CUI\Textures\icons\HEALER]]
UF.ROLE_DPS_TEXTURE 						= [[Interface\AddOns\CUI\Textures\icons\DAMAGER]]

UF.RoleTexture = {
	["TANK"] 	= UF.ROLE_TANK_TEXTURE,
	["HEALER"] 	= UF.ROLE_HEAL_TEXTURE,
	["DAMAGER"] = UF.ROLE_DPS_TEXTURE,
}

local AlternatePowers = {4,5,7,9,12,16,30} -- Supported power types
local AlternatePowerId, AlternatePowerMax, CurrentClass, SoulShards

UF.LastAlternatePowerFrame = nil
UF.AlternatePowerUpdate = false
UF.HolderVisibilityOverride = false

UF.FrameLevel = {
	["health"]					= 3,
	["portrait"]				= 5,
	["power"]					= 8
}

UF.UFSuffix = {"Container","Background","Overlay","Border"}
UF.UFSuffixOrder = {[1] = "Border",[2] = "Overlay",[3] = "Background",[4] = "Container"}
UF.ToCreate = {["arena"] = 5,["party"] = 5,["boss"] = 5,["raid"] = 20,["raid40"] = 40}

UF.UnitTypes = {"Player", "Target", "Focus", "Focustarget", "Pet", "Tot", "Party", "Raid", "Boss"}

function UF:CreateBar(name, strata, sizeX, sizeY, point, parent, enablemouse, enablemousewheel, enablekeyboard)
	
	local Frame, CurrentFrameStrataValue, OverlayFrameStrata, OptionsSource, rgba, Suffix
	local currentSuffix, currentFrame -- For post processing
	local MainFrame = E:NewFrame("Statusbar", name, strata, sizeX, sizeY, point, parent, enablemouse, enablemousewheel, enablekeyboard)
	local MainFramePlain = E:removeDigits(name)
	local LastSuffix = ""
	
	for _, Suffix in pairs(UF.UFSuffix) do
		if Suffix == "Overlay" then
			Frame = E:NewFrame("Statusbar", name .. Suffix, strata, sizeX, sizeY, point, MainFrame, enablemouse, enablemousewheel, enablekeyboard)
			-- Frame:SetStatusBarTexture("Interface/AddOns/CUI/Textures/minimalistic_dark/minimalistic")
			Frame:SetStatusBarTexture(CO.Media:Fetch("statusbar", CO.db.profile.unitframe.units["all"]['barTexture']))
			Frame:GetStatusBarTexture():SetHorizTile(false)
			Frame:SetMinMaxValues(0, 100)
			Frame:SetValue(0)

			E:RegisterStatusBar(Frame)
		else
			Frame = E:NewFrame("Frame", name .. Suffix, strata, sizeX, sizeY, point, MainFrame, enablemouse, enablemousewheel, enablekeyboard)
		end
		
		-- Set specific color for overlay
		if Suffix == "Overlay" then
			rgba = {1,1,1,1}
			
			E.LibSmooth:SmoothBar(Frame)
			
			MainFrame.Overlay = Frame
			MainFrame.SetOverlayColor = function(r,g,b,a) MainFrame.Overlay:GetStatusBarTexture():SetVertexColor(r, g, b, a) end
			MainFrame.GetValue = function() MainFrame.Overlay:GetValue() end
			MainFrame.SetValue = function(val) MainFrame.Overlay:SetValue(val) end
			MainFrame.GetMinMaxValues = function() return unpack({MainFrame.Overlay:GetMinMaxValues()}) end
			MainFrame.SetMinMaxValues = function(min,max) MainFrame.Overlay:SetMinMaxValues(min, max) end
			
			CurrentFrameStrataValue = E:tableContainsValue(E.FrameStratas, MainFrame.Overlay:GetFrameStrata())
			OverlayFrameStrata = E.FrameStratas[CurrentFrameStrataValue + 1]
			
			Frame:GetStatusBarTexture():SetVertexColor(rgba[1], rgba[2], rgba[3], rgba[4])
			
			Frame:SetAllPoints(MainFrame)
		elseif Suffix == "Border" then
			MainFrame.Border = Frame
			
			Frame:SetBackdrop({
				edgeFile = [[Interface\Buttons\WHITE8X8]], 
				edgeSize = 1, 
			})
			Frame:SetBackdropBorderColor(0.15, 0.15, 0.15, 1)
		elseif Suffix == "Container" then
			--Frame:GetStatusBarTexture():SetVertexColor(0,0,0,0)
		else
			Frame.Tex = Frame:CreateTexture(nil)
			Frame.Tex:SetAllPoints(Frame)
			Frame.Tex:SetColorTexture(0.15,0.15,0.15,0.85)
		
			MainFrame.Background = Frame
			MainFrame.Background.SetAlpha = function(val) MainFrame.Background.Tex:SetColorTexture(0.1, 0.1, 0.1, val) end
			--Frame:GetStatusBarTexture():SetVertexColor(0.1, 0.1, 0.1, 0.85)
		end
		
		LastSuffix = Suffix
		
		--Frame:Show()
	end
	
	MainFrame:SetFrameLevel(UF.FrameLevel[MainFramePlain] or 1)
	
	Suffix = UF.UFSuffixOrder[1]
	
	-- Set parents correctly
	for i=1, #UF.UFSuffixOrder do
		
		if not currentSuffix then
			E.Frames[name .. UF.UFSuffixOrder[4]]:SetParent(E.Frames[name])
			E.Frames[name .. UF.UFSuffixOrder[4]]:SetPoint("CENTER", E.Frames[name], "CENTER", 0, 0)
		end
		
		currentSuffix = UF.UFSuffixOrder[i]

		
		if currentSuffix and currentSuffix ~= Suffix then
			currentFrame = E.Frames[name .. currentSuffix]
			E.Frames[name .. Suffix]:SetParent(currentFrame)
			E.Frames[name .. Suffix]:SetPoint("CENTER", currentFrame, "CENTER", 0, 0)
		end
		
		-- Parent Suffix
		Suffix = UF.UFSuffixOrder[i]
	end
	
	return MainFrame
end

function UF:GetUnitSpecs(Unit)
	local classID, specID, name, description, iconID, role, isRecommended, isAllowed
	classID 	= select(3,UnitClass(Unit))
	local data 	= {}
	
	for i=1,GetNumSpecializationsForClassID(classID) do
		specID, name, description, iconID, role, isRecommended, isAllowed = GetSpecializationInfoForClassID(classID, i)
		data[i] = {specID, name, description, iconID, role, isRecommended, isAllowed}
	end
	
	return data
end

function UF:UpdateBarColor(Bar, RGBA)
	local BarTexture = Bar:GetStatusBarTexture()
	local CurrentAlpha = select(4,BarTexture:GetVertexColor())
	BarTexture:SetVertexColor(RGBA[1], RGBA[2], RGBA[3], RGBA[4] or CurrentAlpha or 1)
end

-- Unitframes hover script
function UF:FrameHovered(object, state)
	-- Show Tooltip
	if state == true then -- On Hover
		GameTooltip_SetDefaultAnchor(GameTooltip, TT.CurrentTooltipAnchor)
		GameTooltip:SetOwner(TT.CurrentTooltipAnchor, "ANCHOR_NONE")
		
		if GameTooltip:IsOwned(TT.CurrentTooltipAnchor) then
			GameTooltip:SetUnit(object:GetAttribute("unit"))
		end
	else
		-- GameTooltip:Hide() -- Immediately hides the tooltip. Fades as default if not specified
	end
end

function UF:SetHoverScript(Frame, State)
	if State == true then
		Frame:SetScript('OnEnter', function(self) UF:FrameHovered(self, true) end)
		Frame:SetScript('OnLeave', function(self) UF:FrameHovered(self, false) end)
		Frame:EnableMouse(true)
	else
		Frame:SetScript('OnEnter', function() return end)
		Frame:SetScript('OnLeave', function() return end)
		Frame:EnableMouse(false)
	end
end

function UF:SetUnitDummys(state)
	for k,v in pairs(UF.Frames) do
		if state == true then
			v:SetAttribute("unit", "player")
			v.Fonts.Index:Show()
		else
			v:SetAttribute("unit", v.Unit)
			v.Fonts.Index:Hide()
		end
	end
	
	UF:OverrideHolderVisibility(state)
end

-- Dynamically create every required text frame and store their target at index 1
-- @TODO:
-- Find a home for Raid and Party Fonts
function UF:InitializeFonts()

	local NumToCreate, Font,  FontType, FontObjectName, FontParentName, AddStr

	for k,v in pairs(UF.RequiredFonts) do
	
		FontType = E:firstToUpper(v)
	
		for key, unit in pairs(UF.UnitTypes) do
			
			NumToCreate	= UF.ToCreate[string.lower(unit)] or 1
		
			-- Problem: the Party and Raid Fonts are displayed in the center or the screen instead of being invisible like their parent
			-- Possible Fix: To be done
			for i=1, NumToCreate do
				if NumToCreate ~= 1 then AddStr = i else AddStr = "" end
				
				-- Prevent creation of health, power and level frames in raid
				-- if string.lower(unit) == "raid" and string.lower(FontType) ~= "name" and string.lower(FontType) ~= "level" then break end
				
				FontObjectName = unit .. FontType .. AddStr .."Font"
				FontParentName = unit .. "Health" .. AddStr .. "Overlay"
				
				Font = E:NewFont(FontObjectName, "OVERLAY", E.Frames[FontParentName])
				-- Set index 1 of Font Object to unit (for later identification purposes)
				E.Frames[FontObjectName] 				= Font
				E.Frames[FontObjectName]["unitId"] 		= unit .. AddStr
				E.Frames[FontObjectName]["unitIdRaw"] 	= unit
				E.Frames[FontObjectName]["unitIdNum"]	= AddStr
				E.Frames[FontObjectName]["unitIdNum"]	= AddStr
				E.Frames[FontObjectName]["fontType"]	= FontType
				
				-- Initial call to GetFontInfo initializes the font
				E:GetFontInfo(Font)
			end
		end
	end
end

local function GetAltPowerValue(id)
	local value
	if id == 30 then
		value = UnitStagger("player")
	elseif id == 7 then
		value = WarlockPowerBar_UnitPower("player")
	else
		value = UnitPower("player", id)
	end
	
	return value
end

local function GetAltPowerMax(id)
	local value
	if id == 30 then
		-- Use 60% of maximum HP for stagger (ID = 30)
		value = UnitHealthMax("player") * 0.6
	else
		value = UnitPowerMax("player", id)
	end
	
	return value
end

function UF:SetupAlternatePower()
	
	UF.AlternatePowerUpdate = false
	if UF.LastAlternatePowerFrame then UF.LastAlternatePowerFrame:Hide() end
	
	local barName, altPower, altPowerOverlay, currentAltPowerCluster, AlternatePowerMainFrame, currentSpecialization, currentVisibilityCondition
	AlternatePowerMax = 0
	local gap = 5 -- 5 px gap between frames
	
	AlternatePowerId 		= nil
	CurrentClass 			= select(3, UnitClass("player"))
	currentSpecialization 	= GetSpecialization()
	
	for k,v in pairs(E.ClassPowers) do
		if k == CurrentClass then
			-- Force override to just show druid combo points when in feral form
			if k == 11 then
				AlternatePowerId = 4
				AlternatePowerMax = GetAltPowerMax(AlternatePowerId)
				currentVisibilityCondition = "[form:2] 1;0"
				
				break
			end
			if v[currentSpecialization] then
				if E:tableContainsValue(AlternatePowers, v[currentSpecialization]) or
					(CurrentClass == 7 and (currentSpecialization == 1 or currentSpecialization == 2)) or
					(CurrentClass == 5 and (currentSpecialization == 3)) then
					AlternatePowerId = v[currentSpecialization]
					AlternatePowerMax = GetAltPowerMax(AlternatePowerId)
					currentVisibilityCondition = "1"
					
					break -- We got what we wanted
				end
			end
		end
	end
	
	-- If no active alternate power was found
	if not AlternatePowerId then
		if UF.LastAlternatePowerFrame then
			UF.LastAlternatePowerFrame:Hide()
		end
		return
	else
		if E.Frames["AlternatePower".. AlternatePowerId .. "-" .. AlternatePowerMax .. "-" .. "Cluster"] then
			UF.LastAlternatePowerFrame = E.Frames["AlternatePower".. AlternatePowerId .. "-" .. AlternatePowerMax .. "-" .. "Cluster"]
			UF.LastAlternatePowerFrame:Show()
		end
	end
	
	if not E.Frames["AlternatePowerMainFrame"] then
		AlternatePowerMainFrame = CreateFrame("Frame", "AlternatePowerMainFrame", E.Parent, "SecureHandlerStateTemplate")
		AlternatePowerMainFrame:SetSize(295, 30)
		AlternatePowerMainFrame:SetPoint("CENTER", E.Parent, "CENTER", 0, 25)
		
			E.Frames["AlternatePowerMainFrame"] = AlternatePowerMainFrame
		
		AlternatePowerMainFrame:SetAttribute("_onstate-visible", [[
			if newstate == 1 then
				self:Show();
			else
				self:Hide();
			end
		]]);
		
		-- /dump _G["AlternatePower4-5-Cluster"]:GetParent():GetName()
		-- /run CUI:CreateMover(_G["AlternatePowerMainFrame"], "AltPower")
		E:CreateMover(AlternatePowerMainFrame, L["alternatePower"])
		
		-- Post-load position, since we create this after the initial loading
		E:LoadMoverPositions(AlternatePowerMainFrame)
	else
		AlternatePowerMainFrame = E.Frames["AlternatePowerMainFrame"]
	end
	
	if not InCombatLockdown() then
		RegisterStateDriver(AlternatePowerMainFrame, "visible", currentVisibilityCondition)
	end
	
	-- OnUpdate runes
	if (select(3, UnitClass("player"))) == 6 then
		AlternatePowerMainFrame:SetScript("OnUpdate", function(self, elapsed) UF:UpdateAlternatePower() end)
	end
	
	-- Create just when it doesn't already exist!
	-- Also, this are the SEPARATED frames!
	if not E.Frames["AlternatePower".. AlternatePowerId .. "-" .. AlternatePowerMax .. "-" .. "1"] and E.PowerTypesDisplayType[AlternatePowerId] then
		--E:NewFrame(type, name, strata, sizeX, sizeY, point, parent, enablemouse, enablemousewheel, enablekeyboard, template)
		
		currentAltPowerCluster = E:NewFrame("Frame", "AlternatePower".. AlternatePowerId .. "-" .. AlternatePowerMax .. "-" .. "Cluster","LOW",295,100)
		UF.LastAlternatePowerFrame = currentAltPowerCluster
		
		currentAltPowerCluster:SetParent(AlternatePowerMainFrame)
		currentAltPowerCluster:ClearAllPoints()
		currentAltPowerCluster:SetPoint("CENTER", AlternatePowerMainFrame, "CENTER", 0, 0)
		
		for i=1,AlternatePowerMax do
			barName = "AlternatePower".. AlternatePowerId .. "-" .. AlternatePowerMax .. "-" .. i
			
			UF:CreateBar(barName, "MEDIUM", ((295 / AlternatePowerMax) - gap), 10, {"CENTER", currentAltPowerCluster, "CENTER"}, currentAltPowerCluster, 1, 0, 0)
			
			altPower = E.Frames[barName]
			altPowerOverlay = E.Frames[barName .. "Overlay"]
			
			altPowerOverlay:SetMinMaxValues(0,UF.ALTERNATE_MAX_VALUE)
			altPowerOverlay:SetValue(0)
			
			E.LibSmooth:SmoothBar(altPowerOverlay)
			
			--E:PushFrame(altPower, 0, 40)
			altPower:SetPoint("LEFT", currentAltPowerCluster, "LEFT", (((295 / AlternatePowerMax)*(i-1)) + gap / AlternatePowerMax), 0)
		end
	end
	-- Create just when it doesn't already exist!
	-- Also, those are the NON-SEPARATED frames!
	if not E.Frames["AlternatePower".. AlternatePowerId] and not E.PowerTypesDisplayType[AlternatePowerId] then
		--E:NewFrame(type, name, strata, sizeX, sizeY, point, parent, enablemouse, enablemousewheel, enablekeyboard, template)
		
		currentAltPowerCluster = E:NewFrame("Frame", "AlternatePower".. AlternatePowerId .. "-" .. "Cluster","LOW",295,100)
		UF.LastAlternatePowerFrame = currentAltPowerCluster
		
		currentAltPowerCluster:SetPoint("CENTER", AlternatePowerMainFrame, "CENTER", 0, 0)
		
			barName = "AlternatePower".. AlternatePowerId .. "-"
			
			UF:CreateBar(barName, "MEDIUM", 295 - gap, 10, {"CENTER", currentAltPowerCluster, "CENTER"}, currentAltPowerCluster, 1, 0, 0)
			
			altPower = E.Frames[barName]
			altPowerOverlay = E.Frames[barName .. "Overlay"]
			
			altPowerOverlay:SetMinMaxValues(0,AlternatePowerMax)
			altPowerOverlay:SetValue(0)
			
			E.LibSmooth:SmoothBar(altPowerOverlay)
			
			--E:PushFrame(altPower, 0, 40)
			altPower:SetPoint("LEFT", currentAltPowerCluster, "LEFT", ((295 / AlternatePowerMax) + gap / AlternatePowerMax) + 1, 0)
	end
	
	
	-- Since the frames were already created at this point, we can simply update it
	if AlternatePowerId then UF.AlternatePowerUpdate = true; UF:UpdateAlternatePower() end
end

function UF:UpdateAlternatePower()
	if not AlternatePowerId or (not E.Frames["AlternatePower".. AlternatePowerId .. "-" .. AlternatePowerMax .. "-" .. "Cluster"] and not E.Frames["AlternatePower".. AlternatePowerId .. "-" .. "Cluster"]) or not UF.AlternatePowerUpdate then return end
	local altPowerOverlay, altPowerValue, UpdateTime, updateValueTo, currentRuneCooldownStart, currentRuneCooldownDuration, currentRuneReady
	
	-- Get alt power value
	altPowerValue 	= GetAltPowerValue(AlternatePowerId)
	--if AlternatePowerId == 5 or altPowerValue == UF.LAST_ALT_POWER_VALUE then return end
	-- Iterate if the power is based on a separated display
	if E.PowerTypesDisplayType[AlternatePowerId] then
		for i=1,AlternatePowerMax do
			if not E.Frames["AlternatePower".. AlternatePowerId .. "-" .. AlternatePowerMax .. "-" .. i .."Overlay"] then return end
			
				if not AlternatePowerId then return end
				
				-- DEATHKNIGHT RUNES
				if AlternatePowerId == 5 then
					
					currentRuneCooldownStart, currentRuneCooldownDuration, currentRuneReady = GetRuneCooldown(i)
					
					-- Calculate the cooldown progress
					if not currentRuneReady then
						updateValueTo = (((currentRuneCooldownStart+currentRuneCooldownDuration-GetTime()) / currentRuneCooldownDuration) * UF.ALTERNATE_MAX_VALUE)
						if updateValueTo < 0 then
							updateValueTo = updateValueTo * -1
						end
					else
						updateValueTo = UF.ALTERNATE_MAX_VALUE
					end
					
				-- WARLOCK SHARDS
				elseif AlternatePowerId == 7 then
					SoulShards = WarlockPowerBar_UnitPower("player")
					
					local currentShard = i
					local currentShardValue = SoulShards - currentShard
					
					if currentShard <= SoulShards then
						updateValueTo = UF.ALTERNATE_MAX_VALUE
					else
						if currentShard - SoulShards > 0 and currentShard - SoulShards < 1 then
							updateValueTo = ((1-(currentShard - SoulShards)) * UF.ALTERNATE_MAX_VALUE)
						else
							updateValueTo = 0
						end
					end
				-- MONK STAGGER
				elseif AlternatePowerId == 30 then
					updateValueTo = (GetAltPowerMax(AlternatePowerId) / GetAltPowerValue(AlternatePowerId)) * UF.ALTERNATE_MAX_VALUE
				
				-- DEFAULT BEHAVIOUR
				else
				
					updateValueTo = UF.ALTERNATE_MAX_VALUE
				end
			
			-- Get current overlay frame
			altPowerOverlay	= E.Frames["AlternatePower".. AlternatePowerId .. "-" .. AlternatePowerMax .. "-" .. i .."Overlay"]
			if not altPowerOverlay then return end
			
			if updateValueTo == UF.ALTERNATE_MAX_VALUE then 
				altPowerOverlay:GetStatusBarTexture():SetVertexColor(E.ClassColors[CurrentClass][1],E.ClassColors[CurrentClass][2],E.ClassColors[CurrentClass][3],1)
			else
				-- Perform color update to be sure
				altPowerOverlay:GetStatusBarTexture():SetVertexColor(E.PowerColors[5][1],E.PowerColors[5][2],E.PowerColors[5][3],E.PowerColors[5][4])
			end
			
			
				-- Use new power value since it is VERY likely to have already changed!
				if (i <= UnitPower("player", AlternatePowerId)) or AlternatePowerId == 5 or AlternatePowerId == 7 or AlternatePowerId == 30 then
					-- If i is less than altPowerValue then we expect the frames to be filled
					if updateValueTo < 0 then updateValueTo = updateValueTo * -1 end
					altPowerOverlay:SetValue(updateValueTo)
				else
					-- If i is greater than altPowerValue then we have reached the empty frames
					altPowerOverlay:SetValue(0)
				end
			
		end
	else
		-- Get current overlay frame
		altPowerOverlay 	= E.Frames["AlternatePower".. AlternatePowerId .. "-" .. "Overlay"]
		-- Perform color update to be sure
		altPowerOverlay:GetStatusBarTexture():SetVertexColor(E.PowerColors[AlternatePowerId][1],E.PowerColors[AlternatePowerId][2],E.PowerColors[AlternatePowerId][3],E.PowerColors[AlternatePowerId][4])
		altPowerOverlay:SetValue(GetAltPowerValue(AlternatePowerId))
	end
	
	UF.LAST_ALT_POWER_VALUE = altPowerValue
end

function UF:ToggleMovers(s)
	for k,v in pairs(UF.Frames) do
		
	end
end

-------------------------------------------------------------------------------------------------------------------------------------
-- Refactor prototype
-------------------------------------------------------------------------------------------------------------------------------------

	----------------------------------------------------------
	-- Profile handler
	----------------------------------------------------------
	local ProfileTarget
	local function ApplyUFProfile(self)
		ProfileTarget = CO.db.profile.unitframe.units[self.ProfileUnit]
		
		
		-- F.Background 	= UF:CreateBackground(F)
		-- F.Health 		= UF:CreateHealth(F)
		-- F.Portrait 		= UF:CreatePortrait(F.Health)
		-- F.Border 		= UF:CreateBorder(F.Health) F.Border:SetFrameLevel(5) -- Attaching this to the portrait will inherit the alpha
		-- F.Highlight 	= UF:CreateHighlight(F.Overlay)
		-- F.Role 			= UF:CreateRole(F.Overlay); F.Role:SetFrameLevel(10) -- Upper the framelevel so the texture will always be displayed first
		-- F.Leader		= UF:CreatePartyLeader(F.Overlay); F.Leader:SetFrameLevel(10)
		-- F.TargetIcon	= UF:CreateTargetIcon(F.Overlay); F.TargetIcon:SetFrameLevel(10)
		-- F.PowerBackground = UF:CreateBackground(F.P)
		-- F.Power = UF:CreatePower(F.P)
		-- F.PowerBorder = UF:CreateBorder(F.Power)
		
		-- /dump ElvUF_Player.Health:Hide()
		-- /dump ElvUF_Player.Portrait3D:GetPoint(ElvUF_Player.Portrait3D:GetNumPoints())
		
		-- Health
			self:SetSize(ProfileTarget.healthWidth, ProfileTarget.healthHeight)
			self.Health:SetReverseFill(ProfileTarget.health.barInverseFill)
			self.Health:SetOrientation(ProfileTarget.health.barOrientation)
		-- Power
			self.P:SetSize(ProfileTarget.power.barWidth, ProfileTarget.power.barHeight)
			self.Power:SetReverseFill(ProfileTarget.power.barInverseFill)
			self.Power:SetOrientation(ProfileTarget.power.barOrientation)
			
			self.P:ClearAllPoints()
			self.P:SetPoint(ProfileTarget.power.barPosition)
			E:MoveFrame(self.P, ProfileTarget.power.barXOffset, ProfileTarget.power.barYOffset)
			
		-- Portrait
			self.Portrait:SetAlpha(ProfileTarget.portrait.alpha)
		
		-- Fonts
			self.Fonts.Health:SetTextColor(ProfileTarget.health.fontColor[1], ProfileTarget.health.fontColor[2], ProfileTarget.health.fontColor[3], ProfileTarget.health.fontColor[4])
			self.Fonts.Level:SetTextColor(ProfileTarget.level.fontColor[1], ProfileTarget.level.fontColor[2], ProfileTarget.level.fontColor[3], ProfileTarget.level.fontColor[4])
		
		-- Power Border and Background
			self.PowerBackground:SetColorTexture(ProfileTarget.power.barBackgroundColor[1], ProfileTarget.power.barBackgroundColor[2], ProfileTarget.power.barBackgroundColor[3], ProfileTarget.power.barBackgroundColor[4])
			E:SetFrameBorder(self.PowerBorder, ProfileTarget.power.barBorderSize, ProfileTarget.power.barBorderColor[1], ProfileTarget.power.barBorderColor[2], ProfileTarget.power.barBorderColor[3], ProfileTarget.power.barBorderColor[4])
		
		-- Health Border and Background
			self.Background:SetColorTexture(ProfileTarget.health.barBackgroundColor[1], ProfileTarget.health.barBackgroundColor[2], ProfileTarget.health.barBackgroundColor[3], ProfileTarget.health.barBackgroundColor[4])
			E:SetFrameBorder(self.Border, ProfileTarget.health.barBorderSize, ProfileTarget.health.barBorderColor[1], ProfileTarget.health.barBorderColor[2], ProfileTarget.health.barBorderColor[3], ProfileTarget.health.barBorderColor[4])
		
		-- Update Mover
			E:UpdateMoverDimensions(self)
	end
	
	function UF:LoadProfile()
		for _, frame in pairs(UF.Frames) do
			frame.Update()
		end
	end
	
	function UF:GetUFMover(type)
		if type == "raid" or type == "raid40" or type == "party" or type == "boss" or type == "arena" then
			return E:GetMover(self:GetHolder(type))
		else
			return E:GetMover(self.Frames[type])
		end
	end

	----------------------------------------------------------
	-- Update handlers
	----------------------------------------------------------
		
		local function Bar_UpdateHealth(B)
			if not UnitExists(B.Unit) then return end
				B:SetMinMaxValues(0, UnitHealthMax(B.Unit))
				B:SetValue(UnitHealth(B.Unit))
		end

		local function Bar_UpdatePower(B)
			if not UnitExists(B.Unit) then return end
				B:SetMinMaxValues(0, UnitPowerMax(B.Unit))
				B:SetValue(UnitPower(B.Unit))
		end

		local function Portrait_Update(P)
			if not UnitExists(P.Unit) then return end
			
				local rawUnit = E:removeDigits(P.Unit)
				if CO.db.profile.unitframe.units[rawUnit].portrait.enable then
					E:SetModelInfo(P, "SetUnit", P.Unit)
				else
					E:SetModelInfo(P, "ClearModel")
				end
		end
		
		local function Role_Update(F)
			F.Role.T:SetTexture(UF.RoleTexture[UnitGroupRolesAssigned(F.Unit)])
			-- F.Role.T:SetTexture(UF.RoleTexture["DAMAGER"])
		end
		
		local function RaidTarget_Update(F)
			local index = GetRaidTargetIndex(F.Unit)
			if(index) then
				if not F.TargetIcon.Icon:GetTexture() then
					F.TargetIcon.Icon:SetTexture([[Interface\TargetingFrame\UI-RaidTargetingIcons]])
				end
				
				SetRaidTargetIconTexture(F.TargetIcon.Icon, index)
				F.TargetIcon.Icon:Show()
			else
				F.TargetIcon.Icon:Hide()
			end
		end
		
		local function RaidLeader_Update(F)
			local isAssist, isLeader
			isLeader = (UnitInParty(F.Unit) or UnitInRaid(F.Unit)) and UnitIsGroupLeader(F.Unit)
			
			if isLeader then
				F.Leader.T:SetTexture([[Interface\GroupFrame\UI-Group-LeaderIcon]])
				
				F.Leader.T:Show()
				return
			else
				isAssist = UnitInRaid(F.Unit) and UnitIsGroupAssistant(F.Unit) and not UnitIsGroupLeader(F.Unit)
				
				if isAssist then
					F.Leader.T:SetTexture([[Interface\GroupFrame\UI-Group-AssistantIcon]])
					
					F.Leader.T:Show()
					return
				end
			end
			
			F.Leader.T:Hide()
		end
		
		local function NameFont_Update(self)
			local RGB = E:GetUnitReactionColor(self.Unit)
			self:SetTextColor(RGB.r, RGB.g, RGB.b, 1)
			
			self:SetText(UnitName(self.Unit) or UnitName("player"))
		end
		
		local function LevelFont_Update(self)
			local Level = UnitLevel(self.Unit)
			
			if CO.db.profile.unitframe.units[E:removeDigits(self.Unit)].level.doNotShowOnMaxLevel == true and Level == E.UNIT_MAXLEVEL then
				self:SetText("")
			else
				if UnitLevel(self.Unit) ~= -1 then
					self:SetText(UnitLevel(self.Unit))
				else
					self:SetText("Boss")
				end
			end
		end
		
		local function Fonts_Update(F)
			for k,v in pairs(F.Fonts) do
				v.Update()
			end
		end
		
		-- This gets called by the OnEvent handler, which basically calls whenever a frame shows up and is missing data
		-- Every other update is performed by the individual modules
		local UFColor
		local function UF_Update(F)
			if not UnitExists(F.Unit) then return end
			
				Bar_UpdateHealth(F.Health)
				Bar_UpdatePower(F.Power)
				Portrait_Update(F.Portrait)
				Fonts_Update(F)
				Role_Update(F)
				RaidTarget_Update(F)
				RaidLeader_Update(F)
				
				-- @TODO: When a player joins a raid/party, we in fact do receive his unitid immediately, but his info like class etc. is delayed.
				-- So we have to somehow ... delay this call. Otherwise it will inherit the colors of the previous player.
				if UnitClass(F.Unit) == nil then return end
				UFColor = E:GetUnitReactionColor(F.Unit)
				-- If max power is 0, hide powerbar
				if select(2, F.Power:GetMinMaxValues()) <= 0 then
					F.P:Hide()
					F.Fonts.Power:SetText("")
				else
					if not F.Power:IsVisible() then F.P:Show() end
					UF:UpdateBarColor(F.Power, E:GetUnitPowerColor(F.Unit))
				end
				
				F.ConnectionStatus = UnitIsConnected(F.Unit)
				if F.ConnectionStatus == true then
					F:SetAlpha(1)
					UF:UpdateBarColor(F.Health, {UFColor.r, UFColor.g, UFColor.b})
				else
					F:SetAlpha(0.3)
					UF:UpdateBarColor(F.Health, {0.5, 0.5, 0.5})
				end
				
				-- if F.ConnectionStatus == true and UnitIsConnected(F.Unit) == false then
					-- F:SetAlpha(0.3)
					-- UF:UpdateBarColor(F.Health, {0.5, 0.5, 0.5})
				-- elseif not F.ConnectionStatus and UnitIsConnected(F.Unit) == true then
					-- F:SetAlpha(1)
					
					-- F.color = E:GetUnitReactionColor(F.Unit)
					-- UF:UpdateBarColor(F.Health, {UFColor.r, UFColor.g, UFColor.b})
					
					-- F.Health:GetStatusBarTexture():SetDesaturated(false)
				-- end
		end
		
		function UF:UpdateAllUF()
			for k, v in pairs(UF.Frames) do
				UF_Update(v)
			end
		end

	----------------------------------------------------------
	-- OnEvent handler
	----------------------------------------------------------
	
		local function UnitFrame_OnEvent(self, event, ...)
			UF_Update(self)
		end

	----------------------------------------------------------
	-- Methods to create UF modules
	----------------------------------------------------------
		function UF:CreateUFBar(F)
			local B = CreateFrame("Statusbar", nil)
			B:SetSize(F:GetWidth(), 15)
			B:SetAllPoints(F)
			B:SetParent(F)
			
			B:SetMinMaxValues(0, 100)
			B:SetValue(100)
			B:SetStatusBarTexture(CO.Media:Fetch("statusbar", CO.db.profile.unitframe.units["all"]['barTexture']))
			if UnitExists(F.Unit) then UF:UpdateBarColor(B, E:GetUnitPowerColor(F.Unit)) end
			
			E.LibSmooth:SmoothBar(B)
			
			E:RegisterStatusBar(B)
			
			return B
		end
		
		function UF:CreateHealth(F)
			local H = UF:CreateUFBar(F)
			
			H.Unit = F.Unit
			H.Type = "Health"
			
			UF:RegisterBarEvents(H)
			UF:InitBarEvents(H)
			
			H:SetScript("OnShow", function(self)
				Bar_UpdateHealth(self)
				
				if UnitExists(self.Unit) and UnitIsConnected(self.Unit) and E:GetUnitClassColor(self.Unit) then UF:UpdateBarColor(self, E:GetUnitClassColor(self.Unit)) end
			end)
			
			return H
		end
		
		function UF:CreatePower(F)
			local P = UF:CreateUFBar(F)
			
			P.Unit = F.Unit
			P.Type = "Power"
			
			UF:RegisterBarEvents(P)
			UF:InitBarEvents(P)
			
			P:SetScript("OnShow", function(self)
				Bar_UpdatePower(self)
				
				if UnitExists(self.Unit) then UF:UpdateBarColor(self, E:GetUnitPowerColor(self.Unit)) end
			end)
			
			return P
		end

		function UF:CreateFonts(F)
			
			local Fonts, FontName
			Fonts = {"Health", "Power", "Level", "Name", "Index"}
			
			for k,v in pairs(Fonts) do
				-- If we don't define this here, there are some out-of-this-world errors popping up. Why?
				-- We cannot even wipe it after each iteration. That thing is cursed, trust me.
				local Font = {}
				FontName = format("%s%sFont", F.Unit, v)
				
				--Font = E:NewFont(FontName, "OVERLAY", F.Overlay)
				Font = F.Overlay:CreateFontString(nil)
					E:InitializeFontFrame(Font, "OVERLAY", "FRIZQT__.TTF", 12, {0.933, 0.886, 0.125}, 1, {0,0}, "", 0, 0, F.Overlay, "CENTER", {1,1})
					Font.Unit = F.Unit
				Font.E = CreateFrame("Frame")
				
				F.Fonts[v] = Font
				
				if v == "Health" then
					Font.E:RegisterUnitEvent("UNIT_HEALTH", F.Unit)
					Font.E:RegisterUnitEvent("UNIT_HEALTH_FREQUENT", F.Unit)
					
					Font.Update = function(self, event)
						if not UnitIsDead(F.Unit) then
							if not UnitIsConnected(F.Unit) then
								Font:SetText("Offline")
							else
								Font:SetText(format("%s\n%s", E:readableNumber(UnitHealth(F.Unit), 2), E:Round((UnitHealth(F.Unit) / UnitHealthMax(F.Unit)) * 100, 2) .. "%"))
							end
						else
							Font:SetText(DEAD)
						end
					end
				end
				if v == "Power" then
					if E:IsLegionClient() then
						Font.E:RegisterUnitEvent("UNIT_POWER", F.Unit)
					else
						Font.E:RegisterUnitEvent("UNIT_POWER_UPDATE", F.Unit)
					end
					Font.E:RegisterUnitEvent("UNIT_POWER_FREQUENT", F.Unit)
					Font.E:RegisterUnitEvent("UNIT_DISPLAYPOWER", F.Unit)
					
					Font.Update = function(self, event)
						if not event or (event and event == "UNIT_DISPLAYPOWER") then
							local RGB = E:GetUnitPowerColor(F.Unit)
							Font:SetTextColor(RGB[1], RGB[2], RGB[3], 1)
						end
						Font:SetText(UnitPower(F.Unit))
					end
				end
				if v == "Level" then
					Font.E:RegisterUnitEvent("UNIT_LEVEL", F.Unit)
					
					Font.Update = function(self, event)
						LevelFont_Update(Font)
					end
				end
				if v == "Name" then
					Font.E:RegisterUnitEvent("UNIT_NAME_UPDATE", F.Unit)
					
					Font.Update = function()
						NameFont_Update(Font)
					end
				end
				if v == "Index" then
					Font:SetText(select(2, E:ExtractDigits(F.Unit)))
					Font:SetPoint("TOP", F, "TOP", 0, -2)
					Font.Update = function() end
					
					Font:Hide()
				end
				
				Font.E:SetScript("OnEvent", Font.Update)
				Font.Update() -- Initial update
			end
		end
			
		function UF:CreatePortrait(F, Unit)
			local P = CreateFrame("PlayerModel", nil)
			
			P:SetParent(F)
			P:SetAllPoints(F)
			
			P.Unit = Unit
			
			UF:InitPortrait(P)
			
			return P
		end

		function UF:CreateBackground(F)
			local B = F:CreateTexture(nil, "BACKGROUND")
			B:SetAllPoints(F)
			
			B:SetColorTexture(0.1,0.1,0.1, 1)
			
			return B
		end
		
		function UF:CreateHighlight(F)
			local H = F:CreateTexture("BarHighlight", "HIGHLIGHT")
			H:SetAllPoints(true)
			
			H:SetColorTexture(1,1,0, 0.1)
			H:SetBlendMode("ADD")
			
			return H
		end
		
		function UF:CreateTargetIcon(F)
			local I = CreateFrame("Frame", nil)
			I:SetPoint("TOP", F, "TOP", 0,5)
			I:SetParent(F)
			I:SetSize(16,16)
			
			I.Icon = F:CreateTexture(nil, "OVERLAY")
			I.Icon:SetAllPoints(I)
			
			return I
		end

		function UF:CreateBorder(F)
			local B = CreateFrame("Frame", nil)
			B:SetAllPoints(F)
			B:SetParent(F)
			
			B:SetBackdrop({ edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = UF.UNITFRAMES_BORDER_SIZE, tile = true,})
			B:SetBackdropBorderColor(0, 0, 0, 1)
			
			return B
		end
		
		function UF:CreateRole(F)
			local FT = CreateFrame("Frame", nil)
			FT:SetPoint("TOPRIGHT", F, "TOPRIGHT", 8,8)
			FT:SetParent(F)
			FT:SetSize(16,16)
			
			FT.T = F:CreateTexture(nil, "OVERLAY")
			FT.T:SetAllPoints(FT)
			
			return FT
		end
		
		function UF:CreatePartyLeader(F)
			local PL = CreateFrame("Frame", nil)
			PL:SetPoint("TOPLEFT", F, "TOPLEFT", 0,12)
			PL:SetParent(F)
			PL:SetSize(16,16)
			
			PL.T = F:CreateTexture(nil, "OVERLAY")
			PL.T:SetAllPoints(PL)
			
			return PL
		end
	
	----------------------------------------------------------
	-- Method to create a UnitFrame
	----------------------------------------------------------
	UF.Frames = {}
	function UF:CreateUF(Unit, Index)
		local RawUnit, UnitNum = E:ExtractDigits(Unit)
		local Config = self.db.units[RawUnit]
		local FrameName = format("CUI_%s", Unit)
		local F = CreateFrame("Frame", FrameName, E.Parent)
		F.Fonts = {}
		F.P = CreateFrame("Frame", nil, F)
		
		-- Add Unitframe to register
		if Index then
			UF.Frames[RawUnit .. Index .. UnitNum] = F
				F.ProfileUnit = RawUnit .. Index
		else
			UF.Frames[Unit] = F
				F.ProfileUnit = RawUnit
		end
		
		-- Initial positioning and size
			F:SetPoint("CENTER", E.Parent, "CENTER")
			
			F:SetSize(Config.healthWidth, Config.healthHeight)
			F.P:SetPoint("BOTTOM", F, "BOTTOM", 0, -13)
			F.P:SetSize(Config.power.barWidth, Config.power.barHeight)
		
		-- UF Overlay to interact with, since not even the highlight works on just the Base frame
			F.Overlay = CreateFrame("Button", nil, F, "SecureUnitButtonTemplate")
			F.Overlay:SetAllPoints(F)
			F.Overlay:SetParent(F)
			F.Overlay:SetFrameLevel(10)
			F.Overlay:EnableMouse(true)
		
		-- Set unit
			F.RawUnit = RawUnit
			F.Unit = Unit
			F.Overlay.Unit = Unit
			F.P.Unit = Unit
			
		-- Init connection handler
			F.ConnectionStatus = true
		
		-- Add UF modules
			F.Background 	= UF:CreateBackground(F)
			F.Health 		= UF:CreateHealth(F)
			F.Portrait 		= UF:CreatePortrait(F.Health, Unit)
			F.Border 		= UF:CreateBorder(F.Health) F.Border:SetFrameLevel(5) -- Attaching this to the portrait will inherit the alpha
			F.Highlight 	= UF:CreateHighlight(F.Overlay)
			F.Role 			= UF:CreateRole(F.Overlay); F.Role:SetFrameLevel(10) -- Upper the framelevel so the texture will always be displayed first
			F.Leader		= UF:CreatePartyLeader(F.Overlay); F.Leader:SetFrameLevel(10)
			F.TargetIcon	= UF:CreateTargetIcon(F.Overlay); F.TargetIcon:SetFrameLevel(10)
			F.PowerBackground = UF:CreateBackground(F.P)
			F.Power = UF:CreatePower(F.P)
			F.PowerBorder = UF:CreateBorder(F.Power)
			
			F.P:SetFrameLevel(6)
			
			-- F.Portrait.Overlay = CreateFrame("Frame", nil, F.Health)
			-- F.Portrait.Overlay:SetFrameLevel(F.Health:GetFrameLevel() + 10)
			-- F.Background:SetParent(F.Portrait.Overlay)
			
			
			UF:CreateFonts(F)
		
		-- Set Required attributes
			F:SetAttribute("unit", Unit)
			F.Overlay:SetAttribute("unit", Unit)
			
		-- Set hover script
			UF:SetHoverScript(F.Overlay, true)
		
		-- Set interaction attributes
			F.Overlay:RegisterForClicks("AnyUp")
			F.Overlay:SetAttribute("type1", "target")
			F.Overlay:SetAttribute("*type2", "togglemenu")
			F.Overlay:SetAttribute("shift-type1", "focus")
				if Unit == "focus" then F.Overlay:SetAttribute("shift-type1", "macro"); F.Overlay:SetAttribute("macrotext", "/focus none"); end
			
			-- Register Frame to the engine so it will take care of its visibility
				RegisterUnitWatch(F)
			
			-- Register a bunch of events to handle target changing etc.
			-- Since we register just the neccessary event(s) to each frame, we do not have to care
			-- about which event fired in the OnEvent handler.
			-- Performance, wohoo!
				
				F.Role:RegisterEvent("ROLE_CHANGED_INFORM")
				F.Role:RegisterEvent("GROUP_ROSTER_UPDATE")
				F.Role:RegisterEvent("RAID_ROSTER_UPDATE")
				F.Role:SetScript("OnEvent", function() Role_Update(F) end)
				
				F.TargetIcon:RegisterEvent("RAID_TARGET_UPDATE")
				F.TargetIcon:SetScript("OnEvent", function() RaidTarget_Update(F) end)
				
				F.Leader:RegisterEvent("PARTY_LEADER_CHANGED")
				F.Leader:RegisterEvent("GROUP_ROSTER_UPDATE")
				F.Leader:SetScript("OnEvent", function() RaidLeader_Update(F) end)
			
					-- When the unit turns hostile/friendly or whatever
					F:RegisterUnitEvent("UNIT_FACTION", Unit)
				if Unit == "target" then
					F:RegisterEvent("PLAYER_TARGET_CHANGED")
				elseif Unit == "focus" then
					F:RegisterEvent("PLAYER_FOCUS_CHANGED")
				elseif Unit == "pet" then
					F:RegisterEvent("UNIT_PET")
				elseif RawUnit == "party" or RawUnit == "raid" then
					F:RegisterEvent("GROUP_ROSTER_UPDATE")
					F:RegisterEvent("UPDATE_INSTANCE_INFO")
				elseif RawUnit == "boss" then
					F:RegisterEvent("INSTANCE_ENCOUNTER_ENGAGE_UNIT")
				end
				if Unit ~= "player" then
					F:RegisterUnitEvent("UNIT_TARGET", Unit)
					F:RegisterUnitEvent("UNIT_CONNECTION", Unit)
					F:RegisterUnitEvent("UNIT_NAME_UPDATE", Unit) -- Probably the only reliable way to update when every dataset is ready
				end
				
			-- When the frame event fires, update modules
				F:SetScript("OnEvent", UnitFrame_OnEvent)
				-- To make the update instantaneous when the frame shows up
					F:SetScript("OnShow", UnitFrame_OnEvent)
		
		-- Initial Update
			UF_Update(F)
		-- Setup profile update
			F.Update = function() ApplyUFProfile(F) end
			
			F:Hide()
		
		if RawUnit ~= "arena" and RawUnit ~= "party" and RawUnit ~= "boss" and RawUnit ~= "raid" then
			E:CreateMover(F, L[Unit .. "Frame"])
		end
		
		return F
	end
	
	UF.Holders = {}
	UF.Holders.SortMethod = {
		["arena"] 	= {"TOPLEFT", 0, 15, "+", "-"},
		["party"] 	= {"TOPLEFT", 0, 15, "+", "-"},
		["raid"] 	= {"TOPRIGHT", 2, 2, "-", "-"},
		["raid40"] 	= {"TOPRIGHT", 2, 2, "-", "-"},
		["boss"] 	= {"TOPLEFT", 0, 15, "+", "-"},
	}
	
	function UF:CreateUFHolder(Type, SX, SY)
		local Holder = CreateFrame("Frame", format("%sHolder", Type), E.Parent, "SecureHandlerStateTemplate")
		Holder:SetSize(SX,SY)
		Holder:SetPoint("CENTER", E.Parent, "CENTER")
		Holder.Type = Type
		
		UF.Holders[Type] = Holder
		
		Holder:SetAttribute("_onstate-visible", [[
			if newstate == 1 then
				self:Show();
			else
				self:Hide();
			end
		]]);
		
		E:CreateMover(Holder, L[format("%sFrame", Type)])
		
		return Holder
	end
	
	function UF:GetHolder(Unit)
		return self.Holders[Unit]
	end
	
	function UF:OverrideHolderVisibility(state)
		UF.HolderVisibilityOverride = state
		if state == true then
			for k,v in pairs(self.Holders) do
				if k ~= "SortMethod" then
					RegisterStateDriver(self:GetHolder(k), "visible", "1")
				end
			end
		else
			for k,v in pairs(self.Holders) do
				if k ~= "SortMethod" then
					UF:LoadHolderConfig(k)
				end
			end
			
		end
	end
	
	function UF:LoadHolderConfig(Unit)
		local startSortFrom = ""
		local Holder = self:GetHolder(Unit)
		
		if not Holder then return end
		
			-- We now automate this process, since it's more convenient when configuring
			if CO.db.profile.unitframe.units[Unit].sort.directionVertical == "+" then
				startSortFrom = "BOTTOM"; else startSortFrom = "TOP";
			end
			if CO.db.profile.unitframe.units[Unit].sort.directionHorizontal == "+" then
				startSortFrom = startSortFrom .. "LEFT"; else startSortFrom = startSortFrom .. "RIGHT";
			end
			
		-- Override sort config
		self.Holders.SortMethod[Unit][1] = startSortFrom
		self.Holders.SortMethod[Unit][2] = CO.db.profile.unitframe.units[Unit].sort.gapX
		self.Holders.SortMethod[Unit][3] = CO.db.profile.unitframe.units[Unit].sort.gapY
		self.Holders.SortMethod[Unit][4] = CO.db.profile.unitframe.units[Unit].sort.directionHorizontal
		self.Holders.SortMethod[Unit][5] = CO.db.profile.unitframe.units[Unit].sort.directionVertical
		
		-- Check if the user currently wants the holders to stay visible
		if UF.HolderVisibilityOverride == false then
			RegisterStateDriver(Holder, "visible", CO.db.profile.unitframe.units[Unit].visibilityCondition)
		end
		
		-- Apply changes
		self:SortUFHolderContents(Holder)
	end
	
	function UF:SortUFHolderContents(Holder)
		
		local BeginFrom, XGap, YGap, XGrowth, YGrowth = unpack(self.Holders.SortMethod[Holder.Type])
		
		-- Why tf does this not work?
		if not BeginFrom or not XGap or not YGap or not XGrowth or not YGrowth then return end
		
		local currentColumn, currentRow, contentWidth, contentHeight, currentXOffset, currentYOffset
		local wrapWidth = CO.db.profile.unitframe.units[Holder.Type].targetWrapWidth
		
		local totalWidth, totalHeight = 0, 0
		
		currentColumn = 0
		currentRow = 0
		
		XGap = tonumber(XGap)
		YGap = tonumber(YGap)
		XGrowth = tonumber(XGrowth .. 1)
		YGrowth = tonumber(YGrowth .. 1)
		
		for k,f in pairs(Holder) do
			-- Since we will probably also hit some functions
			if type(f) == "table" then
				--------------------------------------------------------------
					if not contentWidth then -- ... we'll PROBABLY need both
						contentWidth, contentHeight = f:GetWidth(), f:GetHeight()
					end
					
					-- Prevent first frame from being affected by the gap value
						if currentColumn ~= 0 then
							currentXOffset = ((XGrowth * ((contentWidth + XGap))) * (currentColumn + 1))
						else
							currentXOffset = ((XGrowth * ((contentWidth))) * (currentColumn + 1))
						end
					currentYOffset = YGrowth * ((contentHeight + YGap) * (currentRow + 1))
					
					f:ClearAllPoints()
					f:SetPoint(BeginFrom, Holder, BeginFrom)
					-- Push before next calcs are done
						-- Prevent first frame from being affected by the gap value
						if currentColumn ~= 0 then
							E:PushFrame(f, XGrowth * ((contentWidth + XGap) * currentColumn), YGrowth * ((contentHeight + YGap) * currentRow))
						else
							E:PushFrame(f, XGrowth * ((contentWidth) * currentColumn), YGrowth * ((contentHeight + YGap) * currentRow))
						end
					
					if E:makePositive(currentXOffset) > totalWidth then totalWidth = E:makePositive(currentXOffset) end
					if E:makePositive(currentYOffset) > totalHeight then totalHeight = E:makePositive(currentYOffset) end
					
					-- If next push would be too far
					if ((contentWidth + XGap) * currentColumn) + (contentWidth*2) < wrapWidth then
						currentColumn = currentColumn + 1
					else
						currentRow = currentRow + 1
						currentColumn = 0
					end
					
					
				--------------------------------------------------------------
			end
		end
		
		-- Remove uneccessary gap offset
		totalWidth = totalWidth - XGap
		totalHeight = totalHeight - YGap

		Holder:SetSize(totalWidth, totalHeight)
		E:UpdateMoverDimensions(Holder)
	end
	
	function UF:AssignUFHolder(Holder, F)
		F:ClearAllPoints()
		F:SetPoint("CENTER", Holder, "CENTER")
		F:SetParent(Holder)
		
		table.insert(Holder, F)
	end

	-- Requires: Unit, Type in object
	function UF:InitBarEvents(Bar)
		Bar:SetScript("OnEvent", function(self, event, ...)
			if self.Type == "Health" then
				Bar_UpdateHealth(self)
			else
				if event == "UNIT_DISPLAYPOWER" then
					UF:UpdateBarColor(self, E:GetUnitPowerColor(self.Unit))
				else
					Bar_UpdatePower(self)
				end
			end
		end)
	end
	
	function UF:InitPortrait(P)
		P:RegisterUnitEvent("UNIT_PORTRAIT_UPDATE", P.Unit)
		
		E:SetModelInfo(P, "SetRotation", -0.2)
		E:SetModelInfo(P, "SetPortraitZoom", 1)
		E:SetModelInfo(P, "SetCamDistanceScale", 1.4)
		P:SetAlpha(0.35)
		
		P:SetScript("OnEvent", Portrait_Update)
		P:SetScript("OnShow", Portrait_Update)
		
		-- Initial update to make sure we follow the profile
		Portrait_Update(P)
		
		-- We have to use the stored frame for this somehow
		UF:RegisterPortrait(P)
	end

	function UF:RegisterBarEvents(Bar)
		
		Bar:RegisterEvent("PLAYER_ENTERING_WORLD")
		
		if Bar.Unit == "player" or Bar.Unit == "target" then
			if Bar.Type == "Health" then
				Bar:RegisterUnitEvent("UNIT_HEALTH_FREQUENT", Bar.Unit)
			else
				Bar:RegisterUnitEvent("UNIT_POWER_FREQUENT", Bar.Unit)
			end
		else
			if Bar.Type == "Health" then
				Bar:RegisterUnitEvent("UNIT_HEALTH", Bar.Unit)
			else
				if E:IsLegionClient() then
					Bar:RegisterUnitEvent("UNIT_POWER", Bar.Unit)
				else
					Bar:RegisterUnitEvent("UNIT_POWER_UPDATE", Bar.Unit)
				end
			end
		end
		if Bar.Type ~= Health then
			Bar:RegisterUnitEvent("UNIT_DISPLAYPOWER", Bar.Unit)
		end
	end

	----------------------------------------------------------
	-- Register methods - Those exist to automatically react to profile changes
	----------------------------------------------------------
	E.Portraits = {}
	function UF:RegisterPortrait(Portrait)
		-- Expects Portrait.Unit to already be present
		table.insert(E.Portraits, Portrait)
	end

-------------------------------------------------------------------------------------------------------------------------------------
-- Refactor end
-------------------------------------------------------------------------------------------------------------------------------------

function UF:Init()
	CO = E:GetModule("Config")
	TT = E:GetModule("Tooltip")
	self.db = CO.db.profile.unitframe
	
	self:SetupAlternatePower()
	
	-- Experimental
	self:CreateUF("player")
	self:CreateUF("target")
	self:CreateUF("targettarget")
	self:CreateUF("focus")
	self:CreateUF("focustarget")
	self:CreateUF("pet")
	
	local ArenaHolder = self:CreateUFHolder("arena", 200, 400)
	local PartyHolder = self:CreateUFHolder("party", 200, 400)
	local BossHolder = self:CreateUFHolder("boss", 200, 400)
	local RaidHolder = self:CreateUFHolder("raid", 350, 350)
	local RaidFullHolder = self:CreateUFHolder("raid40", 350, 350)
	
	for i=1,5 do
		self:AssignUFHolder(ArenaHolder, self:CreateUF("arena" .. i))
		self:AssignUFHolder(PartyHolder, self:CreateUF("party" .. i))
		self:AssignUFHolder(BossHolder, self:CreateUF("boss" .. i))
	end
	for i=1,20 do
		self:AssignUFHolder(RaidHolder, self:CreateUF("raid" .. i))
	end
	for i=1,40 do
		self:AssignUFHolder(RaidFullHolder, self:CreateUF("raid" .. i, "40"))
	end
	
	self:LoadHolderConfig("arena")
	self:LoadHolderConfig("party")
	self:LoadHolderConfig("boss")
	self:LoadHolderConfig("raid")
	self:LoadHolderConfig("raid40")
	
	UF:LoadProfile()
end

E:AddModule("Unitframes", UF)