local Units = {headerFrames = {}, unitFrames = {}, frameList = {}, unitEvents = {}, remappedUnits = {}, canCure = {}}
Units.childUnits = {["partytarget"] = "party", ["partytargettarget"] = "party", ["partypet"] = "party", ["maintanktarget"] = "maintank", ["mainassisttarget"] = "mainassist", ["bosstarget"] = "boss", ["arenatarget"] = "arena", ["arenatargettarget"] = "arena", ["arenapet"] = "arena", ["battlegroundpet"] = "battleground", ["battlegroundtarget"] = "battleground", ["battlegroundtargettarget"] = "battleground", ["maintanktargettarget"] = "maintank", ["mainassisttargettarget"] = "mainassist", ["bosstargettarget"] = "boss"}
Units.zoneUnits = {["arena"] = "arena", ["arenapet"] = "arena", ["arenatarget"] = "arena", ["arenatargettarget"] = "arena", ["boss"] = {"party", "raid"}, ["bosstarget"] = {"party", "raid"}, ["battleground"] = "pvp", ["battlegroundtarget"] = "pvp", ["battlegroundtargettarget"] = "pvp", ["battlegroundpet"] = "pvp", ["bosstargettarget"] = {"party", "raid"}}
Units.remappedUnits = {["battleground"] = "arena", ["battlegroundpet"] = "arenapet", ["battlegroundtarget"] = "arenatarget", ["battlegroundtargettarget"] = "arenatargettarget"}
Units.headerUnits = {["raid"] = true, ["party"] = true, ["maintank"] = true, ["mainassist"] = true, ["raidpet"] = true, ["partypet"] = true}

local stateMonitor = CreateFrame("Frame", nil, nil, "SecureHandlerBaseTemplate")
stateMonitor.raids = {}
local playerClass = select(2, UnitClass("player"))
local unitFrames, headerFrames, frameList, unitEvents, childUnits, headerUnits, queuedCombat, zoneUnits = Units.unitFrames, Units.headerFrames, Units.frameList, Units.unitEvents, Units.childUnits, Units.headerUnits, {}, Units.zoneUnits
local remappedUnits = Units.remappedUnits
local _G = getfenv(0)

ShadowUF.Units = Units
ShadowUF:RegisterModule(Units, "units")

-- This is the wrapper frame that everything parents to so we can just hide it when we need to deal with pet battles
local petBattleFrame = CreateFrame("Frame", "SUFWrapperFrame", UIParent, "SecureHandlerBaseTemplate")
petBattleFrame:SetFrameStrata("BACKGROUND")
petBattleFrame:SetAllPoints(UIParent)
petBattleFrame:WrapScript(petBattleFrame, "OnAttributeChanged", [[
	if( name ~= "state-petbattle" ) then return end
	if( value == "active" ) then
		self:Hide()
	else
		self:Show()
	end
]])

RegisterStateDriver(petBattleFrame, "petbattle", "[petbattle] active; none")

-- Frame shown, do a full update
local function FullUpdate(self)
	for i=1, #(self.fullUpdates), 2 do
		local handler = self.fullUpdates[i]
		handler[self.fullUpdates[i + 1]](handler, self)
	end
end

-- Re-registers events when unit changes
local function ReregisterUnitEvents(self)
	-- Not an unit event
	if( ShadowUF.fakeUnits[self.unitRealType] or not headerUnits[self.unitType] ) then return end

	for event, list in pairs(self.registeredEvents) do
		if( unitEvents[event] ) then
			local hasHandler
			for handler in pairs(list) do
				hasHandler = true
				break
			end

			if( hasHandler ) then
				self:UnregisterEvent(event)
				self:BlizzRegisterUnitEvent(event, self.unitOwner, self.vehicleUnit)
			end
		end
	end
end

-- Register an event that should always call the frame
local function RegisterNormalEvent(self, event, handler, func, unitOverride)
	-- Make sure the handler/func exists
	if( not handler[func] ) then
		error(string.format("Invalid handler/function passed for %s on event %s, the function %s does not exist.", self:GetName() or tostring(self), tostring(event), tostring(func)), 3)
		return
	end

	-- XXX: replace once 9.0 goes live, and we can cleanly remove events from tags and all modules
	if event == "UNIT_HEALTH_FREQUENT" then
		event = "UNIT_HEALTH"
	end

	if( unitEvents[event] and not ShadowUF.fakeUnits[self.unitRealType] ) then
		self:BlizzRegisterUnitEvent(event, unitOverride or self.unitOwner, self.vehicleUnit)
		if unitOverride then
			self.unitEventOverrides = self.unitEventOverrides or {}
			self.unitEventOverrides[event] = unitOverride
		end
	else
		self:RegisterEvent(event)
	end

	self.registeredEvents[event] = self.registeredEvents[event] or {}

	-- Each handler can only register an event once per a frame.
	if( self.registeredEvents[event][handler] ) then
		return
	end

	self.registeredEvents[event][handler] = func
end

-- Unregister an event
local function UnregisterEvent(self, event, handler)
	if( self.registeredEvents[event] and self.registeredEvents[event][handler] ) then
		self.registeredEvents[event][handler] = nil

		local hasHandler
		for _handler in pairs(self.registeredEvents[event]) do
			hasHandler = true
			break
		end

		if( not hasHandler ) then
			self:UnregisterEvent(event)
		end
	end
end

-- Register an event thats only called if it's for the actual unit
local function RegisterUnitEvent(self, event, handler, func)
	unitEvents[event] = true
	RegisterNormalEvent(self, event, handler, func)
end

-- Register a function to be called in an OnUpdate if it's an invalid unit (targettarget/etc)
local function RegisterUpdateFunc(self, handler, func)
	if( not handler[func] ) then
		error(string.format("Invalid handler/function passed to RegisterUpdateFunc for %s, the function %s does not exist.", self:GetName() or tostring(self), func), 3)
		return
	end

	for i=1, #(self.fullUpdates), 2 do
		local data = self.fullUpdates[i]
		if( data == handler and self.fullUpdates[i + 1] == func ) then
			return
		end
	end

	table.insert(self.fullUpdates, handler)
	table.insert(self.fullUpdates, func)
end

local function UnregisterUpdateFunc(self, handler, func)
	for i=#(self.fullUpdates), 1, -1 do
		if( self.fullUpdates[i] == handler and self.fullUpdates[i + 1] == func ) then
			table.remove(self.fullUpdates, i + 1)
			table.remove(self.fullUpdates, i)
		end
	end
end

-- Used when something is disabled, removes all callbacks etc to it
local function UnregisterAll(self, handler)
	for i=#(self.fullUpdates), 1, -1 do
		if( self.fullUpdates[i] == handler ) then
			table.remove(self.fullUpdates, i + 1)
			table.remove(self.fullUpdates, i)
		end
	end

	for event, list in pairs(self.registeredEvents) do
		if( list[handler] ) then
			list[handler] = nil

			local hasRegister
			for _handler in pairs(list) do
				hasRegister = true
				break
			end

			if( not hasRegister ) then
				self:UnregisterEvent(event)
			end
		end
	end
end

-- Handles setting alphas in a way so combat fader and range checker don't override each other
local function DisableRangeAlpha(self, toggle)
	self.disableRangeAlpha = toggle

	if( not toggle and self.rangeAlpha ) then
		self:SetAlpha(self.rangeAlpha)
	end
end

local function SetRangeAlpha(self, alpha)
	if( not self.disableRangeAlpha ) then
		self:SetAlpha(alpha)
	else
		self.rangeAlpha = alpha
	end
end

local function SetBarColor(self, key, r, g, b)
	self:SetBlockColor(self[key], key, r, g, b)
end

local function SetBlockColor(self, bar, key, r, g, b)
	local bgColor = bar.background.overrideColor or bar.background.backgroundColor
	if( not ShadowUF.db.profile.units[self.unitType][key].invert ) then
		bar:SetStatusBarColor(r, g, b, ShadowUF.db.profile.bars.alpha)
		if( not bgColor ) then
			bar.background:SetVertexColor(r, g, b, ShadowUF.db.profile.bars.backgroundAlpha)
		else
			bar.background:SetVertexColor(bgColor.r, bgColor.g, bgColor.b, ShadowUF.db.profile.bars.backgroundAlpha)
		end
	else
		bar.background:SetVertexColor(r, g, b, ShadowUF.db.profile.bars.alpha)
		if( not bgColor ) then
			bar:SetStatusBarColor(0, 0, 0, 1 - ShadowUF.db.profile.bars.backgroundAlpha)
		else
			bar:SetStatusBarColor(bgColor.r, bgColor.g, bgColor.b, 1 - ShadowUF.db.profile.bars.backgroundAlpha)
		end
	end
