TidyPlates = {}
local activetheme, activethemename = {}, "None"
local numChildren = -1
local echoUpdate = 0
local echoSelf = {}
local function SetSelfEcho(plate) echoSelf[plate] = true; end
local function SetEchoUpdate(echos) echoUpdate = max(echoUpdate,echos) end
local ForEachPlate		-- Function
local EMPTY_TEXTURE = "Interface\\Addons\\TidyPlates\\Media\\Empty"
local useAutohide = false
local select, pairs = select, pairs
local select = select
local InCombat, HasTarget = false, false
local Plates, PlatesVisible = {}, {}
	
-------------------------------------------------------
-- Object Functions
-------------------------------------------------------
local function SetObjectShape(object, width, height) object:SetWidth(width); object:SetHeight(height) end
local function SetObjectFont(object,  font, size, flags) object:SetFont(font, size, flags) end
local function SetObjectJustify(object, horz, vert) object:SetJustifyH(horz); object:SetJustifyV(vert) end
local function SetObjectShadow(object, shadow) if shadow then object:SetShadowColor(0,0,0,1); object:SetShadowOffset(1, -1) else object:SetShadowColor(0,0,0,0) end  end
local function SetObjectAnchor(object, anchor, anchorTo, x, y) object:ClearAllPoints();object:SetPoint(anchor, anchorTo, anchor, x, y) end
local function SetObjectTexture(object, texture) object:SetTexture(texture); object:SetTexCoord(0,1,0,1)  end
local function SetObjectBartexture(object, texture, orientation) object:SetStatusBarTexture(texture); object:SetOrientation(orientation) end
-- SetFontGroupObject
local function SetFontGroupObject(object, objectstyle) 
	SetObjectFont(object, objectstyle.typeface, objectstyle.size, objectstyle.flags) 
	SetObjectJustify(object, objectstyle.align, objectstyle.vertical)
	SetObjectShadow(object, objectstyle.shadow)
end
-- SetAnchorGroupObject
local function SetAnchorGroupObject(object, objectstyle, anchorTo)
	SetObjectShape(object, objectstyle.width, objectstyle.height) --end				
	SetObjectAnchor(object, objectstyle.anchor, anchorTo, objectstyle.x, objectstyle.y) 
end
-- SetBarGroupObject
local function SetBarGroupObject(object, objectstyle, anchorTo)
	SetObjectShape(object, objectstyle.width, objectstyle.height) --end
	SetObjectAnchor(object, objectstyle.anchor, anchorTo, objectstyle.x, objectstyle.y) --end	
	SetObjectBartexture(object, objectstyle.texture, objectstyle.orientation) --end
end
-------------------------------------------------------
-- UpdateStyle
-------------------------------------------------------
local UpdateStyle
do
	-- UpdateStyle Variables
	local index, content, extended, bars, regions, style
	local objectstyle, objectname, objectregion
	-- Style Property Groups
	local fontgroup = {"name", "level", "specialText", "specialText2"}
	local anchorgroup = {"healthborder", "threatborder", "castborder", "castnostop",
						"name",  "specialText", "specialText2", "level",
						"specialArt", "spellicon", "raidicon", "dangerskull"}
	local bargroup = {"castbar", "healthbar"}
	-------------------------------------------------------
	-- Main Function: UpdateStyle 
	-------------------------------------------------------
	function UpdateStyle(plate)
		extended = plate.extended
		bars, regions, style = extended.bars, extended.regions, extended.style

		-- Hitbox
		if not InCombat then objectstyle = style.hitbox; SetObjectShape(plate, objectstyle.width, objectstyle.height) end
		-- Frame
		SetAnchorGroupObject(extended, style.frame, plate)
		-- Anchorgroup
		for i = 1, #anchorgroup do objectname = anchorgroup[i]; SetAnchorGroupObject(regions[objectname], style[objectname], extended) end
			
		-- Bars
		for i = 1, #bargroup do objectname = bargroup[i]; SetBarGroupObject(bars[objectname], style[objectname], extended) end
		-- Texture
		SetObjectTexture(regions.castborder, style.castborder.texture)
		SetObjectTexture(regions.castnostop, style.castnostop.texture)
		-- Font Group
		for i = 1, #fontgroup do objectname = fontgroup[i];SetFontGroupObject(regions[objectname], style[objectname]) end
		-- Show/Hide
		if style.options.showName then regions.name:Show() else regions.name:Hide() end
		if style.options.showSpecialText then regions.specialText:Show() else regions.specialText:Hide() end
		if style.options.showSpecialText2 then regions.specialText2:Show() else regions.specialText2:Hide() end
		if style.options.showSpecialArt then regions.specialArt:Show() else regions.specialArt:Hide() end
		--if not style.options.showspellicon then regions.spellicon:SetAlpha(0) end
		if style.options.showDangerSkull then else regions.dangerskull:SetAlpha(0) end
	end
