if not Stuf then return end

local Stuf = Stuf
local su = Stuf.units
local dbg, dbgaura, notmage, iswarlock
Stuf:AddOnInit(function(_, idbg, CLS) 
	dbg = idbg
	dbgaura = dbg.auracolor
	notmage, iswarlock = CLS ~= "MAGE", CLS == "WARLOCK"
end)

local floor, ceil = floor, ceil
local pairs, ipairs = pairs, ipairs
local sort, tremove, tinsert = sort, tremove, tinsert
local strmatch = strmatch
local GetTime = GetTime
local CreateFrame = CreateFrame
local GameTooltip = GameTooltip
local stamin, stahr = 1/60, 1/3600

local backdrop = { bgFile="Interface\\AddOns\\Stuf\\media\\aura1.tga", }

local key = { L = "LEFT", R = "RIGHT", T = "TOP", B = "BOTTOM", }
local function GrowthBreakdown(var)
	local d1, d2, d3, d4 = strmatch(var or "LRTB", "(%u?)(%u?)(%u?)(%u?)")
	d1, d2, d3, d4 = key[d1 or "L"], key[d2 or "R"], key[d3 or "T"], key[d4 or "B"]
	
	local hdir, vdir
	if d1 == "LEFT" or d1 == "RIGHT" then
		hdir, vdir = (d1 == "RIGHT" and -1) or 1, (d3 == "BOTTOM" and 1) or -1
	else
		hdir, vdir = (d3 == "RIGHT" and -1) or 1, (d1 == "BOTTOM" and 1) or -1
	end
	return d1, d2, d3, d4, hdir, vdir
end
Stuf.GrowthBreakdown = GrowthBreakdown