end

-- Event handling
local function OnEvent(self, event, unit, ...)
	if( not unitEvents[event] or self.unit == unit or (self.unitEventOverrides and self.unitEventOverrides[event] == unit)) then
		for handler, func in pairs(self.registeredEvents[event]) do
			handler[func](handler, self, event, unit, ...)
		end
	end
end

Units.OnEvent = OnEvent

-- Do a full update OnShow, and stop watching for events when it's not visible
local function OnShowForced(self)
	-- Reset the event handler
	self:SetScript("OnEvent", OnEvent)
	self:FullUpdate()
end

local function OnShow(self)
	-- Reset the event handler
	self:SetScript("OnEvent", OnEvent)
	Units:CheckUnitStatus(self)
end

local function OnHide(self)
	self:SetScript("OnEvent", nil)

	-- If it's a volatile such as target or focus, next time it's shown it has to do an update
	-- OR if the unit is still shown, but it's been hidden because our parent (Basically UIParent)
	-- we want to flag it as having changed so it can be updated
	if( self.isUnitVolatile or self:IsShown() ) then
		self.unitGUID = nil
	end
end

-- Deal with enabling modules inside a zone
local function SetVisibility(self)
	local layoutUpdate
	local instanceType = select(2, IsInInstance()) or "none"
	local playerSpec = GetSpecialization()
	if( instanceType == "scenario" ) then instanceType = "party" end

	-- Selectively disable modules
	for _, module in pairs(ShadowUF.moduleOrder) do
		if( module.OnEnable and module.OnDisable and ShadowUF.db.profile.units[self.unitType][module.moduleKey] ) then
			local key = module.moduleKey
			local enabled = ShadowUF.db.profile.units[self.unitType][key].enabled

			-- These modules have mini-modules, the entire module should be enabled if at least one is enabled, and disabled if all are disabled
			if( key == "auras" or key == "indicators" or key == "highlight" ) then
				enabled = nil
				for _, option in pairs(ShadowUF.db.profile.units[self.unitType][key]) do
					if( type(option) == "table" and option.enabled or option == true ) then
						enabled = true
						break
					end
				end
			end

			-- In an actual zone, check to see if we have an override for the zone
			if( instanceType ~= "none" ) then
				if( ShadowUF.db.profile.visibility[instanceType][self.unitType .. key] == false ) then
					enabled = nil
				elseif( ShadowUF.db.profile.visibility[instanceType][self.unitType .. key] == true ) then
					enabled = true
				end
			end

			-- Force disable modules for people who aren't the appropriate class
			if( module.moduleClass and module.moduleClass ~= playerClass ) then
				enabled = nil
			-- Force disable if they aren't the appropriate spec
			elseif( module.moduleSpec and module.moduleSpec[playerSpec] ~= true ) then
				enabled = nil
			end

			-- Restrict by level
			if( module.moduleLevel and enabled and self.unitType == "player" ) then
				if( UnitLevel("player") < module.moduleLevel ) then
					enabled = nil
				end
			end

			-- Module isn't enabled all the time, only in this zone so we need to force it to be enabled
			if( not self.visibility[key] and enabled ) then
				module:OnEnable(self)
				layoutUpdate = true
			elseif( self.visibility[key] and not enabled ) then
				module:OnDisable(self)
				layoutUpdate = true
			end

			self.visibility[key] = enabled or nil
		end
	end

	-- We had a module update, force a full layout update of this frame
	if( layoutUpdate ) then
		ShadowUF.Layout:Load(self)
	end
end

-- Vehicles do not always return their data right away, a pure OnUpdate check seems to be the most accurate unfortunately
local function checkVehicleData(self, elapsed)
	self.timeElapsed = self.timeElapsed + elapsed
	if( self.timeElapsed >= 0.50 ) then
		self.timeElapsed = 0
		self.dataAttempts = self.dataAttempts + 1

		-- Took too long to get vehicle data, or they are no longer in a vehicle
		if( self.dataAttempts >= 6 or not UnitHasVehicleUI(self.unitOwner) or not UnitHasVehiclePlayerFrameUI(self.unitOwner) ) then
			self.timeElapsed = nil
			self.dataAttempts = nil
			self:SetScript("OnUpdate", nil)

			self.inVehicle = false
			self.unit = self.unitOwner
			self:FullUpdate()

		-- Got data, stop checking and do a full frame update
		elseif( UnitIsConnected(self.unit) or UnitHealthMax(self.unit) > 0 ) then
			self.timeElapsed = nil
			self.dataAttempts = nil
			self:SetScript("OnUpdate", nil)

			self.unitGUID = UnitGUID(self.unit)
			self:FullUpdate()
		end
	end
end

-- Check if a unit entered a vehicle
function Units:CheckVehicleStatus(frame, event, unit)
	if( event and frame.unitOwner ~= unit ) then return end

	-- Not in a vehicle yet, and they entered one that has a UI or they were in a vehicle but the GUID changed (vehicle -> vehicle)
	if( ( not frame.inVehicle or frame.unitGUID ~= UnitGUID(frame.vehicleUnit) ) and UnitHasVehicleUI(frame.unitOwner) and UnitHasVehiclePlayerFrameUI(frame.unitOwner) and not ShadowUF.db.profile.units[frame.unitType].disableVehicle ) then
		frame.inVehicle = true
		frame.unit = frame.vehicleUnit

		if( not UnitIsConnected(frame.unit) or UnitHealthMax(frame.unit) == 0 ) then
			frame.timeElapsed = 0
			frame.dataAttempts = 0
			frame:SetScript("OnUpdate", checkVehicleData)
		else
			frame.unitGUID = UnitGUID(frame.unit)
			frame:FullUpdate()
		end

	-- Was in a vehicle, no longer has a UI
	elseif( frame.inVehicle and ( not UnitHasVehicleUI(frame.unitOwner) or not UnitHasVehiclePlayerFrameUI(frame.unitOwner) or ShadowUF.db.profile.units[frame.unitType].disableVehicle ) ) then
		frame.inVehicle = false
		frame.unit = frame.unitOwner
		frame.unitGUID = UnitGUID(frame.unit)
		frame:FullUpdate()
	end
end

-- Handles checking for GUID changes for doing a full update, this fixes frames sometimes showing the wrong unit when they change
function Units:CheckUnitStatus(frame)
	local guid = frame.unit and UnitGUID(frame.unit)
	if( guid ~= frame.unitGUID ) then
		frame.unitGUID = guid

		if( guid ) then
			frame:FullUpdate()
		end
	end
end


-- The argument from UNIT_PET is the pets owner, so the player summoning a new pet gets "player", party1 summoning a new pet gets "party1" and so on
function Units:CheckPetUnitUpdated(frame, event, unit)
	if( unit == frame.unitRealOwner and UnitExists(frame.unit) ) then
		frame.unitGUID = UnitGUID(frame.unit)
		frame:FullUpdate()
	end
end

-- When raid1, raid2, raid3 are in a group with each other and raid1 or raid2 are in a vehicle and get kicked
-- OnAttributeChanged won't do anything because the frame is already setup, however, the active unit is non-existant
-- while the primary unit is. So if we see they're in a vehicle with this case, we force the full update to get the vehicle change
function Units:CheckGroupedUnitStatus(frame)
	if( frame.inVehicle and not UnitExists(frame.unit) and UnitExists(frame.unitOwner) ) then
		frame.inVehicle = false
		frame.unit = frame.unitOwner
		frame.unitGUID = UnitGUID(frame.unit)
		frame:FullUpdate()
	else
		frame.unitGUID = UnitGUID(frame.unit)
		frame:FullUpdate()
	end
end