end
-------------------------------------------------------
-- UpdatePlateIndicators
-------------------------------------------------------
local UpdatePlateIndicators
do
	local color = {}
	local extended, unit, style, bars, regions, aggroregion, alpha, forcealpha, scale
	-- SetIndicatorElite 	-- Set Border and Aggro Glow Textures
	local function SetIndicatorElite()
		if unit.isElite then regions.healthborder:SetTexture( style.healthborder.elitetexture)
			aggroregion:SetTexture(style.threatborder.elitetexture)
		else regions.healthborder:SetTexture( style.healthborder.texture)
			aggroregion:SetTexture(style.threatborder.texture) end
	end
	-------------------------------------------------------
	-- UpdatePlateIndicators: Updates the nameplate information depending on unit conditions
	-------------------------------------------------------
	function UpdatePlateIndicators(plate)
		extended = plate.extended
		style, bars, regions, scale = extended.style, extended.bars, extended.regions, extended.scale
		unit, unitcache = extended.unit, extended.unitcache
		aggroregion = regions.threatborder
		-- Set Alpha, Scale, Elite, Aggro
		if activetheme.SetAlpha then 
			alpha, forcealpha = activetheme.SetAlpha(unit)
			if forcealpha then alpha = (alpha or 1) -- moved * (unit.alpha or 1)  to else
			else alpha = (alpha or 1) * (unit.alpha or 1) end
			extended:SetAlpha(alpha) 
		else extended:SetAlpha(unit.alpha or 1) end
		-- Scale
		if activetheme.SetScale then scale = activetheme.SetScale(unit); if scale then extended:SetScale( scale )end; end
		-- Elite Graphics
		if (unit.isElite == nil ) or unit.isElite ~= unitcache.isElite then SetIndicatorElite() end
		-- Set Aggro Region
		if  style.options.showAggroGlow and InCombat and unit.reaction ~= "FRIENDLY" and unit.type == "NPC" then
			color = style.threatcolor[unit.threatSituation]
			aggroregion:Show()
			aggroregion:SetVertexColor(color.r, color.g, color.b, color.a)
		else aggroregion:Hide() end
		-- Set Special-Case Regions
		if style.options.showSpecialText and activetheme.SetSpecialText then
			regions.specialText:SetText(activetheme.SetSpecialText(unit)) end
		if style.options.showSpecialText2 and activetheme.SetSpecialText2 then
			regions.specialText2:SetText(activetheme.SetSpecialText2(unit)) end
		if style.options.showSpecialArt and activetheme.SetSpecialArt then
			regions.specialArt:SetTexture(activetheme.SetSpecialArt(unit)) end
		-- Set Health Bar
		if activetheme.SetHealthbarColor then
			bars.healthbar:SetStatusBarColor(activetheme.SetHealthbarColor(unit))
		else bars.healthbar:SetStatusBarColor(bars.health:GetStatusBarColor()) end	
		bars.healthbar:SetMinMaxValues(bars.health:GetMinMaxValues())
		bars.healthbar:SetValue(bars.health:GetValue())
	end