local StartTimer, StopTimer, SortTimerBars
do  -- Timer Bars handlers --------------------------------------------------------------------------------------------
	local bars = { }
	local function lsort(a, b)
		return a.endtime < b.endtime
	end
	local function ClearAndSetPoint(f, lrp, lrt, lp, lx, ly, rrp, rrt, rp, rx, ry)
		f:ClearAllPoints()
		f:SetPoint(lrp, lrt, lp, lx, ly)
		if rrp then f:SetPoint(rrp, rrt, rp, rx, ry) end
	end
	function SortTimerBars(p, ptimers)
		local db = p.db
		local count, cols, rows = db.count, db.cols, db.rows
		local d1, d2, d3, d4 = p.d1, p.d2, p.d3, p.d4
		local hdir, vdir = p.hdir, p.vdir
		local spacing, vspacing = db.spacing or 0, db.vspacing or 0

		sort(ptimers, lsort)
		if d1 == "LEFT" or d1 == "RIGHT" then  -- LRTB, LRBT, RLTB, RLBT
			for i, icon in ipairs(ptimers) do
				local crow = ceil(i / cols)
				if crow > rows then
					icon:Hide()
				else
					local ccol = i % cols
					ccol = (ccol == 0 and cols) or ccol
					icon:ClearAllPoints()
					if i == 1 then
						icon:SetPoint(d3..d1, p, d3..d1)
					elseif ccol == 1 then
						icon:SetPoint(d3, ptimers[i - cols], d4, 0, (1 + vspacing) * vdir)
					else
						icon:SetPoint(d1, ptimers[i - 1], d2, (2 + spacing) * hdir, 0)
					end
					icon:Show()
				end
			end
		else  -- TBLR, TBRL, BTLR, BTRL
			for i, icon in ipairs(ptimers) do
				local ccol = ceil(i / rows)
				if ccol > cols then
					icon:Hide()
				else
					icon:ClearAllPoints()
					local crow = i % rows
					crow = (crow == 0 and rows) or crow
					if i == 1 then
						icon:SetPoint(d1..d3, p, d1..d3)
					elseif crow == 1 then
						icon:SetPoint(d3, ptimers[i - rows], d4, (2 + spacing) * hdir, 0)
					else
						icon:SetPoint(d1, ptimers[i - 1], d2, 0, (1 + vspacing) * vdir)
					end
					icon:Show()
				end
			end
		end
	end
	local function TimerOnUpdate(this, a1)
		this.nextupdate = this.nextupdate - a1
		if this.nextupdate > 0 then return end
		this.nextupdate = this.throt
		
		local remain = this.endtime - GetTime()
		this.bar:SetValue(remain * this.duration, this.bvalue)
		if remain < 60 then
			if remain > 10 then
				this.time:SetFormattedText("%d", remain)
			elseif remain > 0.8 then
				this.nextupdate = 0.03
				this.time:SetFormattedText("%0.1f", remain)
			elseif remain > -0.2 then
				this.nextupdate = 0.03
				this:SetAlpha(remain + 0.2)
				this.time:SetFormattedText("%0.1f", remain < 0 and 0 or remain)
			else
				local p = this:GetParent()
				StopTimer(this.name, p)
				SortTimerBars(p, p.timers)
				return
			end
		elseif remain < 600 then
			this.time:SetFormattedText("%d:%02d", remain * stamin, remain % 60)
		elseif remain < 3600 then
			this.time:SetFormattedText("%dm", 1 + remain * stamin)
		else
			this.time:SetFormattedText("%0.1fh", remain * stahr)
		end
	end
	local function GetTimer(name, p, db)
		local timer
		for k, v in ipairs(p.timers) do
			if v.name == name then
				timer = v
			end
		end
		if not timer then
			timer = tremove(bars)  -- attempt to get unused timer
			if not timer then  -- create new timer
				timer = CreateFrame("Frame", nil, p)
				timer:SetScript("OnUpdate", TimerOnUpdate)
				timer:SetScript("OnHide", TimerOnHide)
				timer:Hide()
			
				timer.icon = timer:CreateTexture(nil, "BORDER")
				timer.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
				
				local bb = timer:CreateTexture(nil, "BACKGROUND")
				bb:SetVertexColor(0, 0, 0, 0.4)
				timer.backbar = bb
				
				timer.bar = timer:CreateTexture(nil, "BORDER")
				
				local spark = timer:CreateTexture(nil, "ARTWORK")
				spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark")
				spark:SetBlendMode("ADD")
				spark:SetAlpha(0.8)
				spark:SetWidth(10)
				timer.spark = spark
				
				timer.ctext = timer:CreateFontString(nil, "OVERLAY")
				timer.ctext:SetPoint("TOPLEFT", bb, "TOPLEFT", 1, 0)
				timer.ctext:SetPoint("BOTTOMRIGHT", bb, "BOTTOMRIGHT", -1, 0)
				
				timer.time = timer:CreateFontString(nil, "OVERLAY")
				timer.time:SetPoint("TOPLEFT", bb, "TOPLEFT", 1, 0)
				timer.time:SetPoint("BOTTOMRIGHT", bb, "BOTTOMRIGHT", -1, 0)
			end
			tinsert(p.timers, timer)
		end

		if timer.p ~= p or timer.pdb ~= p.dbupdate then  -- only update layout when necessary
			timer.p, timer.pdb = p, p.dbupdate
			
			local icon, bar, backbar, spark, ctext, ttext = timer.icon, timer.bar, timer.backbar, timer.spark, timer.ctext, timer.time
			local w, h = db.w, db.h
			local sparkc = db.sparkcolor or Stuf.whitecolor
			local fontc = db.fontcolor or Stuf.whitecolor
			local font = Stuf:GetMedia("font", db.font)
			local fontsize = db.fontsize or (h - 2)
			fontsize = fontsize < 2 and 2 or fontsize
			
			timer:SetWidth(w)
			timer:SetHeight(h)
			if db.framelevel then
				timer:SetFrameLevel(db.framelevel)
			end
		
			timer.bvalue = w - h
			icon:SetWidth(h)
			icon:SetHeight(h)
			bar:SetHeight(h)
			spark:SetHeight(h * 2.3)
			spark:SetVertexColor(sparkc.r, sparkc.g, sparkc.b, sparkc.a)
			bar:SetTexture(Stuf.statusbar)
			backbar:SetTexture(Stuf.statusbar)
			ctext:SetFont(font, fontsize - 1)
			ctext:SetTextColor(fontc.r, fontc.g, fontc.b, fontc.a)
			ttext:SetFont(font, fontsize)
			ttext:SetTextColor(fontc.r, fontc.g, fontc.b, fontc.a)
			
			if db.reverse then
				if timer.reversed ~= 1 then
					ClearAndSetPoint(backbar, "TOPLEFT", timer, "TOPLEFT", 0, 0, "BOTTOMRIGHT", icon, "BOTTOMLEFT", 0, 0)
					ClearAndSetPoint(icon, "RIGHT", timer, "RIGHT")
					ClearAndSetPoint(bar, "RIGHT", backbar, "RIGHT")
					spark:SetPoint("CENTER", bar, "LEFT")
					ctext:SetJustifyH("RIGHT")
					ttext:SetJustifyH("LEFT")
					Stuf:GetTexCoordOptions(false, "hflip", true, nil, bar)
					timer.reversed = 1
				end
			elseif timer.reversed ~= 0 then
				ClearAndSetPoint(backbar, "TOPLEFT", icon, "TOPRIGHT", 0, 0, "BOTTOMRIGHT", timer, "BOTTOMRIGHT", 0, 0)
				ClearAndSetPoint(icon, "LEFT", timer, "LEFT")
				ClearAndSetPoint(bar, "LEFT", backbar, "LEFT")
				spark:SetPoint("CENTER", bar, "RIGHT")
				ctext:SetJustifyH("LEFT")
				ttext:SetJustifyH("RIGHT")
				Stuf:GetTexCoordOptions(false, "normal", false, nil, bar)
				timer.reversed = 0
			end
		end
		return timer
	end
	function StartTimer(name, duration, endtime, icon, color, count, p, spellname)
		local f = GetTimer(name, p, p.db)
		f.name = name
		f:SetParent(p)
		f.endtime = endtime
		f.duration = 1 / duration
		f.throt = (duration < 300 and 0.1) or (duration < 600 and 0.25) or 0.5
		f.nextupdate = 0
		f.ctext:SetFormattedText("%s%s", (count and count > 0 and (count.." ")) or "", p.db.showspellname and spellname or "")
		f.icon:SetTexture(icon)
		f.bar:SetVertexColor(color.r, color.g, color.b, color.a or 0.9)
		f:SetAlpha(1)
		f:Show()
	end
	function StopTimer(name, parent)
		for k, v in ipairs(parent.timers) do
			if v.name == name then
				v.name = ""
				v:Hide()
				tinsert(bars, tremove(parent.timers, k))
				break
			end
		end
	end