-- More fun with sorting, due to sorting magic we have to check if we want to create stuff when the frame changes of partys too
local function createChildUnits(self)
	if( not self.unitID ) then return end

	for child, parentUnit in pairs(childUnits) do
		if( parentUnit == self.unitType and ShadowUF.db.profile.units[child].enabled ) then
			Units:LoadChildUnit(self, child, self.unitID)
		end
	end
end

local OnAttributeChanged
local function updateChildUnits(...)
	if( not ShadowUF.db.profile.locked ) then return end

	for i=1, select("#", ...) do
		local child = select(i, ...)
		if( child.parent and child.unitType ) then
			OnAttributeChanged(child, "unit", SecureButton_GetModifiedUnit(child))
		end
	end
end

local function createFakeUnitUpdateTimer(frame)
	if( not frame.updateTimer ) then
		frame.updateTimer = C_Timer.NewTicker(0.5, function() if( UnitExists(frame.unit) ) then frame:FullUpdate() end end)
	end
end

-- Attribute set, something changed
-- unit = Active unitid
-- unitID = Just the number from the unitid
-- unitType = Unitid minus numbers in it, used for configuration
-- unitRealType = The actual unit type, if party is shown in raid this will be "party" while unitType is still "raid"
-- unitOwner = Always the units owner even when unit changes due to vehicles
-- vehicleUnit = Unit to use when the unitOwner is in a vehicle
OnAttributeChanged = function(self, name, unit)
	if( name ~= "unit" or not unit or unit == self.unitOwner ) then return end

	-- Nullify the previous entry if it had one
	local configUnit = self.unitUnmapped or unit
	if( self.configUnit and unitFrames[self.configUnit] == self ) then unitFrames[self.configUnit] = nil end

	-- Setup identification data
	self.unit = unit
	self.unitID = tonumber(string.match(unit, "([0-9]+)"))
	self.unitRealType = string.gsub(unit, "([0-9]+)", "")
	self.unitType = self.unitUnmapped and string.gsub(self.unitUnmapped, "([0-9]+)", "") or self.unitType or self.unitRealType
	self.unitOwner = unit
	self.vehicleUnit = self.unitOwner == "player" and "vehicle" or self.unitRealType == "party" and "partypet" .. self.unitID or self.unitRealType == "raid" and "raidpet" .. self.unitID or nil
	self.inVehicle = nil

	-- Split everything into two maps, this is the simple parentUnit -> frame map
	-- This is for things like finding a party parent for party target/pet, the main map for doing full updates is
	-- an indexed frame that is updated once and won't have unit conflicts.
	if( self.unitRealType == self.unitType ) then
		unitFrames[configUnit] = self
	end

	frameList[self] = true

	if( self.hasChildren ) then
		updateChildUnits(self:GetChildren())
	end

	-- Create child frames
	createChildUnits(self)

	-- Unit already exists but unitid changed, update the info we got on them
	-- Don't need to recheck the unitType and force a full update, because a raid frame can never become
	-- a party frame, or a player frame and so on
	if( self.unitInitialized ) then
		self:ReregisterUnitEvents()
		self:FullUpdate()
		return
	end

	self.unitInitialized = true

	-- Add to Clique
	if( not self:GetAttribute("isHeaderDriven") ) then
		ClickCastFrames = ClickCastFrames or {}
		ClickCastFrames[self] = true
	end

	-- Handles switching the internal unit variable to that of their vehicle
	if( self.unit == "player" or self.unitRealType == "party" or self.unitRealType == "raid" ) then
		self:RegisterNormalEvent("UNIT_ENTERED_VEHICLE", Units, "CheckVehicleStatus")
		self:RegisterNormalEvent("UNIT_EXITED_VEHICLE", Units, "CheckVehicleStatus")
		self:RegisterUpdateFunc(Units, "CheckVehicleStatus")
	end

	-- Phase change, do a full update on it
	self:RegisterUnitEvent("UNIT_PHASE", self, "FullUpdate")

	-- Pet changed, going from pet -> vehicle for one
	if( self.unit == "pet" or self.unitType == "partypet" ) then
		self.unitRealOwner = self.unit == "pet" and "player" or ShadowUF.partyUnits[self.unitID]
		self:SetAttribute("unitRealOwner", self.unitRealOwner)
		self:RegisterNormalEvent("UNIT_PET", Units, "CheckPetUnitUpdated")

		if( self.unit == "pet" ) then
			self:SetAttribute("disableVehicleSwap", ShadowUF.db.profile.units.player.disableVehicle)
		else
			self:SetAttribute("disableVehicleSwap", ShadowUF.db.profile.units.party.disableVehicle)
		end

		-- Logged out in a vehicle
		if( UnitHasVehicleUI(self.unitRealOwner) and UnitHasVehiclePlayerFrameUI(self.unitRealOwner) ) then
			self:SetAttribute("unitIsVehicle", true)
		end

		-- Hide any pet that became a vehicle, we detect this by the owner being untargetable but they have a pet out
		stateMonitor:WrapScript(self, "OnAttributeChanged", [[
			if( name == "state-vehicleupdated" ) then
				self:SetAttribute("unitIsVehicle", UnitHasVehicleUI(self:GetAttribute("unitRealOwner")) and value == "vehicle" and true or false)
			elseif( name == "disablevehicleswap" or name == "state-unitexists" or name == "unitisvehicle" ) then
				-- Unit does not exist, OR unit is a vehicle and vehicle swap is not disabled, hide frame
				if( not self:GetAttribute("state-unitexists") or ( self:GetAttribute("unitIsVehicle") and not self:GetAttribute("disableVehicleSwap") ) ) then
					self:Hide()
				-- Unit exists, show it
				else
					self:Show()
				end
			end
		]])
		RegisterStateDriver(self, "vehicleupdated", string.format("[target=%s, nohelp, noharm] vehicle; pet", self.unitRealOwner, self.unit))

	-- Automatically do a full update on target change
	elseif( self.unit == "target" ) then
		self.isUnitVolatile = true
		self:RegisterNormalEvent("PLAYER_TARGET_CHANGED", Units, "CheckUnitStatus")
		self:RegisterUnitEvent("UNIT_TARGETABLE_CHANGED", self, "FullUpdate")

	-- Automatically do a full update on focus change
	elseif( self.unit == "focus" ) then
		self.isUnitVolatile = true
		self:RegisterNormalEvent("PLAYER_FOCUS_CHANGED", Units, "CheckUnitStatus")
		self:RegisterUnitEvent("UNIT_TARGETABLE_CHANGED", self, "FullUpdate")

	elseif( self.unit == "player" ) then
		-- this should not get called in combat, but just in case make sure we are not actually in combat
		if not InCombatLockdown() then
			self:SetAttribute("toggleForVehicle", true)
		end

		-- Force a full update when the player is alive to prevent freezes when releasing in a zone that forces a ressurect (naxx/tk/etc)
		self:RegisterNormalEvent("PLAYER_ALIVE", self, "FullUpdate")

		-- full update when the player targetable changes, ie. during cutscenes or transports
		self:RegisterUnitEvent("UNIT_TARGETABLE_CHANGED", self, "FullUpdate")

	-- Update boss
	elseif( self.unitType == "boss" ) then
		self:RegisterNormalEvent("INSTANCE_ENCOUNTER_ENGAGE_UNIT", self, "FullUpdate")
		self:RegisterUnitEvent("UNIT_TARGETABLE_CHANGED", self, "FullUpdate")
		self:RegisterUnitEvent("UNIT_NAME_UPDATE", Units, "CheckUnitStatus")

	-- Update arena
	elseif( self.unitType == "arena" ) then
		self:RegisterUnitEvent("UNIT_NAME_UPDATE", self, "FullUpdate")
		self:RegisterUnitEvent("UNIT_CONNECTION", self, "FullUpdate")

	-- Update battleground
	elseif( self.unitType == "battleground" ) then
		self:RegisterUnitEvent("UNIT_NAME_UPDATE", Units, "CheckUnitStatus")

	-- Check for a unit guid to do a full update
	elseif( self.unitRealType == "raid" ) then
		self:RegisterNormalEvent("GROUP_ROSTER_UPDATE", Units, "CheckGroupedUnitStatus")
		self:RegisterUnitEvent("UNIT_NAME_UPDATE", Units, "CheckUnitStatus")
		self:RegisterUnitEvent("UNIT_CONNECTION", self, "FullUpdate")

	-- Party members need to watch for changes
	elseif( self.unitRealType == "party" ) then
		self:RegisterNormalEvent("GROUP_ROSTER_UPDATE", Units, "CheckGroupedUnitStatus")
		self:RegisterNormalEvent("PARTY_MEMBER_ENABLE", Units, "CheckGroupedUnitStatus")
		self:RegisterNormalEvent("PARTY_MEMBER_DISABLE", Units, "CheckGroupedUnitStatus")
		self:RegisterUnitEvent("UNIT_NAME_UPDATE", Units, "CheckUnitStatus")
		self:RegisterUnitEvent("UNIT_OTHER_PARTY_CHANGED", self, "FullUpdate")
		self:RegisterUnitEvent("UNIT_CONNECTION", self, "FullUpdate")

	-- *target units are not real units, thus they do not receive events and must be polled for data
	elseif( ShadowUF.fakeUnits[self.unitRealType] ) then
		createFakeUnitUpdateTimer(self)

		-- Speeds up updating units when their owner changes target, if party1 changes target then party1target is force updated, if target changes target
		-- then targettarget and targettargettarget are also force updated
		if( self.unitRealType == "partytarget" ) then
			self.unitRealOwner = ShadowUF.partyUnits[self.unitID]
		elseif( self.unitRealType == "partytargettarget" ) then
			self.unitRealOwner = ShadowUF.partyUnits[self.unitID] .. "target"
		elseif( self.unitRealType == "raid" ) then
			self.unitRealOwner = ShadowUF.raidUnits[self.unitID]
		elseif( self.unitRealType == "arenatarget" ) then
			self.unitRealOwner = ShadowUF.arenaUnits[self.unitID]
		elseif( self.unitRealType == "arenatargettarget" ) then
			self.unitRealOwner = ShadowUF.arenaUnits[self.unitID] .. "target"
		elseif( self.unit == "focustarget" ) then
			self.unitRealOwner = "focus"
			self:RegisterNormalEvent("PLAYER_FOCUS_CHANGED", Units, "CheckUnitStatus")
		elseif( self.unit == "targettarget" or self.unit == "targettargettarget" ) then
			self.unitRealOwner = "target"
			self:RegisterNormalEvent("PLAYER_TARGET_CHANGED", Units, "CheckUnitStatus")
		end

		self:RegisterNormalEvent("UNIT_TARGET", Units, "CheckPetUnitUpdated")
	end

	self:SetVisibility()
	Units:CheckUnitStatus(self)