end
-------------------------------------------------------
-- Update Plate / Gather Data
-------------------------------------------------------
-- Gathers Information about the unit, and requests updates, if needed
-------------------------------------------------------
local OnShowNameplate, OnHideNameplate, OnMouseoverNameplate, OnUpdateNameplate, OnStartCast, OnStopCast, OnUpdateCast
do
	local ClassReference = {}
	local _, stylename, unitchanged, extended, bars, regions, unit, unitcache, style
	-- ColorToString: Converts a color to a string with a C- prefix
	local function ColorToString(r,g,b) return "C"..math.floor((100*r) + 0.5)..math.floor((100*g) + 0.5)..math.floor((100*b) + 0.5) end
	-- GetUnitCombatStatus: Determines if a unit is in combat by checking the name text color
	local function GetUnitCombatStatus(r, g, b) return (r > .5 and g < .5) end
	-- GetUnitAggroStatus: Determines if a unit is attacking, by looking at aggro glow region
	local GetUnitAggroStatus
	do
		local shown 
		local red, green, blue
		function GetUnitAggroStatus( region)
			shown = region:IsShown()
			if not shown then return "LOW" end
			red, green, blue = region:GetVertexColor()
			if green > .7 then return "MEDIUM" end
			if red > .7 then return "HIGH" end
		end
	end
	-- GetUnitReaction: Determines the reaction, and type of unit from the health bar color
	local function GetUnitReaction(red, green, blue)																											
		if red < .01 and blue < .01 and green > .99 then return "FRIENDLY", "NPC" 
		elseif red < .01 and blue > .99 and green < .01 then return "FRIENDLY", "PLAYER"
		elseif red > .99 and blue < .01 and green > .99 then return "NEUTRAL", "NPC"
		elseif red > .99 and blue < .01 and green < .01 then return "HOSTILE", "NPC"
		else return "HOSTILE", "PLAYER" end
	end
	-- Populates the class color lookup table
	for classname, color in pairs(RAID_CLASS_COLORS) do 
		ClassReference[ColorToString(color.r, color.g, color.b)] = classname end
	-- UpdateUnitStatic: Updates Static Information
	local function UpdateUnitStatic()
		unit.name = regions.name:GetText()
		unit.isBoss = regions.dangerskull:IsShown()
		unit.isDangerous = unit.isBoss
		unit.isElite = regions.eliteicon:IsShown()
	end
	-- UpdateUnitStatus: Updates Unit Variables
	local function UpdateUnitStatus()
		unit.level = regions.level:GetText() or 1
		unit.health = bars.health:GetValue() or 0
		_, unit.healthmax = bars.health:GetMinMaxValues()
		if InCombat then unit.threatSituation = GetUnitAggroStatus(regions.threatGlow) else unit.threatSituation = "LOW" end
		unit.isMarked = regions.raidicon:IsShown()
		unit.isInCombat = GetUnitCombatStatus(regions.name:GetTextColor())
		unit.red, unit.green, unit.blue = bars.health:GetStatusBarColor()
		unit.reaction, unit.type = GetUnitReaction(unit.red, unit.green, unit.blue)
		unit.class = ClassReference[ColorToString(unit.red, unit.green, unit.blue)] or "UNKNOWN"
		unit.InCombatLockdown = InCombat
		--unit.isCasting = bars.castbar:IsShown()	--5.11.2
	end
	-- UpdateNameplateStyle
	local function UpdateNameplateStyle(plate, forceStyleUpdate)
		if activetheme.multiStyle then stylename = activetheme.SetStyle(unit); extended.style = activetheme[stylename]
		else extended.style = activetheme; stylename = tostring(activetheme) end
		if extended.stylename ~= stylename or forceStyleUpdate then UpdateStyle(plate); extended.stylename = stylename; end
		style = extended.style
	end
	-- UpdateUnitCache
	local function UpdateUnitCache() for key, value in pairs(unit) do unitcache[key] = value end end
	-- UpdateReferences
	local function UpdateReferences(plate)
		unitchanged = false
		extended = plate.extended
		bars = extended.bars
		regions = extended.regions 
		unit = extended.unit
		unitcache = extended.unitcache
	end
	-- UpdateSpecialRegions
	local function UpdateSpecialRegions()	
		regions.highlight:SetTexture(style.healthborder.glowtexture)
		regions.highlight:SetAllPoints(regions.healthborder)
		SetAnchorGroupObject(regions.level, style.level, extended) 
		SetAnchorGroupObject(regions.spellicon, style.spellicon, extended) 
		SetAnchorGroupObject(bars.castbar, style.castbar, extended) 
		if style.options.showLevel and (not regions.dangerskull:IsShown()) then regions.level:Show() else regions.level:Hide() end
	end
	-- OnShowNameplate
	OnShowNameplate = function(plate)
		PlatesVisible[plate] = true

		UpdateReferences(plate)
		UpdateUnitStatic()
		UpdateUnitStatus()
		plate.alpha = plate:GetAlpha()
		unit.alpha = plate.alpha
		unit.isTarget = false
		
		UpdateNameplateStyle(plate, true)
		UpdateSpecialRegions()
		if activetheme.OnInitialize then activetheme.OnInitialize(extended) end	
		-- Indicators
		UpdatePlateIndicators(plate)
		if activetheme.OnUpdate then activetheme.OnUpdate(extended, unit) end
		SetSelfEcho(plate)
	end
	-- OnHideNameplate
	OnHideNameplate = function(plate)
		PlatesVisible[plate] = nil
		extended = plate.extended
		wipe(extended.unit)
		wipe(extended.unitcache)
	end
	-- OnMouseoverNameplate
	OnMouseoverNameplate = function(plate)
		UpdateReferences(plate)
		unit.isMouseover = regions.highlight:IsShown()
		if unit.isMouseover then unit.guid = UnitGUID("mouseover")  end
		UpdatePlateIndicators(plate)
		if activetheme.OnUpdate then activetheme.OnUpdate(extended, unit) end
	end
	-- OnUpdateNameplate
	OnUpdateNameplate = function(plate)
		UpdateReferences(plate)
		UpdateUnitStatus()

		unit.alpha = plate.alpha -- Set in PlateHandler's OnUpdate
		unit.isTarget = HasTarget and unit.alpha == 1
		unit.isMouseover = regions.highlight:IsShown()
		if unit.isTarget then unit.guid = UnitGUID("target")  end
		
		for key, value in pairs(unit) do if unitcache[key] ~= value then unitchanged = true end end
		if unitchanged then 
			UpdateNameplateStyle(plate)
			UpdateSpecialRegions()
			UpdatePlateIndicators(plate)
		end
		UpdateUnitCache()
		if activetheme.OnUpdate then activetheme.OnUpdate(extended, unit) end
	end
	
	-- OnStartCastNameplate, OnStopCastNameplate
	OnStartCast = function(plate)		
		UpdateReferences(plate)
		UpdateUnitStatus()
				
		--unit.isCasting = bars.castbar:IsShown(); unitcache.isCasting = unit.isCasting
		UpdateNameplateStyle(plate, true)
		UpdateSpecialRegions()
		UpdatePlateIndicators(plate)
		UpdateUnitCache()
		

		unit.isCasting = true
		bars.castbar:Show()
		bars.castbar:SetStatusBarColor(bars.cast:GetStatusBarColor())
		bars.castbar:SetMinMaxValues(bars.cast:GetMinMaxValues())
		bars.castbar:SetValue(bars.cast:GetValue())
		--if activetheme.OnUpdate then activetheme.OnUpdate(extended, unit) end
		--SetSelfEcho(plate)
	end
	OnStopCast = function(plate)		
		UpdateReferences(plate)
		--unit.isCasting = false; unitcache.isCasting = unit.isCasting
		UpdatePlateIndicators(plate)
		
		unit.isCasting = false
		bars.castbar:Hide()
		--if activetheme.OnUpdate then activetheme.OnUpdate(extended, unit) end
		--SetSelfEcho(plate)
	end
	
	OnUpdateCast = function(plate)	
		UpdateReferences(plate)
		--bars.castbar:SetMinMaxValues(bars.cast:GetMinMaxValues())
		bars.castbar:SetValue(bars.cast:GetValue())
	end