end

local function AuraTimeTextOnUpdate(this, a1)
	this.nextupdate = (this.nextupdate or 0) - a1
	if this.nextupdate > 0 then return end
	
	local remain = this.endtime - GetTime()
	if remain < 60 then
		if remain > 10 then
			this.ttext:SetFormattedText("%d", remain)
			this.nextupdate = remain - floor(remain)
		elseif remain > 0 then
			this.ttext:SetFormattedText("%0.1f", remain)
			this.nextupdate = 0.08
		else
			this.ttext:SetText("")
			this:SetScript("OnUpdate", nil)
		end
		if this.id and GameTooltip:IsOwned(this) then
			this:GetScript("OnEnter")(this)
		end
	elseif remain < 3600 then
		this.ttext:SetFormattedText("%dm", 1 + remain * stamin)
		this.nextupdate = remain % 60
	else
		this.ttext:SetFormattedText("%0.1fh", remain * stahr)
		this.nextupdate = remain % 3600
	end
end
local function StartIconTimer(this, duration, endtime, mine)
	if this.ttext then
		if (mine or not this.ttextonlymine) and endtime and endtime > 0 then
			this.endtime = endtime
			this.nextupdate = 0
			this:SetScript("OnUpdate", AuraTimeTextOnUpdate)
		else
			this.ttext:SetText("")
			this:SetScript("OnUpdate", nil)
		end
	end
	if this.showpie then
		if (mine or this.showpie ~= 1) and endtime and endtime > 0 then
			this.pie:SetCooldown(endtime - duration, duration)
			this.pie:Show()
		else
			this.pie:Hide()
		end
	end
end