end

Units.OnAttributeChanged = OnAttributeChanged

local secureInitializeUnit = [[
	local header = self:GetParent()

	self:SetHeight(header:GetAttribute("style-height"))
	self:SetWidth(header:GetAttribute("style-width"))
	self:SetScale(header:GetAttribute("style-scale"))

	self:SetAttribute("toggleForVehicle", true)

	self:SetAttribute("*type1", "target")
	self:SetAttribute("*type2", "togglemenu")
	self:SetAttribute("type2", "togglemenu")

	self:SetAttribute("isHeaderDriven", true)

	-- initialize frame
	header:CallMethod("initialConfigFunction", self:GetName())

	-- Clique integration
	local clickHeader = header:GetFrameRef("clickcast_header")
	if( clickHeader ) then
		clickHeader:SetAttribute("clickcast_button", self)
		clickHeader:RunAttribute("clickcast_register")
	end
]]

local unitButtonTemplate = ClickCastHeader and (BackdropTemplateMixin and "ClickCastUnitTemplate,SUF_SecureUnitTemplate,BackdropTemplate" or "ClickCastUnitTemplate,SUF_SecureUnitTemplate") or (BackdropTemplateMixin and "SUF_SecureUnitTemplate,BackdropTemplate" or "SUF_SecureUnitTemplate")

-- Header unit initialized
local function initializeUnit(header, frameName)
	local frame = _G[frameName]

	frame.ignoreAnchor = true
	frame.unitType = header.unitType

	Units:CreateUnit(frame)
end

-- Show tooltip
local function OnEnter(self)
	if( self.OnEnter ) then
		self:OnEnter()
	end
end

local function OnLeave(self)
	if( self.OnLeave ) then
		self:OnLeave()
	end
end

local function SUF_OnEnter(self)
	if( not ShadowUF.db.profile.tooltipCombat or not InCombatLockdown() ) then
		if not GameTooltip:IsForbidden() then
			UnitFrame_OnEnter(self)
		end
	end
end

local function SUF_OnLeave(self)
	if not GameTooltip:IsForbidden() then
		UnitFrame_OnLeave(self)
	end
end

-- Create the generic things that we want in every secure frame regardless if it's a button or a header
local function ClassToken(self)
	return (select(2, UnitClass(self.unit)))
end

local function ArenaClassToken(self)
	local specID = GetArenaOpponentSpec(self.unitID)
	return specID and select(6, GetSpecializationInfoByID(specID))
end

function Units:CreateUnit(...)
	local frame = select("#", ...) > 1 and CreateFrame(...) or select(1, ...)
	frame.fullUpdates = {}
	frame.registeredEvents = {}
	frame.visibility = {}
	frame.BlizzRegisterUnitEvent = frame.RegisterUnitEvent
	frame.RegisterNormalEvent = RegisterNormalEvent
	frame.RegisterUnitEvent = RegisterUnitEvent
	frame.RegisterUpdateFunc = RegisterUpdateFunc
	frame.UnregisterAll = UnregisterAll
	frame.UnregisterSingleEvent = UnregisterEvent
	frame.SetRangeAlpha = SetRangeAlpha
	frame.DisableRangeAlpha = DisableRangeAlpha
	frame.UnregisterUpdateFunc = UnregisterUpdateFunc
	frame.ReregisterUnitEvents = ReregisterUnitEvents
	frame.SetBarColor = SetBarColor
	frame.SetBlockColor = SetBlockColor
	frame.FullUpdate = FullUpdate
	frame.SetVisibility = SetVisibility
	frame.UnitClassToken = ClassToken
	frame.topFrameLevel = 5

	-- Ensures that text is the absolute highest thing there is
	frame.highFrame = CreateFrame("Frame", nil, frame)
	frame.highFrame:SetFrameLevel(frame.topFrameLevel + 2)
	frame.highFrame:SetAllPoints(frame)

	frame:HookScript("OnAttributeChanged", OnAttributeChanged)
	frame:SetScript("OnEvent", OnEvent)
	frame:HookScript("OnEnter", OnEnter)
	frame:HookScript("OnLeave", OnLeave)
	frame:SetScript("OnShow", OnShow)
	frame:SetScript("OnHide", OnHide)

	frame.OnEnter = SUF_OnEnter
	frame.OnLeave = SUF_OnLeave

	frame:RegisterForClicks("AnyUp")
	-- non-header frames don't set those, so we need to do it
	if( not InCombatLockdown() and not frame:GetAttribute("isHeaderDriven") ) then
		frame:SetAttribute("*type1", "target")
		frame:SetAttribute("*type2", "togglemenu")
	end

	return frame
end

-- Reload a header completely
function Units:ReloadHeader(type)
	if( ShadowUF.db.profile.units[type].frameSplit ) then
		if( headerFrames.raid ) then
			self:InitializeFrame("raid")
		else
			self:SetHeaderAttributes(headerFrames.raidParent, type)
			ShadowUF.Layout:AnchorFrame(UIParent, headerFrames.raidParent, ShadowUF.db.profile.positions[type])
			ShadowUF:FireModuleEvent("OnLayoutReload", type)
		end
	elseif( type == "raid" and not ShadowUF.db.profile.units[type].frameSplit and headerFrames.raidParent ) then
		self:InitializeFrame("raid")

	elseif( headerFrames[type] ) then
		self:SetHeaderAttributes(headerFrames[type], type)
		ShadowUF.Layout:AnchorFrame(UIParent, headerFrames[type], ShadowUF.db.profile.positions[type])
		ShadowUF:FireModuleEvent("OnLayoutReload", type)
	end
end