end
-------------------------------------------------------
-- ApplyPlateExtension
-------------------------------------------------------
local ApplyPlateExtension
do
	local regionPosition = { "threatGlow", "healthborder", "castborder", "castnostop", 
					"spellicon", "highlight", "name", "level",
					"dangerskull", "raidicon", "eliteicon" }
	-- GetRegionReferences: populates the Tidy Plates region cache with the Blizz regions
	local GetRegionReferences
	do
		local index, region, position
		local select = select
		function GetRegionReferences(regions, ...)
			for index = 1, select( "#", ... ) do
				region = select( index, ... )
				position = regionPosition[index]
				if region and position then regions[regionPosition[index]] = region end
			end
		end
	end
	-------------------------------------------------------
	-- ApplyPlateExtension: Applies scripts, hooks, and adds additional frame variables and elements
	-------------------------------------------------------
	do
		local bars, regions, health, castbar, healthbar
		function ApplyPlateExtension(plate)
			Plates[plate] = true
			plate.extended = CreateFrame("Frame", nil, plate)
			local extended = plate.extended
			extended:SetPoint("CENTER", plate)
			extended.lastUpdate = 0					-- Change: For Throttling
			extended.style, extended.unit, extended.unitcache, extended.stylecache, extended.widgets = {}, {}, {}, {}, {}
			extended.regions, extended.bars, extended.objects = {}, {}, {}
			bars = extended.bars
			--bars.health, bars.castbar = plate:GetChildren()
			bars.health, bars.cast = plate:GetChildren()
			extended.stylename = ""
			regions = extended.regions
			-- Create new objects
			--bars.healthbar = CreateFrame("StatusBar", nil, extended) 
			bars.healthbar = CreateAltStatusbar(extended) 
			bars.castbar = CreateAltStatusbar(extended) 
			regions.threatborder = extended:CreateTexture(nil, "ARTWORK")
			regions.specialArt = extended:CreateTexture(nil, "OVERLAY")
			regions.specialText = extended:CreateFontString(nil, "OVERLAY")
			regions.specialText2 = extended:CreateFontString(nil, "OVERLAY")
			health, cast, healthbar, castbar = bars.health, bars.cast, bars.healthbar, bars.castbar
			-- Set Frame Levels and Parent
			local level = plate:GetFrameLevel()
			--health:SetParent(extended)
			--health:SetFrameLevel(level)
			healthbar:SetFrameLevel(level)
			--castbar:SetParent(extended)
			castbar:SetFrameLevel(level)
			castbar:Hide()
			extended:EnableDrawLayer( "HIGHLIGHT" );
			-- Reparent and Layer Regions
			GetRegionReferences(regions, plate:GetRegions())
			for index, region in pairs(regions) do region:SetParent(extended) end
			regions.castborder:SetDrawLayer("ARTWORK")		
			regions.castnostop:SetDrawLayer("ARTWORK")
			regions.raidicon:SetDrawLayer("OVERLAY")
			regions.spellicon:SetDrawLayer("OVERLAY")
			--regions.spellicon:SetTexCoord(.07, 1-.07, .08, 1-.07)  
			-- obj:SetTexCoord(left,right,top,bottom) -- From TopLeft = 0,0, botomright = 1,1 for full - Works!
			-- Hide Specific Elements
			regions.threatGlow:SetTexture(EMPTY_TEXTURE )
			regions.eliteicon:SetAlpha(0)
			bars.health:SetStatusBarTexture(EMPTY_TEXTURE )
			bars.cast:SetStatusBarTexture(EMPTY_TEXTURE )
			-- Update Immediately
			OnShowNameplate(plate)
			-- Hook for Updates			
			plate:HookScript("OnShow", OnShowNameplate)
			plate:HookScript("OnHide", OnHideNameplate)
			cast:HookScript("OnShow", function () OnStartCast(plate) end) 
			cast:HookScript("OnHide", function () OnStopCast(plate) end) 
			cast:HookScript("OnValueChanged", function () OnUpdateCast(plate) end) 
			health:HookScript("OnValueChanged", function () SetSelfEcho(plate) end) 
		end
	end