local UpdateAura
do 	-- Aura handlers --------------------------------------------------------------------------------------------------
	local function getoffset(t, xy, dir)  -- gets precalculated offset if certain aura icons are shown
		local offset = 0
		for index, frame in ipairs(t) do
			if frame:IsShown() then
				offset = (frame[xy] or 0) + (1 * dir)
			else
				break
			end
		end
		return offset
	end
	local function ApplyPush(buffgroup, debuffgroup, auratimers)
		local x, y = 0, 0

		local bdb = buffgroup and buffgroup.db
		local ddb = debuffgroup and debuffgroup.db
		local adb = auratimers and auratimers.db

		if buffgroup and not buffgroup.disablepush then
			if (bdb.push == "v" or bdb.push == "h") then
				local castbar = buffgroup:GetParent().castbar
				if castbar and castbar:IsShown() then
					if bdb.push == "v" then
						y = (castbar.db.h + 2) * buffgroup.vdir
					else
						x = (castbar.db.w + 2) * buffgroup.hdir
					end
				end
			end
			buffgroup:SetPoint("TOPLEFT", bdb.x + x, bdb.y + y)
		end
		if debuffgroup then
			if buffgroup and (ddb.push == "h" or ddb.push == "v") then
				if ddb.push == "h" then
					x = x + getoffset(buffgroup.firstrow, "xoff", buffgroup.hdir)
				else
					y = y + getoffset(buffgroup.firstcol, "yoff", buffgroup.vdir)
				end
				debuffgroup:SetPoint("TOPLEFT", ddb.x + x, ddb.y + y)
			else
				debuffgroup:SetPoint("TOPLEFT", ddb.x, ddb.y)
			end
		end
		if auratimers then
			if debuffgroup and (adb.push == "h" or adb.push == "v") then
				if adb.push == "h" then
					x = x + getoffset(debuffgroup.firstrow, "xoff", debuffgroup.hdir)
				else
					y = y + getoffset(debuffgroup.firstcol, "yoff", debuffgroup.vdir)
				end
				auratimers:SetPoint("TOPLEFT", adb.x + x, adb.y + y)
			else
				auratimers:SetPoint("TOPLEFT", adb.x, adb.y)
			end
		end
	end
	Stuf.ApplyPush = ApplyPush
	local ShowSetupMode
	local temp, debuffconfig, rtime, showpet = { }, nil, nil, nil
	local UnitBuff, UnitDebuff, UnitIsUnit = UnitBuff, UnitDebuff, UnitIsUnit
	function UpdateAura(unit, uf, _, _, _, config)  -- updates all elements dealing with buffs/debuffs
		uf = uf or su[unit]
		if not uf or uf.hidden then return end
		
		local allow, clr, bfilter, dfilter, onlymineb, onlymined = true, nil, nil, nil, nil, nil
		local name, rank, icon, count, atype, duration, endtime, ismine, isstealable
		local cache = uf.cache
		
		local dispellicon, buffgroup, debuffgroup, auratimers = uf.dispellicon, uf.buffgroup, uf.debuffgroup, uf.auratimers
		if not dispellicon or dispellicon.hidden then
			dispellicon = nil
		end
		if not buffgroup or buffgroup.hidden then
			buffgroup = nil
		elseif cache.assist then
			bfilter = buffgroup.filter
			onlymineb = buffgroup.db.onlymine
		end
		if not debuffgroup or debuffgroup.hidden then
			debuffgroup = nil
		elseif cache.assist then
			dfilter = debuffgroup.filter
		else
			onlymined = debuffgroup.db.onlymine
		end
		if not auratimers or auratimers.hidden then
			auratimers = nil
		else
			for k, v in ipairs(auratimers.timers) do
				temp[v.name] = true
			end
			showpet = auratimers.db.showpet or Stuf.vunit == "pet"
		end
		if config then
			ShowSetupMode = ShowSetupMode or function(dispellicon, buffgroup, debuffgroup, auratimers)  -- shows buffs/debuffs in config mode
				local currenttime = GetTime()
				if dispellicon then
					local dc = dbgaura.Curse
					dispellicon.texture:SetTexture("Interface\\Icons\\Spell_ChargeNegative")
					dispellicon.ctext:SetText(2)
					dispellicon:SetBackdropColor(dc.r, dc.g, dc.b)
					dispellicon:Show()
				end
				rtime = rtime or { 0, 65, 610, 3000, }
				for i = 1, 32, 1 do
					local icon = buffgroup and buffgroup[i]
					if icon then
						icon.texture:SetTexture("Interface\\Icons\\Spell_ChargePositive")
						icon.ctext:SetText(i)
						local clr = i > 3 and dbgaura.Buff or dbgaura.MyBuff
						icon:SetBackdropColor(clr.r, clr.g, clr.b)
						icon:Show()
						local duration = rtime[math.random(1,4)] or 0
						StartIconTimer(icon, duration, currenttime + duration, true)
					end
					if auratimers and i <= auratimers.db.count/2 then
						local duration = 30 * i
						StartTimer("b"..i, duration, currenttime + duration, "Interface\\Icons\\Spell_ChargePositive", dbgaura.MyBuff, i*2 - 2, auratimers, "Spell Name Spell Name Spell Name")
						temp["b"..i] = nil
					end
				end
				debuffconfig = debuffconfig or { "none", "Magic", "Curse", "Poison", "Disease", }
				for i = 1, 40, 1 do
					local icon = debuffgroup and debuffgroup[i]
					local clr = dbgaura[debuffconfig[(i % 5) + 1] or "none"]
					if icon then
						icon.texture:SetTexture("Interface\\Icons\\Spell_ChargeNegative")
						icon.ctext:SetText(i)
						icon:SetBackdropColor(clr.r, clr.g, clr.b)
						icon:Show()
						local duration = rtime[math.random(1,4)] or 0
						StartIconTimer(icon, duration, currenttime + duration, true)
					end
					if auratimers and i <= auratimers.db.count/2 then
						local duration = 30.5 * i
						StartTimer("d"..i, duration, currenttime + duration, "Interface\\Icons\\Spell_ChargeNegative", clr, i*2-1, auratimers, "Spell Name Spell Name Spell Name")
						temp["d"..i] = nil
					end
				end
				if auratimers then  -- stop timers that shouldn't exist anymore
					for k, v in pairs(temp) do
						StopTimer(k, auratimers)
					end
					SortTimerBars(auratimers, auratimers.timers)
				end
				ApplyPush(buffgroup, debuffgroup, auratimers)
			end
			return ShowSetupMode(dispellicon, buffgroup, debuffgroup, auratimers)
		end
		if dispellicon then
			if cache.assist then
				if iswarlock then
					if UnitCreatureFamily("pet") == "Felhunter" then
						for i = 1, 40, 1 do
							name, _, icon, count, atype = UnitDebuff(unit, i)
							if not name or atype == "Magic" then
								break
							else
								name = nil
							end
						end
					end
				else
					name, _, icon, count, atype = UnitDebuff(unit, 1, "RAID")
				end
				if name then
					local dc = dbgaura[atype or "none"] or dbgaura.none
					dispellicon.texture:SetTexture(icon)
					dispellicon.ctext:SetText((count > 1 and count) or "")
					dispellicon:SetBackdropColor(dc.r, dc.g, dc.b)
					dispellicon:Show()
				else
					dispellicon:Hide()
				end
			else
				dispellicon:Hide()
			end
		end
		
		for i = 1, 32, 1 do  -- update buffgroup
			if allow then  -- prevents calling UnitBuff when it's useless
				name, rank, icon, count, atype, duration, endtime, ismine, isstealable = UnitBuff(unit, i, bfilter)
				ismine = ismine == "player" or ismine == "vehicle" or (showpet and ismine == "pet")
				allow = name and (not onlymineb or ismine)
			end
			
			local b = buffgroup and buffgroup[i]
			if b then
				if allow then
					b.texture:SetTexture(icon)
					b.ctext:SetText((count and count > 1 and count) or "")
					if cache.attackable and (isstealable or (atype == "Magic" and notmage)) then
						clr = dbgaura.Magic
					elseif ismine then
						clr = dbgaura.MyBuff
					else
						clr = dbgaura.Buff
					end
					b:SetBackdropColor(clr.r, clr.g, clr.b)
					b:Show()
					StartIconTimer(b, duration, endtime, ismine)
				else
					b:Hide()
				end
			elseif not allow then
				break
			end
			if auratimers and ismine and endtime and endtime > 0 then
				StartTimer("b"..i, duration, endtime, icon, dbgaura.MyBuff, count, auratimers, name)
				temp["b"..i] = nil
			end
		end
		
		allow = true
		for i = 1, 40, 1 do  -- update debuffgroup
			if allow then  -- prevents calling UnitDebuff when it's useless
				name, rank, icon, count, atype, duration, endtime, ismine, isstealable = UnitDebuff(unit, i, dfilter)
				ismine = ismine == "player" or ismine == "vehicle" or (showpet and ismine == "pet")
				clr = dbgaura[atype or "none"] or dbgaura.none
				allow = name and (not onlymined or ismine)
			end
			
			local b = debuffgroup and debuffgroup[i]
			if b then
				if allow then
					b:SetBackdropColor(clr.r, clr.g, clr.b)
					b.texture:SetTexture(icon)
					b.ctext:SetText((count and count > 1 and count) or "")
					b:Show()
					StartIconTimer(b, duration, endtime, ismine)
				else
					b:Hide()
				end
			elseif not allow then
				break
			end
			if auratimers and ismine and endtime and endtime > 0 then
				StartTimer("d"..i, duration, endtime, icon, clr, count, auratimers, name)
				temp["d"..i] = nil
			end
		end
		if auratimers then  -- stop timers that shouldn't exist anymore
			for k, v in pairs(temp) do
				StopTimer(k, auratimers)
				temp[k] = nil
			end
			SortTimerBars(auratimers, auratimers.timers)
		end
		ApplyPush(buffgroup, debuffgroup, auratimers)
	end