function Units:PositionHeaderChildren(frame)
    local point = frame:GetAttribute("point") or "TOP"
    local relativePoint = ShadowUF.Layout:GetRelativeAnchor(point)

	if( #(frame.children) == 0 ) then return end

    local xMod, yMod = math.abs(frame:GetAttribute("xMod")), math.abs(frame:GetAttribute("yMod"))
    local x = frame:GetAttribute("xOffset") or 0
    local y = frame:GetAttribute("yOffset") or 0

	for id, child in pairs(frame.children) do
		if( id > 1 ) then
			frame.children[id]:ClearAllPoints()
			frame.children[id]:SetPoint(point, frame.children[id - 1], relativePoint, xMod * x, yMod * y)
		else
			frame.children[id]:ClearAllPoints()
			frame.children[id]:SetPoint(point, frame, point, 0, 0)
		end
	end
end

function Units:CheckGroupVisibility()
	if( not ShadowUF.db.profile.locked ) then return end
	local raid = headerFrames.raid and not ShadowUF.db.profile.units.raid.frameSplit and headerFrames.raid or headerFrames.raidParent
	local party = headerFrames.party
	if( party ) then
		party:SetAttribute("showParty", ( not ShadowUF.db.profile.units.raid.showParty or not ShadowUF.enabledUnits.raid ) and true or false)
		party:SetAttribute("showPlayer", ShadowUF.db.profile.units.party.showPlayer)
	end

	if( raid and party ) then
		raid:SetAttribute("showParty", not party:GetAttribute("showParty"))
		raid:SetAttribute("showPlayer", party:GetAttribute("showPlayer"))
	end
end

function Units:SetHeaderAttributes(frame, type)
	local config = ShadowUF.db.profile.units[type]
	local xMod = config.attribPoint == "LEFT" and 1 or config.attribPoint == "RIGHT" and -1 or 0
	local yMod = config.attribPoint == "TOP" and -1 or config.attribPoint == "BOTTOM" and 1 or 0
	local widthMod = (config.attribPoint == "LEFT" or config.attribPoint == "RIGHT") and MEMBERS_PER_RAID_GROUP or 1
	local heightMod = (config.attribPoint == "TOP" or config.attribPoint == "BOTTOM") and MEMBERS_PER_RAID_GROUP or 1

	frame:SetAttribute("point", config.attribPoint)
	frame:SetAttribute("sortMethod", config.sortMethod)
	frame:SetAttribute("sortDir", config.sortOrder)

	frame:SetAttribute("xOffset", config.offset * xMod)
	frame:SetAttribute("yOffset", config.offset * yMod)
	frame:SetAttribute("xMod", xMod)
	frame:SetAttribute("yMod", yMod)

	-- Split up raid frame groups
	if( config.frameSplit and type == "raid" ) then
		local anchorPoint, relativePoint, xModRow, yModRow = ShadowUF.Layout:GetSplitRelativeAnchor(config.attribPoint, config.attribAnchorPoint)
		local columnPoint, xColMod, yColMod = ShadowUF.Layout:GetRelativeAnchor(config.attribPoint)

		local lastHeader = frame
		for id=1, 8 do
			local childHeader = headerFrames["raid" .. id]
			if( childHeader ) then
				childHeader:SetAttribute("showRaid", ShadowUF.db.profile.locked and true)

				childHeader:SetAttribute("minWidth", config.width * widthMod)
				childHeader:SetAttribute("minHeight", config.height * heightMod)

				if( childHeader ~= frame ) then
					childHeader:SetAttribute("point", config.attribPoint)
					childHeader:SetAttribute("sortMethod", config.sortMethod)
					childHeader:SetAttribute("sortDir", config.sortOrder)
					childHeader:SetAttribute("showPlayer", nil)
					childHeader:SetAttribute("showParty", nil)

					childHeader:SetAttribute("xOffset", frame:GetAttribute("xOffset"))
					childHeader:SetAttribute("yOffset", frame:GetAttribute("yOffset"))

					childHeader:ClearAllPoints()
					if( (id - 1) % config.groupsPerRow == 0 ) then
						local x = config.groupSpacing * xColMod
						local y = config.groupSpacing * yColMod

						-- When we're anchoring a new column to the bottom of naother one, the height will mess it up
						-- if what we anchored to isn't full, by anchoring it to the top instead will get a consistent result
						local point = columnPoint
						if( point == "BOTTOM" ) then
							point = config.attribPoint
							x = x + (config.height * 5) * xColMod
							y = y + (config.height * 5) * yColMod
						end

						childHeader:SetPoint(config.attribPoint, headerFrames["raid" .. id - config.groupsPerRow], point, x, y)
					else
						childHeader:SetPoint(anchorPoint, lastHeader, relativePoint, config.columnSpacing * xModRow, config.columnSpacing * yModRow)
					end

					lastHeader = childHeader
				end

				-- There appears to be a bug where if you reloadui with a split raid frames the positions get messed up
				-- if we force a repositioning through startingIndex it's fixed thought.
				childHeader:SetAttribute("startingIndex", 10000)
				childHeader:SetAttribute("startingIndex", 1)
			end
		end

	-- Normal raid, ma or mt
	elseif( type == "raidpet" or type == "raid" or type == "mainassist" or type == "maintank" ) then
		local filter
		if( config.filters ) then
			for id, enabled in pairs(config.filters) do
				if( enabled ) then
					if( filter ) then
						filter = filter .. "," .. id
					else
						filter = id
					end
				end
			end
		else
			filter = config.groupFilter
		end

		frame:SetAttribute("showRaid", ShadowUF.db.profile.locked and true)
		frame:SetAttribute("maxColumns", config.maxColumns)
		frame:SetAttribute("unitsPerColumn", config.unitsPerColumn)
		frame:SetAttribute("columnSpacing", config.columnSpacing)
		frame:SetAttribute("columnAnchorPoint", config.attribAnchorPoint)
		frame:SetAttribute("groupFilter", filter or "1,2,3,4,5,6,7,8")
		frame:SetAttribute("roleFilter", config.roleFilter)

		if( config.groupBy == "CLASS" ) then
			frame:SetAttribute("groupingOrder", "DEATHKNIGHT,DEMONHUNTER,DRUID,HUNTER,MAGE,PALADIN,PRIEST,ROGUE,SHAMAN,WARLOCK,WARRIOR,MONK,EVOKER")
			frame:SetAttribute("groupBy", "CLASS")
		elseif( config.groupBy == "ASSIGNEDROLE" ) then
			frame:SetAttribute("groupingOrder", "TANK,HEALER,DAMAGER,NONE")
			frame:SetAttribute("groupBy", "ASSIGNEDROLE")
		else
			frame:SetAttribute("groupingOrder", "1,2,3,4,5,6,7,8")
			frame:SetAttribute("groupBy", "GROUP")
		end

	-- Need to position the fake units
	elseif( type == "boss" or type == "arena" or type == "battleground" ) then
		frame:SetAttribute("attribPoint", config.attribPoint)
		frame:SetAttribute("baseOffset", config.offset)
		frame:SetAttribute("childChanged", 1)

		self:PositionHeaderChildren(frame)

	-- Update party frames to not show anyone if they should be in raids
	elseif( type == "party" ) then
		frame:SetAttribute("maxColumns", math.ceil((config.showPlayer and 5 or 4) / config.unitsPerColumn))
		frame:SetAttribute("unitsPerColumn", config.unitsPerColumn)
		frame:SetAttribute("columnSpacing", config.columnSpacing)
		frame:SetAttribute("columnAnchorPoint", config.attribAnchorPoint)

		self:CheckGroupVisibility()
		if( stateMonitor.party ) then
			stateMonitor.party:SetAttribute("hideSemiRaid", ShadowUF.db.profile.units.party.hideSemiRaid)
			stateMonitor.party:SetAttribute("hideAnyRaid", ShadowUF.db.profile.units.party.hideAnyRaid)
		end
	end

	if( type == "raid" ) then
		self:CheckGroupVisibility()

		for id, monitor in pairs(stateMonitor.raids) do
			monitor:SetAttribute("hideSemiRaid", ShadowUF.db.profile.units.raid.hideSemiRaid)
		end
	end

	if( not InCombatLockdown() and headerUnits[type] and frame.shouldReset ) then
		-- Children no longer have ClearAllPoints() called on them before they are repositioned
		-- this tries to stop it from bugging out by clearing it then forcing it to reposition everything
		local name = frame:GetName() .. "UnitButton"
		local index = 1
		local child = _G[name .. index]
		while( child ) do
			child:ClearAllPoints()

			index = index + 1
			child = _G[name .. index]
		end

		-- Hiding and reshowing the header forces an update
		if( frame:IsShown() ) then
			frame:Hide()
			frame:Show()
		end
	end

	frame.shouldReset = true
end

-- Load a single unit such as player, target, pet, etc
function Units:LoadUnit(unit)
	-- Already be loaded, just enable
	if( unitFrames[unit] ) then
		RegisterUnitWatch(unitFrames[unit], unitFrames[unit].hasStateWatch)
		return
	end

	local frame = self:CreateUnit("Button", "SUFUnit" .. unit, petBattleFrame, BackdropTemplateMixin and "SecureUnitButtonTemplate,BackdropTemplate" or "SecureUnitButtonTemplate")
	frame:SetAttribute("unit", unit)
	frame.hasStateWatch = unit == "pet"

	-- Annd lets get this going
	RegisterUnitWatch(frame, frame.hasStateWatch)
end

local function setupRaidStateMonitor(id, headerFrame)
	if( stateMonitor.raids[id] ) then return end

	stateMonitor.raids[id] = CreateFrame("Frame", nil, nil, "SecureHandlerBaseTemplate")
	stateMonitor.raids[id]:SetAttribute("raidDisabled", nil)
	stateMonitor.raids[id]:SetFrameRef("raidHeader", headerFrame)
	stateMonitor.raids[id]:SetAttribute("hideSemiRaid", ShadowUF.db.profile.units.raid.hideSemiRaid)
	stateMonitor.raids[id]:WrapScript(stateMonitor.raids[id], "OnAttributeChanged", [[
		if( name ~= "state-raidmonitor" and name ~= "raiddisabled" and name ~= "hidesemiraid" ) then
			return
		end

		local header = self:GetFrameRef("raidHeader")
		if( self:GetAttribute("raidDisabled") ) then
			if( header:IsVisible() ) then header:Hide() end
			return
		end

		if( self:GetAttribute("hideSemiRaid") and self:GetAttribute("state-raidmonitor") ~= "raid6" ) then
			header:Hide()
		else
			header:Show()
		end
	]])

	RegisterStateDriver(stateMonitor.raids[id], "raidmonitor", "[target=raid6, exists] raid6; none")
end

function Units:LoadSplitGroupHeader(type)
	if( headerFrames.raid ) then headerFrames.raid:Hide() end
	headerFrames.raidParent = nil

	for id, monitor in pairs(stateMonitor.raids) do
		monitor:SetAttribute("hideSemiRaid", ShadowUF.db.profile.units.raid.hideSemiRaid)
		monitor:SetAttribute("raidDisabled", id == -1 and true or nil)
		monitor:SetAttribute("recheck", time())
	end

	local config = ShadowUF.db.profile.units[type]
	for id, enabled in pairs(ShadowUF.db.profile.units[type].filters) do
		local frame = headerFrames["raid" .. id]
		if( enabled ) then
			if( not frame ) then
				frame = CreateFrame("Frame", "SUFHeader" .. type .. id, petBattleFrame, "SecureGroupHeaderTemplate")
				frame:SetAttribute("template", unitButtonTemplate)
				frame:SetAttribute("initial-unitWatch", true)
				frame:SetAttribute("showRaid", true)
				frame:SetAttribute("groupFilter", id)
				frame:SetAttribute("initialConfigFunction", secureInitializeUnit)
				frame.initialConfigFunction = initializeUnit
				frame.isHeaderFrame = true
				frame.unitType = type
				frame.unitMappedType = type
				frame.splitParent = type
				frame.groupID = id
				--frame:SetBackdrop({bgFile = "Interface\\ChatFrame\\ChatFrameBackground", edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", edgeSize = 1})
				--frame:SetBackdropBorderColor(1, 0, 0, 1)
				--frame:SetBackdropColor(0, 0, 0, 0)

				frame:SetAttribute("style-height", config.height)
				frame:SetAttribute("style-width", config.width)
				frame:SetAttribute("style-scale", config.scale)

				if( ClickCastHeader ) then
					-- the OnLoad adds the functions like SetFrameRef to the header
					SecureHandler_OnLoad(frame)
					frame:SetFrameRef("clickcast_header", ClickCastHeader)
				end

				headerFrames["raid" .. id] = frame
			end

			frame:Show()

			if( not headerFrames.raidParent or headerFrames.raidParent.groupID > id ) then
				headerFrames.raidParent = frame
			end

			setupRaidStateMonitor(id, frame)

		elseif( frame ) then
			frame:Hide()
		end
	end

	if( headerFrames.raidParent ) then
		self:SetHeaderAttributes(headerFrames.raidParent, type)
		ShadowUF.Layout:AnchorFrame(UIParent, headerFrames.raidParent, ShadowUF.db.profile.positions.raid)
	end
end

-- Load a header unit, party or raid
function Units:LoadGroupHeader(type)
	-- Already created, so just reshow and we out
	if( headerFrames[type] ) then
		headerFrames[type]:Show()

		if( type == "party" and stateMonitor.party ) then
			stateMonitor.party:SetAttribute("partyDisabled", nil)
		end

		if( type == "raid" ) then
			for id, monitor in pairs(stateMonitor.raids) do
				monitor:SetAttribute("hideSemiRaid", ShadowUF.db.profile.units.raid.hideSemiRaid)
				monitor:SetAttribute("raidDisabled", id >= 0 and true or nil)
			end
		end

		if( type == "party" or type == "raid" ) then
			self:CheckGroupVisibility()
		end
		return
	end

	local headerFrame = CreateFrame("Frame", "SUFHeader" .. type, petBattleFrame, type == "raidpet" and "SecureGroupPetHeaderTemplate" or "SecureGroupHeaderTemplate")
	headerFrames[type] = headerFrame

	self:SetHeaderAttributes(headerFrame, type)

	headerFrame:SetAttribute("template", unitButtonTemplate)
	headerFrame:SetAttribute("initial-unitWatch", true)
	headerFrame:SetAttribute("initialConfigFunction", secureInitializeUnit)

	headerFrame.initialConfigFunction = initializeUnit
	headerFrame.isHeaderFrame = true
	headerFrame.unitType = type
	headerFrame.unitMappedType = type

	-- For securely managely the display
	local config = ShadowUF.db.profile.units[type]
	headerFrame:SetAttribute("style-height", config.height)
	headerFrame:SetAttribute("style-width", config.width)
	headerFrame:SetAttribute("style-scale", config.scale)

	if( type == "raidpet" ) then
		headerFrame:SetAttribute("filterOnPet", true)
	end

	if( ClickCastHeader ) then
		-- the OnLoad adds the functions like SetFrameRef to the header
		SecureHandler_OnLoad(headerFrame)
		headerFrame:SetFrameRef("clickcast_header", ClickCastHeader)
	end

	ShadowUF.Layout:AnchorFrame(UIParent, headerFrame, ShadowUF.db.profile.positions[type])

	-- We have to do party hiding based off raid as a state driver so that we can smoothly hide the party frames based off of combat and such
	-- technically this isn't the cleanest solution because party frames will still have unit watches active
	-- but this isn't as big of a deal, because SUF automatically will unregister the OnEvent for party frames while hidden
	if( type == "party" ) then
		stateMonitor.party = CreateFrame("Frame", nil, nil, "SecureHandlerBaseTemplate")
		stateMonitor.party:SetAttribute("partyDisabled", nil)
		stateMonitor.party:SetFrameRef("partyHeader", headerFrame)
		stateMonitor.party:SetAttribute("hideSemiRaid", ShadowUF.db.profile.units.party.hideSemiRaid)
		stateMonitor.party:SetAttribute("hideAnyRaid", ShadowUF.db.profile.units.party.hideAnyRaid)
		stateMonitor.party:WrapScript(stateMonitor.party, "OnAttributeChanged", [[
			if( name ~= "state-raidmonitor" and name ~= "partydisabled" and name ~= "hideanyraid" and name ~= "hidesemiraid" ) then return end
			if( self:GetAttribute("partyDisabled") ) then return end

			if( self:GetAttribute("hideAnyRaid") and ( self:GetAttribute("state-raidmonitor") == "raid1" or self:GetAttribute("state-raidmonitor") == "raid6" ) ) then
				self:GetFrameRef("partyHeader"):Hide()
			elseif( self:GetAttribute("hideSemiRaid") and self:GetAttribute("state-raidmonitor") == "raid6" ) then
				self:GetFrameRef("partyHeader"):Hide()
			else
				self:GetFrameRef("partyHeader"):Show()
			end
		]])
		RegisterStateDriver(stateMonitor.party, "raidmonitor", "[target=raid6, exists] raid6; [target=raid1, exists] raid1; none")

	elseif( type == "raid" ) then
		setupRaidStateMonitor(-1, headerFrame)
	else
		headerFrame:Show()
	end

	-- Any frames that were split out in this group need to be hidden
	if( headerFrames.raidParent ) then
		for _, f in pairs(headerFrames) do
			if( f.splitParent == type ) then
				f:Hide()
			end
		end
	end
end

-- Fake headers that are supposed to act like headers to the users, but are really not
function Units:LoadZoneHeader(type)
	if( headerFrames[type] ) then
		headerFrames[type]:Show()
		for _, child in pairs(headerFrames[type].children) do
			RegisterUnitWatch(child, child.hasStateWatch)
		end

		if( type == "arena" ) then
			self:InitializeArena()
		end
		return
	end

	local headerFrame = CreateFrame("Frame", "SUFHeader" .. type, petBattleFrame, "SecureHandlerBaseTemplate")
	headerFrame.isHeaderFrame = true
	headerFrame.unitType = type
	headerFrame.unitMappedType = remappedUnits[type] or type
	headerFrame:SetClampedToScreen(true)
	headerFrame:SetMovable(true)
	headerFrame:SetHeight(0.1)
	headerFrame:SetAttribute("totalChildren", #(ShadowUF[type .. "Units"]))
	headerFrame.children = {}

	headerFrames[type] = headerFrame

	if( type == "arena" ) then
		headerFrame:SetScript("OnAttributeChanged", function(frame, key, value)
			if( key == "childChanged" and value and frame.children[value] and frame:IsVisible() ) then
				frame.children[value]:FullUpdate()
			end
		end)
	end

	for id, unit in pairs(ShadowUF[type .. "Units"]) do
		local frame = self:CreateUnit("Button", "SUFHeader" .. type .. "UnitButton" .. id, headerFrame, BackdropTemplateMixin and "SecureUnitButtonTemplate,BackdropTemplate" or "SecureUnitButtonTemplate")
		frame.ignoreAnchor = true
		frame.hasStateWatch = true
		frame.unitUnmapped = type .. id
		frame:SetAttribute("unit", unit)
		frame:SetAttribute("unitID", id)
		frame:Hide()

		-- Override with our arena specific concerns
		frame.UnitClassToken = ArenaClassToken
		frame:SetScript("OnShow", OnShowForced)

		headerFrame.children[id] = frame
		headerFrame:SetFrameRef("child" .. id, frame)

		-- Arena frames are only allowed to be shown not hidden from the unit existing, or else when a Rogue
		-- stealths the frame will hide which looks bad. Instead force it to stay open and it has to be manually hidden when the player leaves an arena.
		if( type == "arena" ) then
			stateMonitor:WrapScript(frame, "OnAttributeChanged", [[
				if( name == "state-unitexists" ) then
					local parent = self:GetParent()
					if( value and self:GetAttribute("unitDisappeared") ) then
						parent:SetAttribute("childChanged", self:GetAttribute("unitID"))
						self:SetAttribute("unitDisappeared", nil)
					elseif( not value and not self:GetAttribute("unitDisappeared") ) then
						self:SetAttribute("unitDisappeared", true)
					end

					if( value ) then
						self:Show()
					end
				end
			]])
		else
			stateMonitor:WrapScript(frame, "OnAttributeChanged", [[
				if( name == "state-unitexists" ) then
					if( value ) then
						self:Show()
					else
						self:Hide()
					end

					local parent = self:GetParent()
					parent:SetAttribute("childChanged", self:GetAttribute("unitID"))
				end
			]])
		end

		RegisterUnitWatch(frame, frame.hasStateWatch)
	end

	-- Dynamic height/width adjustment
	stateMonitor:WrapScript(headerFrame, "OnAttributeChanged", [[
		if( name ~= "childchanged" ) then return end

		local visible = 0
		for i=1, self:GetAttribute("totalChildren") do
			if( self:GetFrameRef("child" .. i):IsShown() ) then
				visible = visible + 1
			end
		end

		if( visible == 0 ) then
			self:Hide()
			return
		end

		local child = self:GetFrameRef("child1")
		local xMod = math.abs(self:GetAttribute("xMod"))
		local yMod = math.abs(self:GetAttribute("yMod"))
		local offset = self:GetAttribute("baseOffset")

		self:SetWidth(xMod * ((child:GetWidth() * (visible - 1)) + (offset * (visible - 1))) + child:GetWidth())
		self:SetHeight(yMod * ((child:GetHeight() * (visible - 1)) + (offset * (visible - 1))) + child:GetHeight())
		self:Show()
	]])


	self:SetHeaderAttributes(headerFrame, type)
	ShadowUF.Layout:AnchorFrame(UIParent, headerFrame, ShadowUF.db.profile.positions[type])

	if( type == "arena" ) then
		self:InitializeArena()
	end
end

-- Load a unit that is a child of another unit (party pet/party target)
function Units:LoadChildUnit(parent, type, id)
	if( InCombatLockdown() ) then
		if( not queuedCombat[parent:GetName() .. type] ) then
			queuedCombat[parent:GetName() .. type] = {parent = parent, type = type, id = id}
		end
		return
	else
		-- This is a bit confusing to write down, but just in case I forget:
		-- It's possible theres a bug where you have a frame skip creating it's child because it thinks one was already created, but the one that was created is actually associated to another parent. What would need to be changed is it checks if the frame has the parent set to it and it's the same unit type before returning, not that the units match.
		for frame in pairs(frameList) do
			if( frame.unitType == type and frame.parent == parent ) then
				RegisterUnitWatch(frame, frame.hasStateWatch)
				return
			end
		end
	end

	parent.hasChildren = true

	local suffix
	if( string.match(type, "pet$") ) then
		suffix = "pet"
	elseif( string.match(type, "targettarget$") ) then
		suffix = "targettarget"
	else
		suffix = "target"
	end

	-- Now we can create the actual frame
	local frame = self:CreateUnit("Button", "SUFChild" .. type .. string.match(parent:GetName(), "(%d+)"), parent, BackdropTemplateMixin and "SecureUnitButtonTemplate,BackdropTemplate" or "SecureUnitButtonTemplate")
	frame.unitType = type
	frame.parent = parent
	frame.isChildUnit = true
	frame.hasStateWatch = type == "partypet"
	frame:SetFrameStrata("LOW")
	frame:SetAttribute("useparent-unit", true)
	frame:SetAttribute("unitsuffix", suffix)
	OnAttributeChanged(frame, "unit", SecureButton_GetModifiedUnit(frame))
	frameList[frame] = true

	RegisterUnitWatch(frame, frame.hasStateWatch)
	ShadowUF.Layout:AnchorFrame(parent, frame, ShadowUF.db.profile.positions[type])
end

-- Initialize units
function Units:InitializeFrame(type)
	if( type == "raid" and ShadowUF.db.profile.units[type].frameSplit ) then
		self:LoadSplitGroupHeader(type)
	elseif( type == "party" or type == "raid" or type == "maintank" or type == "mainassist" or type == "raidpet" ) then
		self:LoadGroupHeader(type)
	elseif( self.childUnits[type] ) then
		for frame in pairs(frameList) do
			if( frame.unitType == self.childUnits[type] and ShadowUF.db.profile.units[frame.unitType] and frame.unitID ) then
				self:LoadChildUnit(frame, type, frame.unitID)
			end
		end
	elseif( self.zoneUnits[type] ) then
		self:LoadZoneHeader(type)
	else
		self:LoadUnit(type)
	end
end

-- Uninitialize units
function Units:UninitializeFrame(type)
	if( type == "party" or type == "raid" ) then
		self:CheckGroupVisibility()
	end

	-- Disables showing party in raid automatically if raid frames are disabled
	if( type == "party" and stateMonitor.party ) then
		stateMonitor.party:SetAttribute("partyDisabled", true)
	end
	if( type == "raid" ) then
		for _, monitor in pairs(stateMonitor.raids) do
			monitor:SetAttribute("raidDisabled", true)
		end
	end

	-- Disable the parent and the children will follow
	if( ShadowUF.db.profile.units[type].frameSplit ) then
		for _, headerFrame in pairs(headerFrames) do
			if( headerFrame.splitParent == type ) then
				headerFrame:Hide()
			end
		end
	elseif( headerFrames[type] ) then
		headerFrames[type]:Hide()

		if( headerFrames[type].children ) then
			for _, frame in pairs(headerFrames[type].children) do
				if( self.zoneUnits[type] ) then
					UnregisterUnitWatch(frame)
					frame:SetAttribute("state-unitexists", false)
				end

				frame:Hide()
			end
		end
	else
		-- Disable all frames of this type
		for frame in pairs(frameList) do
			if( frame.unitType == type ) then
				UnregisterUnitWatch(frame)
				frame:SetAttribute("state-unitexits", false)
				frame:Hide()
			end
		end
	end
end

-- Profile changed, reload units
function Units:ProfileChanged()
	-- Reset the anchors for all frames to prevent X is dependant on Y
	for frame in pairs(frameList) do
		if( frame.unit ) then
			frame:ClearAllPoints()
		end
	end

	for frame in pairs(frameList) do
		if( frame.unit and ShadowUF.db.profile.units[frame.unitType].enabled ) then
			-- Force all enabled modules to disable
			for key, module in pairs(ShadowUF.modules) do
				if( frame[key] and frame.visibility[key] ) then
					frame.visibility[key] = nil
					module:OnDisable(frame)
				end
			end

			-- Now enable whatever we need to
			frame:SetVisibility()
			ShadowUF.Layout:Load(frame)
			frame:FullUpdate()
		end
	end

	for _, frame in pairs(headerFrames) do
		if( ShadowUF.db.profile.units[frame.unitType].enabled ) then
			self:ReloadHeader(frame.unitType)
		end
	end
end

-- Small helper function for creating bars with
function Units:CreateBar(parent)
	local bar = CreateFrame("StatusBar", nil, parent)
	bar:SetFrameLevel(parent.topFrameLevel or 5)
	bar.parent = parent

	bar.background = bar:CreateTexture(nil, "BORDER")
	bar.background:SetHeight(1)
	bar.background:SetWidth(1)
	bar.background:SetAllPoints(bar)
	bar.background:SetHorizTile(false)

	return bar
end

-- Handle showing for the arena prep frames
function Units:InitializeArena()
	if( not headerFrames.arena or InCombatLockdown() ) then return end

	local specs = GetNumArenaOpponentSpecs()
	if( not specs or specs == 0 ) then return end

	for i=1, specs do
		local frame = headerFrames.arena.children[i]
		frame:SetAttribute("state-unitexists", true)
		frame:Show()
		frame:FullUpdate()
	end
end

-- Deal with zone changes for enabling modules
local instanceType, queueZoneCheck
function Units:CheckPlayerZone(force)
	if( InCombatLockdown() ) then
		queueZoneCheck = force and 2 or 1
		return
	end

	-- CanHearthAndResurrectFromArea() returns true for world pvp areas, according to BattlefieldFrame.lua
	local instance = CanHearthAndResurrectFromArea() and "pvp" or select(2, IsInInstance()) or "none"
	if( instance == "scenario" ) then instance = "party" end

	if( instance == instanceType and not force ) then return end
	instanceType = instance

	ShadowUF:LoadUnits()
	for frame in pairs(frameList) do
		if( frame.unit and ShadowUF.db.profile.units[frame.unitType].enabled ) then
			frame:SetVisibility()

			-- Auras are enabled so will need to check if the filter has to change
			if( frame.visibility.auras ) then
				ShadowUF.modules.auras:UpdateFilter(frame)
			end

			if( UnitExists(frame.unit) ) then
				frame:FullUpdate()
			end
		end
	end
end

-- Handle figuring out what auras players can cure
local curableSpells = {
	["DRUID"] = {[88423] = {"Magic", "Curse", "Poison"}, [2782] = {"Curse", "Poison"}},
	["PRIEST"] = {[527] = {"Magic", "Disease"}, [32375] = {"Magic"}, [213634] = {"Disease"}},
	["PALADIN"] = {[4987] = {"Poison", "Disease", "Magic"}, [213644] = {"Poison", "Disease"}},
	["SHAMAN"] = {[77130] = {"Curse", "Magic"}, [51886] = {"Curse"}, [383013] = {"Poison"}},
	["MONK"] = {[115450] = {"Poison", "Disease", "Magic"}, [218164] = {"Poison", "Disease"}},
	["MAGE"] = {[475] = {"Curse"}},
	["WARLOCK"] = {[89808] = {"Magic"}},
	["EVOKER"] = {[365585] = {"Poison"}, [360823] = {"Magic", "Poison"}, [374251] = {"Poison", "Curse", "Disease"}}
}

curableSpells = curableSpells[playerClass]

local function checkCurableSpells()
	if( not curableSpells ) then return end

	table.wipe(Units.canCure)

	for spellID, cures in pairs(curableSpells) do
		if( IsPlayerSpell(spellID) or IsSpellKnown(spellID, true) ) then
			for _, auraType in pairs(cures) do
				Units.canCure[auraType] = true
			end
		end
	end
end

local centralFrame = CreateFrame("Frame")
centralFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
centralFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
centralFrame:RegisterEvent("PLAYER_LOGIN")
centralFrame:RegisterEvent("PLAYER_LEVEL_UP")
centralFrame:RegisterEvent("CINEMATIC_STOP")
centralFrame:RegisterEvent("ARENA_PREP_OPPONENT_SPECIALIZATIONS")
centralFrame:RegisterEvent("ARENA_OPPONENT_UPDATE")
centralFrame:SetScript("OnEvent", function(self, event, unit)
	-- Check if the player changed zone types and we need to change module status, while they are dead
	-- we won't change their zone type as releasing from an instance will change the zone type without them
	-- really having left the zone
	if( event == "ZONE_CHANGED_NEW_AREA" ) then
		if( UnitIsDeadOrGhost("player") ) then
			self:RegisterEvent("PLAYER_UNGHOST")
		else
			self:UnregisterEvent("PLAYER_UNGHOST")
			Units:CheckPlayerZone()
		end

	-- Force update frames
	elseif( event == "ARENA_PREP_OPPONENT_SPECIALIZATIONS" or event == "ARENA_OPPONENT_UPDATE" ) then
		Units:InitializeArena()

	-- They're alive again so they "officially" changed zone types now
	elseif( event == "PLAYER_UNGHOST" ) then
		Units:CheckPlayerZone()

	-- Monitor level up
	elseif( event == "PLAYER_LEVEL_UP" or event == "CINEMATIC_STOP" ) then
		if( unitFrames.player ) then
			unitFrames.player:SetVisibility()
			unitFrames.player:FullUpdate()
		end

	-- Monitor talent changes for curable changes
	elseif( event == "PLAYER_SPECIALIZATION_CHANGED" or event == "UNIT_PET" or event == "SPELLS_CHANGED") then
		checkCurableSpells()

		for frame in pairs(ShadowUF.Units.frameList) do
			if( frame.unit ) then
				frame:SetVisibility()

				if( frame:IsVisible() ) then
					frame:FullUpdate()
				end
		    end
	    end

	elseif( event == "PLAYER_LOGIN" ) then
		checkCurableSpells()
		self:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
		self:RegisterEvent("SPELLS_CHANGED")
		if( playerClass == "WARLOCK" ) then
			self:RegisterUnitEvent("UNIT_PET", "player", nil)
		end

	-- This is slightly hackish, but it suits the purpose just fine for somthing thats rarely called.
	elseif( event == "PLAYER_REGEN_ENABLED" ) then
		-- Now do all of the creation for child wrapping
		for _, queue in pairs(queuedCombat) do
			Units:LoadChildUnit(queue.parent, queue.type, queue.id)
		end

		table.wipe(queuedCombat)

		if( queueZoneCheck ) then
			Units:CheckPlayerZone(queueZoneCheck == 2 and true)
			queueZoneCheck = nil
		end
	end
end)