end
-------------------------------------------------------
-- World Update Functions
-------------------------------------------------------
local OnUpdate, UpdateAll
do
	local plate, curChildren
	local PlateSetAlpha, PlateGetAlpha
	-- IsFrameNameplate: Checks to see if the frame is a Blizz nameplate
	local function IsFrameNameplate(frame)
		local region = frame:GetRegions()
		return region and region:GetObjectType() == "Texture" and region:GetTexture() == "Interface\\TargetingFrame\\UI-TargetingFrame-Flash" 
	end
	-- OnWorldFrameChange: Checks for new Blizz Plates
	local function OnWorldFrameChange(...)
		for index = 1, select("#", ...) do
			plate = select(index, ...)
			if not Plates[plate] and IsFrameNameplate(plate) then
				ApplyPlateExtension(plate)
				if not PlateSetAlpha then
					PlateSetAlpha = plate.SetAlpha
					PlateGetAlpha = plate.GetAlpha
				end
			end
		end
	end
	-- 
	function ForEachPlate(functionToRun, ...)
		for plate in pairs(PlatesVisible) do
			if plate.extended:IsShown() then -- Plate and extended frame both explicitly visible
				functionToRun(plate, ...)
			end
		end
	end

	do
		-- This is the function that ForEachPlate calls repeatedly after UpdateAll requests an update
		local function CallOnUpdate(plate, force)
			if force then 
				local extended = plate.extended
				extended.unitcache = wipe(extended.unitcache)				
				extended.stylename = ""
				OnShowNameplate(plate)
			else
				OnUpdateNameplate(plate)
			end
		end
		-- UpdateAll: Requests an update on all Tidy Plates Frames
		UpdateAll = function(force)  ForEachPlate(CallOnUpdate, force) end
	end

	-- OnUpdate: This function will look for new frames under the WorldFrame
	OnUpdate = function(self)
		curChildren = WorldFrame:GetNumChildren()
		if (curChildren ~= numChildren) then
			numChildren = curChildren
			OnWorldFrameChange(WorldFrame:GetChildren()) 
		end	
		
		if (HasTarget) then -- Restore full opacity
			for plate in pairs(PlatesVisible) do
				-- Save original alpha before resetting it
				plate.alpha = PlateGetAlpha(plate)
				PlateSetAlpha(plate, 1) -- Local copies of methods are faster than table method lookups

				if echoSelf[plate] then
					OnUpdateNameplate(plate)
					echoSelf[plate] = nil
				end
			end
		end
		if echoUpdate > 0 then UpdateAll(false);echoUpdate = echoUpdate - 1; end
	end