end


do  -- Aura Icons -----------------------------------------------------------------------------------------------------
	local lbf = LibStub("LibButtonFacade", true)
	local GetWeaponEnchantInfo = GetWeaponEnchantInfo
	local function BuffOnEnter(this) -- buff tooltip
		GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT", 8, -16)
		GameTooltip:SetUnitBuff(this:GetParent().unit, this.id, this:GetParent().filter)
	end
	local function DebuffOnEnter(this) -- debuff tooltip
		GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT", 8, -16)
		GameTooltip:SetUnitDebuff(this:GetParent().unit, this.id, this:GetParent().filter)
	end
	local function TempOnEnter(this)
		GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT", 8, -16)
		GameTooltip:SetInventoryItem("player", (this.id == 1 and 16) or 17)
	end
	local function TempEnchantOnUpdate(unit, uf, _, _, _, config)
		uf = uf or su[unit]
		local f = uf and not uf.hidden and uf.tempenchant
		if not f then return end
		
		f.tog = (f.tog or 0) + 1
		if f.tog < 4 then return end
		f.tog = 0
		
		local ctime = GetTime()
		local i1, i2 = f[1], f[2]
		
		local e1, e1duration, e1count, e2, e2duration, e2count
		if config then
			e1, e1duration, e1count, e2, e2duration, e2count = true, 60000, 0, true, 600000, 23
		else
			e1, e1duration, e1count, e2, e2duration, e2count = GetWeaponEnchantInfo()
		end
		if e1 then
			i1.texture:SetTexture(GetInventoryItemTexture("player", 16))
			i1.ctext:SetText(e1count > 1 and e1count or "")
			i1:Show()
			local duration = e1duration * 0.001
			StartIconTimer(i1, duration, ctime + duration, true)
		else
			i1:Hide()
		end
		if e2 then
			i2.texture:SetTexture(GetInventoryItemTexture("player", 17))
			i2.endtime = ctime + e2duration * 0.001
			i2.ctext:SetText(e2count > 1 and e2count or "")
			i2:Show()
			local duration = e2duration * 0.001
			StartIconTimer(i2, duration, ctime + duration, true)
		else
			i2:Hide()
		end
	end
	local function CreateAuraGroup(unit, uf, name, db, _, config)
		local f = uf[name]
		local isplayer, isbuff, isdebuff, istemp = (unit == "player"), (name == "buffgroup"), (name == "debuffgroup"), (name == "tempenchant")
		if db.hide then
			if f then
				f.hidden = true
				f:Hide()
				if isplayer then
					if istemp then
						TemporaryEnchantFrame:Show()
					elseif isbuff then
						BuffFrame:Show()
						BuffFrame:RegisterEvent("UNIT_AURA")
					end
				end
				UpdateAura(unit, uf, nil, nil, nil, config)
			end
			return
		end
		if not f then
			f = CreateFrame("Frame", nil, uf)
			f:SetSize(2, 2)
			f.unit = unit
			f.db = db
			f.firstcol, f.firstrow = { }, { }
			
			uf[name] = f
			uf.refreshfuncs["auras"] = UpdateAura
			if istemp then
				uf.refreshfuncs.tempenchant = TempEnchantOnUpdate
				uf.metroelements.tempenchant = TempEnchantOnUpdate
			elseif isplayer and isbuff then
				f.secure = CreateFrame("Frame", nil, f, "SecureAuraHeaderTemplate")
				f.secure:SetSize(2, 2)
				f.secure:SetAttribute("unit", "player")
				f.secure:SetAttribute("filter", "HELPFUL")
				f.secure:SetAttribute("separateOwn", "1")
				f.secure:SetAttribute("template", "StufBuffTemplate")
				f.secure:SetAttribute("minWidth", 1)
				f.secure:SetAttribute("minHeight", 1)
				f.secure:SetAttribute("sortMethod", "INDEX")
				f.secure:SetAttribute("sortDir", "+")
				f.disablepush = true
			end
		else
			f.hidden = nil
			f:Show()
		end
		
		local x, y, w, h = db.x, db.y, db.w, db.h
		local cfontsize = db.counttfontsize or db.fontsize or (w < 2 and 1) or floor(w * 0.6 + 0.5)
		local cfont = Stuf:GetMedia("font", db.counttfont)
		local cfontflags = db.counttfontflags ~= "None" and db.counttfontflags
		local cc = db.counttfontcolor or Stuf.whitecolor
		local sc = dbg.shadowcolor
		local d1, d2, d3, d4, hdir, vdir = GrowthBreakdown(db.growth)
		local hfirst = (d1 == "LEFT" or d1 == "RIGHT")
		local spacing, vspacing = db.spacing or 0, db.vspacing or 0
		local cols, rows = db.cols or (hfirst and 2) or 1, db.rows or (hfirst and 1) or 2
		f.hdir, f.vdir = hdir, vdir
		
		f.filter = db.curable and "RAID"
		f:SetPoint("TOPLEFT", uf, "TOPLEFT", x, y)
		if db.framelevel then
			f:SetFrameLevel(db.framelevel)
		end
		if istemp then
			TemporaryEnchantFrame:Hide()
		elseif isplayer and isbuff then
			BuffFrame:Hide()
			BuffFrame:UnregisterEvent("UNIT_AURA")
			f.secure:ClearAllPoints()
			if hfirst then
				f.secure:SetAttribute("point", d3..d1)
				f.secure:SetAttribute("xOffset", (spacing + w) * hdir)
				f.secure:SetAttribute("yOffset", 0)
				f.secure:SetAttribute("wrapAfter", cols)
				f.secure:SetAttribute("wrapXOffset", 0)
				f.secure:SetAttribute("wrapYOffset", (vspacing + h) * vdir)
				f.secure:SetAttribute("maxWraps", rows)
				f.secure:SetPoint(d3..d1, f, "TOPLEFT")
			else
				f.secure:SetAttribute("point", d1..d3)
				f.secure:SetAttribute("xOffset", 0)
				f.secure:SetAttribute("yOffset", (vspacing + h) * vdir)
				f.secure:SetAttribute("wrapAfter", rows)
				f.secure:SetAttribute("wrapXOffset", (spacing + w) * hdir)
				f.secure:SetAttribute("wrapYOffset", 0)
				f.secure:SetAttribute("maxWraps", cols)
				f.secure:SetPoint(d1..d3, f, "TOPLEFT")
			end
			f.secure:SetFrameLevel(f:GetFrameLevel() + 20)
			f.secure:Show()
		end
		f:Show()

		local offset1, offset2, uselbf
		if dbg.aurastyle == 2 then
			offset1 = w * 0.05 + 0.5
			offset1 = (offset1 > 3 and 3) or (offset1 < 1 and 1) or floor(offset1)
			offset2 = offset1
			backdrop.bgFile = "Interface\\AddOns\\Stuf\\media\\aura2.tga"
		elseif dbg.aurastyle == 3 and lbf then
			uselbf = true
			offset1, offset2 = 0, 0
			backdrop.bgFile = ""
			if not Stuf.lbfgroup then
				Stuf.lbfgroup = lbf:Group("Stuf")
				lbf:RegisterSkinCallback("Stuf", function(_, SkinID, Gloss, Backdrop, Group, Button, Colors)
					dbg.lbfskin, dbg.lbfgloss, dbg.lbfbackdrop = SkinID, Gloss, Backdrop
				end)
			end
			Stuf.lbfgroup:Skin(dbg.lbfskin, dbg.lbfgloss or true, dbg.lbfbackdrop or true)
		else
			offset1 = (w < 6 and 1) or floor(w * 0.1 + 0.5)
			offset2 = floor(w * 0.05)
			backdrop.bgFile = "Interface\\AddOns\\Stuf\\media\\aura1.tga"
		end

		wipe(f.firstcol)
		wipe(f.firstrow)
		
		local tfc, tfont, tfontsize, tfontflags
		if not uf.ismetro and db.timefontcolor and db.timefontcolor.a > 0.1 then
			tfc = db.timefontcolor
			tfont = Stuf:GetMedia("font", db.timefont)
			tfontsize = db.timefontsize or cfontsize
			tfontflags = db.timefontflags ~= "None" and db.timefontflags
		end
		for i = 1, db.count or 2, 1 do
			local icon = f[i]
			if not icon then
				icon = CreateFrame("Button", lbf and format("Stuf.units.%s.%s.a%d", unit, name, i) or nil, f)
				icon:Hide()
				icon.overlay = CreateFrame("Frame", nil, icon)
				icon.overlay:SetFrameLevel(4)
				icon.texture = icon.overlay:CreateTexture(nil, "BORDER")
				icon.texture:SetTexCoord(0.07, 0.93, 0.07, 0.93)
				icon.ctext = icon.overlay:CreateFontString(nil, "ARTWORK")
			
				icon.id = i
				icon:SetScript("OnEnter", (isdebuff and DebuffOnEnter) or (istemp and TempOnEnter) or BuffOnEnter)
				icon:SetScript("OnLeave", Stuf.GameTooltipOnLeave)

				f[i] = icon
			end
			if db.showpie then
				icon.pie = icon.pie or CreateFrame("Cooldown", nil, icon, "StufAuraCooldown")
				icon.pie:SetReverse(true)
				icon.pie.noCooldownCount = db.hidecc
				icon.showpie = (db.pieonlymine and 1) or true
			elseif icon.pie then
				icon.pie:Hide()
				icon.showpie = nil
			end
			icon:SetWidth(w)
			icon:SetHeight(h)
			icon:EnableMouse(not db.nomouse)
			icon.SetAlpha = Stuf.SetAlpha
			icon:SetAlpha(1)
			icon:SetBackdrop(backdrop)
			if uselbf then
				icon.Icon = icon.texture
				icon.Cooldown = icon.pie
				icon.Border = icon.Border or icon:CreateTexture(icon:GetName().."Border", "OVERLAY")
				Stuf.lbfgroup:AddButton(icon, icon)
				Stuf.lbfsetcolor = Stuf.lbfsetcolor or function(icon, r, g, b, a)
					icon.Border:SetVertexColor(r, g, b, a)
				end
				icon.SetBackdropColor = Stuf.lbfsetcolor
			else
				if icon.Icon then
					icon.SetBackdropColor = Stuf.SetBackdropColor
				end
				icon.texture:SetPoint("TOPRIGHT", icon, "TOPRIGHT", -offset1, -offset1)
				icon.texture:SetPoint("BOTTOMLEFT", icon, "BOTTOMLEFT", offset2, offset2)
			end
			if istemp then
				icon:SetBackdropColor(0.5, 0, 1)
			end
			
			icon.ctext:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", db.counttx or (-offset2 + 1), db.countty or offset2)
			Stuf:UpdateTextLook( icon.ctext, nil, cfont, cfontsize, cfontflags,
			                     db.counttjustifyH or "RIGHT", db.counttjustifyV or "BOTTOM", cc, db.counttshadowx or -1, db.counttshadowy or 1 )
			if tfc then
				local ttext = icon.ttext
				if not ttext then
					ttext = icon.overlay:CreateFontString(nil, "ARTWORK")
					icon.ttext = ttext
				end
				icon.ttextonlymine = db.timeonlymine
				icon.talpha = db.alpha or 1
				ttext:SetWidth(db.timew or (w + 4))
				ttext:SetHeight(db.timeh or cfontsize)
				ttext:SetPoint("TOPLEFT", icon, "TOPLEFT", db.timex or -2, db.timey or -(h - cfontsize))
				Stuf:UpdateTextLook( ttext, nil, tfont, tfontsize, tfontflags,
				                     db.timejustifyH, db.timejustifyV, tfc, db.timeshadowx, db.timeshadowy )
				ttext:Show()
			elseif icon.ttext then
				icon.ttextonlymine = nil
				icon.ttext:Hide()
			end
			
			icon:ClearAllPoints()
			icon.xoff, icon.yoff = nil, nil
			if hfirst then  -- LRTB, LRBT, RLTB, RLBT
				local crow = ceil(i / cols)
				if crow > rows then break end
				local ccol = i % cols
				ccol = (ccol == 0 and cols) or ccol
				if i == 1 then  -- first row, first col
					icon:SetPoint(d3..d1, f, d3..d1)
					icon.xoff = (w + 4 + spacing) * hdir
					icon.yoff = (h + 2 + vspacing) * vdir
					tinsert(f.firstrow, icon)
					tinsert(f.firstcol, icon)
				elseif ccol == 1 then  -- start of a new row (first column)
					icon:SetPoint(d3, f[i - cols], d4, 0, vspacing * vdir)
					icon.yoff = ((h * crow) + (vspacing * crow) + 2) * vdir
					tinsert(f.firstcol, icon)
				else
					icon:SetPoint(d1, f[i - 1], d2, spacing * hdir, 0)
					if crow == 1 then
						icon.xoff = ((w * ccol) + (spacing * ccol) + 4) * hdir
						tinsert(f.firstrow, icon)
					end
				end
			else  -- TBLR, TBRL, BTLR, BTRL
				local ccol = ceil(i / rows)
				if ccol > cols then break end
				local crow = i % rows
				crow = (crow == 0 and rows) or crow
				if i == 1 then  -- first row, first col
					icon:SetPoint(d1..d3, f, d1..d3)
					icon.xoff = (w + 4 + spacing) * hdir 
					icon.yoff = (h + 2 + vspacing) * vdir
					tinsert(f.firstrow, icon)
					tinsert(f.firstcol, icon)
				elseif crow == 1 then  -- start of a new column (first row)
					icon:SetPoint(d3, f[i - rows], d4, spacing * hdir, 0)
					icon.xoff = ((w * ccol) + (spacing * ccol) + 4) * hdir
					tinsert(f.firstrow, icon)
				else
					icon:SetPoint(d1, f[i - 1], d2, 0, vspacing * vdir)
					if ccol == 1 then
						icon.yoff = ((h * crow) + (vspacing * crow) + 2) * vdir
						tinsert(f.firstcol, icon)
					end
				end
			end
		end  -- for
		for i = db.count + 1, 80, 1 do  -- hide icons in case db.count is less than before
			if not f[i] then break end
			f[i]:EnableMouse(false)
			f[i]:SetAlpha(0)
			f[i].SetAlpha = Stuf.nofunc
		end
		if Stuf.inworld then
			UpdateAura(unit, uf, nil, nil, nil, config)
		end
	end
	Stuf:AddBuilder("buffgroup", CreateAuraGroup)
	Stuf:AddBuilder("debuffgroup", CreateAuraGroup)
	Stuf:AddBuilder("tempenchant", CreateAuraGroup)
	Stuf:AddEvent("UNIT_AURA", UpdateAura)
	
	Stuf:AddBuilder("auratimers", function(unit, uf, name, db, _, config)
		local f = uf[name]
		if db.hide then
			if f then
				f.hidden = true
				f:Hide()
			end
			return
		end
		if not f then
			f = CreateFrame("Frame", nil, uf)
			f:SetWidth(2)
			f:SetHeight(2)
			f.unit = unit
			f.db = db
			f.timers = { }
			uf[name] = f
			uf.refreshfuncs["auras"] = UpdateAura
		else
			f.hidden = nil
			f:Show()
		end

		f.d1, f.d2, f.d3, f.d4, f.hdir, f.vdir = GrowthBreakdown(db.growth)
		f:SetPoint("TOPLEFT", uf, "TOPLEFT", db.x, db.y)
		
		f.dbupdate = (f.dbupdate or 0) + 1
		if Stuf.inworld then
			UpdateAura(unit, uf, nil, nil, nil, config)
		end
	end)