end
-------------------------------------------------------
-- Event Handlers
-------------------------------------------------------
do
	local events = {}
	local PlateHandler = CreateFrame("Frame", nil, WorldFrame)
	PlateHandler:SetFrameStrata("TOOLTIP") -- When parented to WorldFrame, causes OnUpdate handler to run close to last
	PlateHandler:SetScript("OnEvent", function(self, event, ...) events[event]() end)
	-- Events
	function events:PLAYER_ENTERING_WORLD() PlateHandler:SetScript("OnUpdate", OnUpdate) end
	function events:PLAYER_REGEN_ENABLED() InCombat = false; SetEchoUpdate(1); if useAutoHide then SetCVar("nameplateShowEnemies", 0) end end
	function events:PLAYER_REGEN_DISABLED() InCombat = true; SetEchoUpdate(1); if useAutoHide then SetCVar("nameplateShowEnemies", 1) end end
	function events:PLAYER_TARGET_CHANGED()
		HasTarget = UnitExists("target") == 1 -- Must be bool, never nil!
		if (not HasTarget) then
			for plate in pairs(PlatesVisible) do
				plate.alpha = 1
			end
		end
		SetEchoUpdate(1)
	end
	function events:RAID_TARGET_UPDATE() SetEchoUpdate(1) end
	function events:UNIT_THREAT_SITUATION_UPDATE() SetEchoUpdate(1) end
	function events:UNIT_LEVEL() SetEchoUpdate(1) end
	function events:UPDATE_MOUSEOVER_UNIT() ForEachPlate(OnMouseoverNameplate) end
	function events:UNIT_AURA() SetEchoUpdate(1) end	
	-- Event Registration
	for eventname in pairs(events) do PlateHandler:RegisterEvent(eventname) end	
end

-------------------------------------------------------
-- External Commands
-------------------------------------------------------
function TidyPlates:ForceUpdate() UpdateAll(true) end
TidyPlates.Update = function() SetEchoUpdate(1) end
function TidyPlates:UseAutoHide(option) useAutoHide = option; if useAutoHide and (not InCombat) then SetCVar("nameplateShowEnemies", 0) end end
function TidyPlates:ActivateTheme(theme) if theme and type(theme) == 'table' then activetheme = theme; end end


			