end


do  -- Dispell Icon ---------------------------------------------------------------------------------------------------
	local function DispellOnUpdate(this, a1)  -- flash effect
		local dir = this.dir or 0.5
		local alp = (this.alp or 0.5) + a1 * dir
		if (dir == 0.5 and alp > 0.7) or (dir == -0.5 and alp < 0.3) then
			this.dir = dir * -1
		end
		this.alp = alp
		this:SetAlpha(alp)  -- flash between 0.3 and 0.7 alpha
	end
	Stuf:AddBuilder("dispellicon", function(unit, uf, name, db, _, config)
		if not Stuf.supportspell and not iswarlock then return end
		local f = uf[name]
		if db.hide then
			if f then
				f.hidden = true
				f:Hide()
			end
			return
		end
		if not f then
			f = CreateFrame("Frame", nil, uf)
			f.texture = f:CreateTexture(nil, "BORDER")
			f.texture:SetTexCoord(0.07, 0.93, 0.07, 0.93)
			f.ctext = f:CreateFontString(nil, "ARTWORK")
			f:SetScript("OnUpdate", DispellOnUpdate)
			
			f.db = db
			f:Hide()
			uf.refreshfuncs["auras"] = UpdateAura
			uf[name] = f
		else
			f.hidden = nil
		end
		f:SetFrameLevel(db.framelevel or 3)
		f:SetWidth(db.w)
		f:SetHeight(db.h)
		f:SetPoint("TOPLEFT", uf, "TOPLEFT", db.x, db.y)
		f:SetAlpha(db.alpha or 1)
		f:SetBackdrop(backdrop)
		
		local offset1, offset2 = 0, floor(db.h * 0.05 + 0.5)
		if dbg.aurastyle == 2 then
			backdrop.bgFile = "Interface\\AddOns\\Stuf\\media\\aura2.tga"
			offset1 = offset2
		else
			backdrop.bgFile = "Interface\\AddOns\\Stuf\\media\\aura1.tga"
			offset1 = floor(db.h * 0.1 + 0.5)
		end
		f:SetBackdrop(backdrop)
		f.texture:SetPoint("TOPRIGHT", f, "TOPRIGHT", -offset1, -offset1)
		f.texture:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", offset2, offset2)
		f.ctext:SetPoint("TOPLEFT", f, "TOPLEFT", db.counttx or 0, db.countty or 0)
		f.ctext:SetWidth(db.counttw or db.w)
		f.ctext:SetHeight(db.countth or db.h)

		Stuf:UpdateTextLook( f.ctext, db.counttfont, nil, db.counttfontsize or (db.h * 0.5), db.counttfontflags,
		                     db.counttjustifyH or "RIGHT", db.counttjustifyV or "BOTTOM", 
							 db.counttfontcolor or Stuf.whitecolor, db.counttshadowx or -1, db.counttshadowy or 1 )
		if Stuf.inworld then
			UpdateAura(unit, uf, nil, nil, nil, config)
		end
	end)
end
