-- X-Perl UnitFrames
-- Author: Zek <Boodhoof-EU>
-- License: GNU GPL v3, 29 June 2007 (see LICENSE.txt)

local conf
XPerl_RequestConfig(function(New) conf = New end, "$Revision: 188 $")
local percD	= "%d"..PERCENT_SYMBOL

-- Some local copies for speed
local strsub = strsub
local format = format
local max = math.max
local min = math.min
local IsItemInRange = IsItemInRange
local IsSpellInRange = IsSpellInRange
local UnitBuff = UnitBuff
local UnitDebuff = UnitDebuff
local UnitCanAssist = UnitCanAssist
local UnitCanAttack = UnitCanAttack
local UnitClass = UnitClass
local UnitExists = UnitExists
local UnitFactionGroup = UnitFactionGroup
local UnitHealth = UnitHealth
local UnitHealthMax = UnitHealthMax
local UnitIsEnemy = UnitIsEnemy
local UnitIsPVP = UnitIsPVP
local UnitIsTapped = UnitIsTapped
local UnitIsVisible = UnitIsVisible
local UnitPlayerControlled = UnitPlayerControlled
local UnitReaction = UnitReaction
local GetNumRaidMembers = GetNumRaidMembers
local GetNumPartyMembers = GetNumPartyMembers
local GetRaidRosterInfo = GetRaidRosterInfo

local largeNumDiv = XPERL_LOC_LARGENUMDIV
local largeNumTag = XPERL_LOC_LARGENUMTAG

------------------------------------------------------------------------------
-- Re-usable tables
local FreeTables = setmetatable({}, {__mode = "k"})
local requested, freed = 0, 0
function XPerl_GetReusableTable(...)
	requested = requested + 1
	for t in pairs(FreeTables) do
		FreeTables[t] = nil
		for i = 1, select("#", ...) do
			t[i] = select(i, ...)
		end
		return t
	end
	return {...}
end
function XPerl_FreeTable(t, deep)
	if (t) then
		if (type(t) ~= "table") then
			error("Usage: XPerl_FreeTable([table])")
		end
		if (FreeTables[t]) then
			error("XPerl_FreeTable - Table already freed")
		end

		freed = freed + 1

		FreeTables[t] = true
		for k,v in pairs(t) do
			if (deep and type(v) == "table") then
				XPerl_FreeTable(v, true)
			end
			t[k] = nil
		end
		t[''] = 0
		t[''] = nil
	end
end
function XPerl_TableStats()
	return requested, freed
end

local new, del = XPerl_GetReusableTable, XPerl_FreeTable

-- meta table for string based colours. Allows for other mods changing class colours and things all working
XPerlColourTable = setmetatable({},{
	__index = function(self, class)
		local c = RAID_CLASS_COLORS[strupper(class or "")]
		if (c) then
			c = format("|c00%02X%02X%02X", 255 * c.r, 255 * c.g, 255 * c.b)
		else
			c = "|c00808080"
		end
		self[class] = c
		return c
	end
})

-- Dummy Tutorial function if it's not enabled
if (not XPerl_Tutorial) then
	function XPerl_Tutorial()
	end
end

if not UIDropDownMenu_CreateInfo then
	local UIDropDownMenu_ButtonInfo
	function UIDropDownMenu_CreateInfo()
		-- Reuse the same table to prevent memory churn
		local info = UIDropDownMenu_ButtonInfo
		for k,v in pairs(info) do
			info[k] = nil
		end
		return UIDropDownMenu_ButtonInfo
	end
end

--XPerl_Percent = setmetatable({},
--	{__mode = "kv",
--	__index = function(self, i)
--		if (type(i) == "number" and i >= 0) then
--			self[i] = format(percD, i)
--			return self[i]
--		end
--		return ""
--	end
--	})
--local xpPercent = XPerl_Percent

-- XPerl_ShowMessage
-- debug function
function XPerl_ShowMessage(cMsg)
	local str = "|c00FF7F00"..event.."|r"
	local theEnd
	if (arg1 and (arg1 == "player" or arg1 == "pet" or arg1 == "target" or arg1 =="focus" or strfind(arg1, "^raid") or strfind(arg1, "^party"))) then
		local class = select(2, UnitClass(arg1))
		str = str..", |c00808080"..tostring(arg1).."("..XPerlColourTable[class]..UnitName(arg1).."|c00808080)|r"
		theEnd = 2
	else
		theEnd = 1
	end

	local tail, doit = ""
	for i = 9,theEnd,-1 do
		local v = getglobal("arg"..i)
		if (v or doit) then
			if (tail ~= "") then
				tail = tostring(v)..", "..tail
			else
				tail = tostring(v)
			end
			doit = true
		end
	end
	if (tail ~= "") then
		str = str..", "..tail
	end

	if (cMsg) then
		str = cMsg.." - "..str
	end

	local cf = ChatFrame2
	if (not cf:IsVisible()) then
		cf = DEFAULT_CHAT_FRAME
	end
	if (this and this.GetName and this:GetName()) then
		cf:AddMessage("|c00007F7F"..this:GetName().."|r - "..str)
	else
		cf:AddMessage(str)
	end
end

XPerl_AnchorList = {"TOP", "LEFT", "BOTTOM", "RIGHT"}

-- FindABandage()
local function FindABandage()
	local bandages = {
		[21991] = true, -- Heavy Netherweave Bandage
		[21990] = true, -- Netherweave Bandage
		[14530] = true, -- Heavy Runecloth Bandage
		[14529] = true, -- Runecloth Bandage
		[8545] = true, -- Heavy Mageweave Bandage
		[8544] = true, -- Mageweave Bandage
		[6451] = true, -- Heavy Silk Bandage
		[6450] = true, -- Silk Bandage
		[3531] = true, -- Heavy Wool Bandage
		[3530] = true, -- Wool Bandage
		[2581] = true, -- Heavy Linen Bandage
		[1251] = true, -- Linen Bandage
	}

	for k,v in pairs(bandages) do
		if (GetItemCount(k) > 0) then
			return GetItemInfo(k)
		end
	end
end

local playerClass

-- We have a dummy do-nothing function here for classes that don't have range checking
-- The do-something function is setup after variables_loaded and we work out spell to use just once
function XPerl_UpdateSpellRange()
	return
end

-- DoRangeCheck
local function DoRangeCheck(unit, opt)

	local range
	if (opt.PlusHealth) then
		local hp, hpMax = UnitHealth(unit), UnitHealthMax(unit)
		if (hp / hpMax > opt.HealthLowPoint) then
			range = 0
		end
	end

	if (range == 0 and opt.PlusDebuff) then
		local name = UnitDebuff(unit, 1, 1)
		if (not name) then
			range = 0
		else
			range = nil		-- Override's the health check, because there's a debuff on unit
		end
	end

	if (not range) then
		if (opt.interact) then
			range = CheckInteractDistance(unit, opt.interact)
			-- 1 = Inspect = 30 yards
			-- 2 = Trade = 11.11 yards
			-- 3 = Duel = 10 yards
			-- 4 = Follow = 28 yards
		elseif (opt.spell) then
			range = IsSpellInRange(opt.spell, unit)
		elseif (opt.item) then
			range = IsItemInRange(opt.item, unit)
		else
			range = 1
		end
	end

	if (range ~= 1) then
		return opt.FadeAmount
	end
end

-- XPerl_UpdateSpellRange(self)
function XPerl_UpdateSpellRange2(self, overrideUnit, isRaidFrame)
	local unit
	if (overrideUnit) then
		unit = overrideUnit
	else
		unit = self:GetAttribute("unit")
		if (not unit) then
			unit = SecureButton_GetUnit(self)
		end
	end
	if (unit) then
		local rf = conf.rangeFinder
		local mainA, nameA, statsA		-- Receives main, name and stats alpha levels

		if (rf.enabled and (isRaidFrame or not conf.rangeFinder.raidOnly)) then
			if (not UnitIsVisible(unit)) then
				if (rf.Main.enabled) then
					mainA = conf.transparency.frame * rf.Main.FadeAmount
				else
					if (rf.NameFrame.enabled) then
						nameA = rf.NameFrame.FadeAmount
					end
					if (rf.StatsFrame.enabled) then
						statsA = rf.StatsFrame.FadeAmount
					end
				end

			--elseif (XPerl_Highlight:HasEffect(UnitName(unit), "AGGRO")) then
			--	mainA = conf.transparency.frame

			elseif (UnitCanAssist("player", unit)) then
				if (rf.Main.enabled) then
					mainA = DoRangeCheck(unit, rf.Main)
					if (mainA) then
						mainA = mainA * conf.transparency.frame
					end
				end

				if (rf.NameFrame.enabled) then
					-- check for same item/spell. Saves doing the check multiple times
					if (rf.Main.enabled and (rf.Main.spell == rf.NameFrame.spell) and (rf.Main.item == rf.NameFrame.item) and (rf.Main.PlusHealth == rf.NameFrame.PlusHealth)) then
						if (mainA) then
							nameA = rf.NameFrame.FadeAmount
						end
					else
						nameA = DoRangeCheck(unit, rf.NameFrame)
						if (not nameA and mainA) then
							-- In range, but 'Whole' frame is out of range, so we need to override the fade for name
							nameA = 1
						end
					end
				end
				if (rf.StatsFrame.enabled) then
					-- check for same item/spell. Saves doing the check multiple times
					if (rf.Main.enabled and (rf.Main.spell == rf.StatsFrame.spell) and (rf.Main.item == rf.StatsFrame.item) and (rf.Main.PlusHealth == rf.StatsFrame.PlusHealth)) then
						if (mainA) then
							statsA = rf.StatsFrame.FadeAmount
						end
					else
						statsA = DoRangeCheck(unit, rf.StatsFrame)
						if (not statsA and mainA) then
							-- In range, but 'Whole' frame is out of range, so we need to override the fade for stats
							statsA = 1
						end
					end
				end
			end
		end

		local forcedMainA
		if (not mainA) then
			if (UnitIsConnected(unit)) then
				mainA = conf.transparency.frame
				forcedMainA = true
			else
				mainA = conf.transparency.frame * rf.Main.FadeAmount
				nameA, statsA = mainA
				forcedMainA = true
			end
		end

--if (self == XPerl_Target) then
--   ChatFrame7:AddMessage(format("%4s %4s %4s", tostring(mainA), tostring(nameA), tostring(statsA)))
--end

		self:SetAlpha(mainA)
		if (self.nameFrame) then
			if (nameA or forcedMainA) then
				self.nameFrame:SetAlpha(nameA or mainA)
			else
				self.nameFrame:SetAlpha(1)
			end
		end
		if (self.statsFrame) then
			if (nameA or forcedMainA) then
				self.statsFrame:SetAlpha(statsA or mainA)
			else
				self.statsFrame:SetAlpha(1)
			end
		end
	end
end

-- XPerl_StartupSpellRange()
function XPerl_StartupSpellRange()
	local _
	_, playerClass = UnitClass("player")

	if (not XPerl_DefaultRangeSpells.ANY) then
		XPerl_DefaultRangeSpells.ANY = {}
	end

	local b = FindABandage()
	if (b) then
		XPerl_DefaultRangeSpells.ANY.item = b
	end

	local rf = conf.rangeFinder

	local function Setup1(self)
		if ((type(self.spell) ~= "string" and type(self.item) ~= "string") or
						(self.item and not GetItemInfo(self.item))) then

			self.spell = XPerl_DefaultRangeSpells[playerClass] and XPerl_DefaultRangeSpells[playerClass].spell
			if (type(self.spell) ~= "string") then
				self.item = (XPerl_DefaultRangeSpells.ANY and XPerl_DefaultRangeSpells.ANY.item) or ""
			end
		end

		if (not self.FadeAmount) then
			self.FadeAmount = 0.3
		end
		if (not self.HealthLowPoint) then
			self.HealthLowPoint = 0.7
		end
	end

	Setup1(rf.Main)
	Setup1(rf.NameFrame)
	Setup1(rf.StatsFrame)

	--if (rangeCheckSpell) then
		-- Put the real work function in place
		XPerl_UpdateSpellRange = XPerl_UpdateSpellRange2
	--else
	--	XPerl_UpdateSpellRange = function() end
	--end
end

XPerl_RegisterOptionChanger(XPerl_StartupSpellRange)

-- XPerl_StatsFrame_SetGrey
local function XPerl_StatsFrame_SetGrey(self, r, g, b)
	if (not r) then
		r, g, b = 0.5, 0.5, 0.5
	end

	self.healthBar:SetStatusBarColor(r, g, b, 1)
	self.healthBar.bg:SetVertexColor(r, g, b, 0.5)
	if (self.manaBar) then
		self.manaBar:SetStatusBarColor(r, g, b, 1)
		self.manaBar.bg:SetVertexColor(r, g, b, 0.5)
	end
	self.greyMana = true
end

-- XPerl_SetChildMembers - Recursive
-- This iterates a frame's child frames and regions and assigns member variables
-- based on the sub-set part of the child's name compared to the parent frame name
function XPerl_SetChildMembers(self)
	local n = self:GetName()
	if (n) then
		local match = "^"..n.."(.+)$"

		local function AddList(list)
			for k,v in pairs(list) do
				local t = v:GetName()
				if (t) then
					local found = strmatch(t, match)
					if (found) then
						--if (self[found] == v) then
						--	break		-- Already done
						--end
						self[found] = v
					end
				end
			end
		end

		AddList({self:GetRegions()})

		local c = {self:GetChildren()}
		AddList(c, true)

		for k,v in pairs(c) do
			if (v:GetName()) then
				XPerl_SetChildMembers(v)
			end
			v:SetScript("OnLoad", nil)
		end

		self:SetScript("OnLoad", nil)
	end
end

-- XPerl_AllBarTextures
function XPerl_AllBarTextures(short)

	local list = {
		{"Blizzard", "Interface\\TargetingFrame\\UI-StatusBar"},
		{"Perl", "Interface\\Addons\\XPerl\\Images\\XPerl_StatusBar"},
	}

	for i = 1,9 do
		tinsert(list, {"X-Perl "..i, "Interface\\Addons\\XPerl\\Images\\XPerl_StatusBar"..(i + 1)})
	end

	if (not short and LibStub) then
		local media = LibStub("LibSharedMedia-3.0", true)
		if (not media) then
			media = LibStub("SharedMedia-2.0", true)
			if (not media) then
				media = LibStub("SharedMedia-1.0", true)
			end
		end
		if (media) then
			local smlBars = media:List("statusbar")
			for k,v in pairs(smlBars) do
				tinsert(list, {v, media:Fetch("statusbar", v)})
			end
		end
	end

	return list
end

-- XPerl_GetBarTexture
function XPerl_GetBarTexture()
	return (conf.bar.texture and conf.bar.texture[2]) or "Interface\\TargetingFrame\\UI-StatusBar"
end

-- XPerl_StatsFrame_Setup
function XPerl_StatsFrame_Setup(self)
	XPerl_SetChildMembers(self)
	self.SetGrey = XPerl_StatsFrame_SetGrey
end

-- XPerl_GetClassColour
local defaultColour = {r = 0.5, g = 0.5, b = 1}
function XPerl_GetClassColour(class)
	return (class and RAID_CLASS_COLORS[class]) or defaultColour
end

-- UnitIsFeignDeath - chinese might not have patch yet.
if (type(UnitIsFeignDeath) ~= "function") then
	function UnitIsFeignDeath(unit)
		for i = 1,20 do
			local name, rank, buff = UnitBuff(unit, i)

			if (buff) then
				if (strfind(strlower(buff), "feigndeath")) then
					return true
				end
			else
				break
			end
		end
	end
end

---------------------------------
--Loading Function             --
---------------------------------

-- XPerl_BlizzFrameDisable
function XPerl_BlizzFrameDisable(self)
	UnregisterUnitWatch(self)		-- Should stop Archaeologist from re-showing target frame
	self:UnregisterAllEvents()
	self:Hide()
	-- Make it so it won't be visible, even if shown by another mod
	self:ClearAllPoints()
	self:SetPoint("BOTTOMLEFT", UIParent, "TOPLEFT", -400, 500)

	local healthBar = getglobal(self:GetName().."HealthBar")
	if (healthBar) then
		healthBar:UnregisterAllEvents()
	end

	local manaBar = getglobal(self:GetName().."ManaBar")
	if (manaBar) then
		manaBar:UnregisterAllEvents()
	end
end

-- smoothColor
local function smoothColor(percentage)
	local r, g, b
	if (percentage < 0.5) then
		r = 1
		g = min(1, max(0, 2*percentage))
		b = 0
	else
		g = 1
		r = min(1, max(0, 2*(1 - percentage)))
		b = 0
	end

	return r, g, b
end

---------------------------------
--Smooth Health Bar Color      --
---------------------------------
function XPerl_SetSmoothBarColor(self, percentage)
	if (self) then
		local r, g, b
		if (conf.colour.classic) then
			r, g, b = smoothColor(percentage)
		else
			local c = conf.colour.bar
			r = min(1, max(0, c.healthEmpty.r + ((c.healthFull.r - c.healthEmpty.r) * percentage)))
			g = min(1, max(0, c.healthEmpty.g + ((c.healthFull.g - c.healthEmpty.g) * percentage)))
			b = min(1, max(0, c.healthEmpty.b + ((c.healthFull.b - c.healthEmpty.b) * percentage)))
		end

		self:SetStatusBarColor(r, g, b)

		if (self.bg) then
			self.bg:SetVertexColor(r, g, b, 0.25)
		end
	end
end

local barColours
function XPerl_ResetBarColourCache()
	barColours = setmetatable({}, {
		__index = function(self, k)
			local c = RAID_CLASS_COLORS[k]
			if (c) then
				if (not conf.colour.classbarBright) then
					conf.colour.classbarBright = 1
				end
				self[k] = {	r = max(0, min(1, c.r * conf.colour.classbarBright)),
						g = max(0, min(1, c.g * conf.colour.classbarBright)),
						b = max(0, min(1, c.b * conf.colour.classbarBright))}
				return self[k]
			end
		end
	})
end
XPerl_ResetBarColourCache()

-- XPerl_ColourHealthBar
function XPerl_ColourHealthBar(self, healthPct, partyid)
	if (not partyid) then
		partyid = self.partyid
	end
	local bar = self.statsFrame.healthBar
	if (conf.colour.classbar and UnitIsPlayer(partyid)) then
		local class = select(2, UnitClass(partyid))
		if (class) then
			local c = barColours[class]	-- RAID_CLASS_COLORS[class]
			if (c) then
				bar:SetStatusBarColor(c.r, c.g, c.b)
				if (bar.bg) then
					bar.bg:SetVertexColor(c.r, c.g, c.b, 0.25)
				end
				return
			end
		end
	end

	XPerl_SetSmoothBarColor(bar, healthPct)
end

-- XPerl_SetHealthBar
function XPerl_SetHealthBar(self, hp, Max)
	local bar = self.statsFrame.healthBar

	bar:SetMinMaxValues(0, Max)
	if (conf.bar.inverse) then
		bar:SetValue(Max - hp)
	else
		bar:SetValue(hp)
	end

	local percent = max(0, hp / Max)		-- Fix for SetFormatted text displaying -21474... for 0 / 0
	XPerl_ColourHealthBar(self, percent)

	if (bar.percent) then
		if (self.conf.healerMode and self.conf.healerMode.enable and self.conf.healerMode.type == 2) then
			bar.percent:SetText(hp - Max)
		else
			bar.percent:SetFormattedText(percD or "%d%%", (percent + 0.005) * 100)
		end
	end

	if (bar.text) then
		local hbt = bar.text
		if (self.conf.healerMode.enable and self.conf.healerMode.type ~= 2) then
			if (self.conf.healerMode.type == 1) then
				if (Max > 100000) then
					hbt:SetFormattedText("%d%s/%d%s", (hp - Max) / largeNumDiv, largeNumTag, Max / largeNumDiv, largeNumTag)
				else
					hbt:SetFormattedText("%d/%d", hp - Max, Max)
				end
			else
				if (Max > 100000) then
					hbt:SetFormattedText("%d%s", (hp - Max) / largeNumDiv, largeNumTag)
				else
					hbt:SetFormattedText("%d", hp - Max)
				end
			end
		else
			if (Max > 100000) then
				hbt:SetFormattedText("%.1f%s/%.1f%s", hp / largeNumDiv, largeNumTag, Max / largeNumDiv, largeNumTag)
			else
				hbt:SetFormattedText("%d/%d", hp, Max)
			end
		end
	end
	XPerl_SetExpectedHealth(self)
end

---------------------------------
--Class Icon Location Functions--
---------------------------------
--local ClassPos = {
--	WARRIOR	= {0,    0.25,    0,	0.25},
--	MAGE	= {0.25, 0.5,     0,	0.25},
--	ROGUE	= {0.5,  0.75,    0,	0.25},
--	DRUID	= {0.75, 1,       0,	0.25},
--	HUNTER	= {0,    0.25,    0.25,	0.5},
--	SHAMAN	= {0.25, 0.5,     0.25,	0.5},
--	PRIEST	= {0.5,  0.75,    0.25,	0.5},
--	WARLOCK	= {0.75, 1,       0.25,	0.5},
--	PALADIN	= {0,    0.25,    0.5,	0.75},
--	none	= {0.25, 0.5, 0.5, 0.75},
--}
--function XPerl_ClassPos(class)
--	return unpack(ClassPos[class] or ClassPos.none)
--end

local ClassPos = CLASS_BUTTONS
function XPerl_ClassPos(class)
	local b = ClassPos[class]		-- Now using the Blizzard supplied from FrameXML/WorldStateFrame.lua
	if (b) then
		return unpack(b)
	end
	return 0.25, 0.5, 0.5, 0.75
end

-- XPerl_Toggle
function XPerl_Toggle()
	if (XPerlLocked == 1) then
		XPerl_UnlockFrames()
	else
		XPerl_LockFrames()
	end
end

-- XPerl_UnlockFrames
function XPerl_UnlockFrames()
	XPerl_LoadOptions()

	XPerlLocked = 0

	if (XPerl_Party_Virtual) then
		XPerl_Party_Virtual(true)
	end

	if (XPerl_Player_Pet_Virtual) then
		XPerl_Player_Pet_Virtual(true)
	end

	if (XPerl_AggroAnchor) then
		XPerl_AggroAnchor:Enable()
	end

	if (XPerl_Options) then
		XPerl_Tutorial(7)
		XPerl_Options:Show()
		XPerl_Options:SetAlpha(0)
		XPerl_Options.Fading = "in"
	end

	if (XPerl_RaidTitles) then
		XPerl_RaidTitles()
		if (XPerl_RaidPets_Titles) then
			XPerl_RaidPets_Titles()
		end
	end
end

-- XPerl_LockFrames
function XPerl_LockFrames()
	XPerlLocked = 1
	if (XPerl_Options) then
		XPerl_Options.Fading = "out"
	end

	if (XPerl_Party_Virtual) then
		XPerl_Party_Virtual()
	end

	if (XPerl_Player_Pet_Virtual) then
		XPerl_Player_Pet_Virtual()
	end

	if (XPerl_AggroAnchor) then
		XPerl_AggroAnchor:Disable()
	end

	if (XPerl_RaidTitles) then
		XPerl_RaidTitles()
		if (XPerl_RaidPets_Titles) then
			XPerl_RaidPets_Titles()
		end
	end

	XPerl_OptionActions()
end

-- Minimap Icon
function XPerl_MinimapButton_OnClick(self, button)
	if (button == "LeftButton") then
		XPerl_Toggle()
	elseif (button == "RightButton") then
		XPerl_MinimapMenu(self)
	end
end

-- XPerl_MinimapMenu_OnLoad
function XPerl_MinimapMenu_OnLoad(self)
	self.displayMode = "MENU"
	UIDropDownMenu_Initialize(self, XPerl_MinimapMenu_Initialize)
end

-- XPerl_MinimapMenu_Initialize
function XPerl_MinimapMenu_Initialize(level)
	local info

	if (level == 2) then
		if (UIDROPDOWNMENU_MENU_VALUE == "raidbuffs") then
			info = UIDropDownMenu_CreateInfo()
			info.isTitle = 1
			info.text = XPERL_MINIMENU_RAIDBUFF
			UIDropDownMenu_AddButton(info, level)

			info = UIDropDownMenu_CreateInfo()
			info.notCheckable = 1
			info.text = BINDING_NAME_TOGGLEBUFFCASTABLE
			info.func = function() XPerl_ToggleRaidBuffs(1) end
			UIDropDownMenu_AddButton(info, level)

			info = UIDropDownMenu_CreateInfo()
			info.notCheckable = 1
			info.text = BINDING_NAME_TOGGLEBUFFTYPE
			info.func = XPerl_ToggleRaidBuffs
			UIDropDownMenu_AddButton(info, level)

		elseif (UIDROPDOWNMENU_MENU_VALUE == "raidsort") then

			info = UIDropDownMenu_CreateInfo()
			info.isTitle = 1
			info.text = XPERL_MINIMENU_RAIDSORT
			UIDropDownMenu_AddButton(info, level)

			info = UIDropDownMenu_CreateInfo()
			info.checked = XPerlDB.raid.sortByClass == 1
			info.text = XPERL_MINIMENU_RAIDSORT_GROUP
			info.func = function() XPerl_ToggleRaidSort(1) end
			UIDropDownMenu_AddButton(info, level)

			info = UIDropDownMenu_CreateInfo()
			info.checked = XPerlDB.raid.sortByClass == nil
			info.text = XPERL_MINIMENU_RAIDSORT_CLASS
			info.func = function() XPerl_ToggleRaidSort(0) end
			UIDropDownMenu_AddButton(info, level)
		end
		return
	end

	info = UIDropDownMenu_CreateInfo()
	info.isTitle = 1
	info.text = XPerl_ProductName
	UIDropDownMenu_AddButton(info)

	info = UIDropDownMenu_CreateInfo()
	info.notCheckable = 1
	info.func = XPerl_Toggle
	info.text = XPERL_MINIMENU_OPTIONS
	UIDropDownMenu_AddButton(info)

	if (XPerl_ToggleRaidBuffs) then
		info = UIDropDownMenu_CreateInfo()
		info.notCheckable = 1
		info.text = XPERL_MINIMENU_RAIDBUFF
		info.hasArrow = 1
		info.value = "raidbuffs"
		UIDropDownMenu_AddButton(info)
	end

	if (XPerl_ToggleRaidSort) then
		info = UIDropDownMenu_CreateInfo()
		info.notCheckable = 1
		info.text = XPERL_MINIMENU_RAIDSORT
		info.hasArrow = 1
		info.value = "raidsort"
		UIDropDownMenu_AddButton(info)
	end

	if (IsAddOnLoaded("XPerl_RaidHelper")) then
		if (XPerl_Assists_Frame and not XPerl_Assists_Frame:IsShown()) then
			info = UIDropDownMenu_CreateInfo()
			info.notCheckable = 1
			info.text = XPERL_MINIMENU_ASSIST
			info.func = function()
					XPerlConfigHelper.AssistsFrame = 1
					XPerlConfigHelper.TargettingFrame = 1
					XPerl_SetFrameSides()
				end
			UIDropDownMenu_AddButton(info)
		end
	end

	if (IsAddOnLoaded("XPerl_RaidMonitor")) then
		if (XPerl_RaidMonitor_Frame and not XPerl_RaidMonitor_Frame:IsShown()) then
			info = UIDropDownMenu_CreateInfo()
			info.notCheckable = 1
			info.text = XPERL_MINIMENU_CASTMON
			info.func = function()
					XPerlRaidMonConfig.enabled = 1
					XPerl_RaidMonitor_Frame:SetFrameSizes()
				end
			UIDropDownMenu_AddButton(info)
		end
	end

	if (IsAddOnLoaded("XPerl_RaidAdmin")) then
		if (XPerl_AdminFrame and not XPerl_AdminFrame:IsShown()) then
			info = UIDropDownMenu_CreateInfo()
			info.notCheckable = 1
			info.text = XPERL_MINIMENU_RAIDAD
			info.func = function() XPerl_AdminFrame:Show() end
			UIDropDownMenu_AddButton(info)
		end

		if (XPerl_Check and not XPerl_Check:IsShown()) then
			info = UIDropDownMenu_CreateInfo()
			info.notCheckable = 1
			info.text = XPERL_MINIMENU_ITEMCHK
			info.func = function() XPerl_Check:Show() end
			UIDropDownMenu_AddButton(info)
		end

		if (XPerl_RosterText and not XPerl_RosterText:IsShown()) then
			info = UIDropDownMenu_CreateInfo()
			info.notCheckable = 1
			info.text = XPERL_MINIMENU_ROSTERTEXT
			info.func = function() XPerl_RosterText:Show() end
			UIDropDownMenu_AddButton(info)
		end
	end
end

-- XPerl_MinimapMenu
function XPerl_MinimapMenu(self)
	if (not XPerl_Minimap_Dropdown) then
		CreateFrame("Frame", "XPerl_Minimap_Dropdown")
		XPerl_MinimapMenu_OnLoad(XPerl_Minimap_Dropdown)
	end

	ToggleDropDownMenu(1, nil, XPerl_Minimap_Dropdown, self:GetName(), 0, 0)
end

local xpModList = {"XPerl", "XPerl_Player", "XPerl_PlayerBuffs", "XPerl_PlayerPet", "XPerl_Target", "XPerl_TargetTarget", "XPerl_Party", "XPerl_PartyPet", "XPerl_ArcaneBar", "XPerl_HUD", "XPerl_RaidFrames", "XPerl_RaidHelper", "XPerl_RaidAdmin", "XPerl_RaidMonitor", "XPerl_RaidPets", "XPerl_GrimReaper"}
local xpStartupMemory = {}

-- XPerl_MinimapButton_Init
function XPerl_MinimapButton_Init(self)
	self.time = 0
	collectgarbage()
	UpdateAddOnMemoryUsage()
	local totalKB = 0
	for k,v in pairs(xpModList) do
		local usedKB = GetAddOnMemoryUsage(v)
		if ((usedKB or 0) > 0) then
			xpStartupMemory[v] = usedKB
		end
	end

	XPerl_MinimapButton_UpdatePosition(self)

	if (conf.minimap.enable) then
		self:Show()
	else
		self:Hide()
	end

	self.UpdateTooltip = XPerl_MinimapButton_OnEnter

	XPerl_MinimapButton_Init = nil
end

-- XPerl_MinimapButton_UpdatePosition
function XPerl_MinimapButton_UpdatePosition(self)
	if (not conf.minimap.radius) then
		conf.minimap.radius = 78
	end
	self:SetPoint(
		"TOPLEFT",
		"Minimap",
		"TOPLEFT",
		54 - (conf.minimap.radius * cos(conf.minimap.pos)),
		(conf.minimap.radius * sin(conf.minimap.pos)) - 55
	)
end

-- XPerl_MinimapButton_Dragging
function XPerl_MinimapButton_Dragging(self, elapsed)
	local xpos,ypos = GetCursorPosition()
	local xmin,ymin = Minimap:GetLeft(), Minimap:GetBottom()

	xpos = xmin-xpos/UIParent:GetScale()+70
	ypos = ypos/UIParent:GetScale()-ymin-70

	if (IsAltKeyDown()) then
		local radius = (xpos^2 + ypos^2)^0.5
		if (radius < 78) then
			radius = 78
		end
		if (radius > 148) then
			radius = 148
		end
		conf.minimap.radius = radius
		end

	local angle = math.deg(math.atan2(ypos,xpos))
	if (angle < 0) then
		angle = angle + 360
	end
	conf.minimap.pos = angle

	XPerl_MinimapButton_UpdatePosition(self)
end

-- DiffColour(diff, val)
local function DiffColour(val)
	local r, g, b, offset
	offset = max(0, min(0.5, 0.5 * min(1, val)))
	if (val < 0) then
		r = 0.5 + offset
		g = 0.5 - offset
		b = r
	else
		r = 0.5 + offset
		g = 0.5 - offset
		b = g
	end
	return format("|c00%02X%02X%02X", 255 * r, 255 * g, 255 * b)
end

-- XPerl_MinimapButton_OnEnter
function XPerl_MinimapButton_OnEnter(self)
	if (self.dragging) then
		return
	end

	XPerl_Tutorial(1)			-- Minimap Icon/Options

	GameTooltip:SetOwner(self, "ANCHOR_LEFT")
	GameTooltip:SetText(XPerl_Version.." "..XPerl_GetRevision(), 1, 1, 1)
	GameTooltip:AddLine(XPERL_MINIMAP_HELP1)
	GameTooltip:AddLine(XPERL_MINIMAP_HELP2)
	if (UpdateAddOnMemoryUsage) then
		if (IsAltKeyDown()) then
			GameTooltip:AddLine(XPERL_MINIMAP_HELP6)
		elseif (not IsShiftKeyDown()) then
			GameTooltip:AddLine(XPERL_MINIMAP_HELP5)
		end
	end

	if (GetRealNumRaidMembers) then
		if (GetNumRaidMembers() > 0 and GetRealNumRaidMembers() > 0) then
			if (select(2, IsInInstance()) == "pvp") then
				GameTooltip:AddLine(format(XPERL_MINIMAP_HELP3, GetRealNumRaidMembers(), GetRealNumPartyMembers()))

				if (IsRealPartyLeader()) then
					GameTooltip:AddLine(XPERL_MINIMAP_HELP4)
				end
			end
		end
	end

	if (UpdateAddOnMemoryUsage and IsAltKeyDown()) then
		local showDiff = IsShiftKeyDown()

		local allAddonsCPU = 0
		for i = 1,GetNumAddOns() do
			allAddonsCPU = allAddonsCPU + GetAddOnCPUUsage(i)
		end

		-- Show X-Perl memory usage
		UpdateAddOnMemoryUsage()
		UpdateAddOnCPUUsage()
		local totalKB, totalCPU, diffKB, diff = 0, 0, 0
		local cpuText = ""
		for k,v in pairs(xpModList) do
			local usedKB = GetAddOnMemoryUsage(v)
			local usedCPU = GetAddOnCPUUsage(v)
			if ((usedKB or 0) > 0) then
				totalKB = totalKB + usedKB
				totalCPU = totalCPU + usedCPU

				if (allAddonsCPU > 0) then
					cpuText = format(" |c008080FF%.2f%%|r", 100 * (usedCPU / allAddonsCPU))
				end

				if (showDiff) then
					diff = usedKB - xpStartupMemory[v]
					diffKB = diffKB + diff
					GameTooltip:AddDoubleLine(format(" %s", v), format("%.1fkb (%s%.1fkb|r)%s", usedKB, DiffColour(diff / 500), diff, cpuText), 1, 1, 0.5, 1, 1, 1)
				else
					GameTooltip:AddDoubleLine(format(" %s", v), format("%.1fkb%s", usedKB, cpuText), 1, 1, 0.5, 1, 1, 1)
				end
			end
		end

		if (showDiff) then
			local color = DiffColour(diffKB / 2000)

			GameTooltip:AddDoubleLine("Total", format("%.1fkb (%s%.1fkb|r)", totalKB, color, diffKB), 1, 1, 1, 1, 1, 1)
		else
			GameTooltip:AddDoubleLine("Total", format("%.1fkb", totalKB), 1, 1, 1, 1, 1, 1)
		end

		local usedKB = GetAddOnMemoryUsage("XPerl_Options")
		if ((usedKB or 0) > 0) then
			GameTooltip:AddDoubleLine(" XPerl_Options", format("%.1fkb", usedKB), 0.5, 0.5, 0.5, 0.5, 0.5, 0.5)
		end

		if (totalCPU > 0) then
			GameTooltip:AddDoubleLine(" XPerl CPU Usage Comparison", format("%.2f%%", 100 * (totalCPU / allAddonsCPU)), 0.5, 0.5, 1, 0.5, 0.5, 1)
		end
	end

	GameTooltip:Show()
	GameTooltip.updateTooltip = 1
end


-- XPerl_SetManaBarType
local ManaColours = {		-- "mana", "rage", "focus", "energy", "runes"}
	[SPELL_POWER_MANA] = "mana",
	[SPELL_POWER_RAGE] = "rage",
	[SPELL_POWER_FOCUS] = "focus",
	[SPELL_POWER_ENERGY] = "energy",
	[SPELL_POWER_HAPPINESS] = "happiness",
	[SPELL_POWER_RUNES] = "runes",
}
function XPerl_SetManaBarType(self)
	local m = self.statsFrame.manaBar
	if (m and not self.statsFrame.greyMana) then
		local unit = SecureButton_GetUnit(self)
		if (unit) then
			local p = UnitPowerType(unit)
			if (p) then
				local c = conf.colour.bar[ManaColours[p]]
				if (c) then
					m:SetStatusBarColor(c.r, c.g, c.b, 1)
					m.bg:SetVertexColor(c.r, c.g, c.b, 0.25)
				end
			end
		end
	end
end

-- XPerl_TooltipModiferPressed()
function XPerl_TooltipModiferPressed(buffs)
	local mod, ic
	if (buffs) then
		if (not conf.tooltip.enableBuffs) then
			return
		end
		mod = conf.tooltip.buffModifier
		ic = conf.tooltip.buffHideInCombat
	else
		if (not conf.tooltip.enable) then
			return
		end
		mod = conf.tooltip.modifier
		ic = conf.tooltip.hideInCombat
	end

	if (mod == "alt") then
		mod = IsAltKeyDown()
	elseif (mod == "shift") then
		mod = IsShiftKeyDown()
	elseif (mod == "control") then
		mod = IsControlKeyDown()
	else
		mod = true
	end

	mod = mod and (not ic or not InCombatLockdown())

	return mod
end

-- XPerl_PlayerTip
function XPerl_PlayerTip(self, unitid)
	if (not unitid) then
		unitid = SecureButton_GetUnit(self)
	end

	if (not unitid or XPerlLocked == 0) then
		return
	end

	if (not XPerl_TooltipModiferPressed()) then
		return
	end

	if (SpellIsTargeting()) then
		if (SpellCanTargetUnit(unitid)) then
			SetCursor("CAST_CURSOR")
		else
			SetCursor("CAST_ERROR_CURSOR")
		end
	end

	GameTooltip_SetDefaultAnchor(GameTooltip, this)
	GameTooltip:SetUnit(unitid)

	if (XPerl_RaidTipExtra) then
		XPerl_RaidTipExtra(unitid)
	end

--[[	if (conf.tooltip.xperlInfo and XPerl_GetUsage and not strmatch(unitid, "pet")) then
		local xpUsage = XPerl_GetUsage(UnitName(unitid), unitid)
		if (xpUsage) then
			local xp, any = "|cFFD00000X-Perl|r "
			if (xpUsage.version) then
				xp = xp..xpUsage.version
			else
				xp = xp..XPerl_VersionNumber
			end
			any = true

			if (xpUsage.mods and IsShiftKeyDown()) then
				local modList = XPerl_DecodeModuleList(xpUsage.mods)
				if (modList) then
					xp = xp.." : |c00909090"..modList
				end
			end
			if (any) then
				GameTooltip:AddLine(xp, 1, 1, 1, 1)
				GameTooltip:Show()
			end
		end
	end ]]

	XPerl_Highlight:TooltipInfo(UnitName(unitid))
end

-- XPerl_PlayerTipHide
function XPerl_PlayerTipHide()
	if (conf.tooltip.fading) then
		GameTooltip:FadeOut()
	else
		GameTooltip:Hide()
	end
end

-- XPerl_ToolTip_AddBuffDuration
function XPerl_ToolTip_AddBuffDuration(self, partyid, buffID)
	if (XPerl_Raid_AddBuffDuration) then
		XPerl_Raid_AddBuffDuration(self, partyid, buffID)
	end
end

-- XPerl_ToolTip_AddBuffDuration
function XPerl_ToolTip_AddDebuffDuration(self, partyid, index, cureCast)
	if (XPerl_Raid_AddDebuffDuration) then
		XPerl_Raid_AddDebuffDuration(self, partyid, index, cureCast)
	end
end

-- XPerl_ColourFriendlyUnit
function XPerl_ColourFriendlyUnit(self, partyid)
	local color
	if (UnitCanAttack("player", partyid) and UnitIsEnemy("player", partyid)) then	-- For dueling
		color = conf.colour.reaction.enemy
	else
		if (conf.colour.class) then
			color = XPerl_GetClassColour(select(2, UnitClass(partyid)))
		else
			if (UnitIsPVP(partyid)) then
				color = conf.colour.reaction.friend
			else
				color = conf.colour.reaction.none
			end
		end
	end

	self:SetTextColor(color.r, color.g, color.b, conf.transparency.text)
end

-- XPerl_ReactionColour
function XPerl_ReactionColour(argUnit)
	if (UnitPlayerControlled(argUnit) or not UnitIsVisible(argUnit)) then
		if (UnitFactionGroup("player") == UnitFactionGroup(argUnit)) then
			if (UnitIsEnemy("player", argUnit)) then
				-- Dueling
				return conf.colour.reaction.enemy

			elseif (UnitIsPVP(argUnit)) then
				return conf.colour.reaction.friend
			end
		else
			if (UnitIsPVP(argUnit)) then
				if (UnitIsPVP("player")) then
					return conf.colour.reaction.enemy
				else
					return conf.colour.reaction.neutral
				end
			end
		end
	else
		if (UnitIsTapped(argUnit) and not UnitIsTappedByPlayer(argUnit)) then
			return conf.colour.reaction.tapped
		else
			local reaction = UnitReaction(argUnit, "player")
			if (reaction) then
				if (reaction >= 5) then
					return conf.colour.reaction.friend
				elseif (reaction <= 2) then
					return conf.colour.reaction.enemy
				elseif (reaction == 3) then
					return conf.colour.reaction.unfriendly
				else
					return conf.colour.reaction.neutral
				end
			else
				if (UnitFactionGroup("player") == UnitFactionGroup(argUnit)) then
					return conf.colour.reaction.friend
				elseif (UnitIsEnemy("player", argUnit)) then
					return conf.colour.reaction.enemy
				else
					return conf.colour.reaction.neutral
				end
			end
		end
	end

	return conf.colour.reaction.none
end

-- XPerl_SetUnitNameColor
function XPerl_SetUnitNameColor(self, unit)
	local color
	if (UnitIsPlayer(unit) or not UnitIsVisible(unit)) then			-- Changed UnitPlayerControlled to UnitIsPlayer for 2.3.5
		-- 1.8.3 - Changed to override pvp name colours
		if (conf.colour.class) then
			color = XPerl_GetClassColour(select(2, UnitClass(unit)))
		else
			color = XPerl_ReactionColour(unit)
		end
	else
		if (UnitIsTapped(unit) and not UnitIsTappedByPlayer(unit)) then
			color = conf.colour.reaction.tapped
		else
			color = XPerl_ReactionColour(unit)
		end
	end

	self:SetTextColor(color.r, color.g, color.b, conf.transparency.text)
end

-- XPerl_HasBuff(unit, buffName)
function XPerl_HasBuff(unit, buffName)
	for i = 1,40 do
		local name = UnitBuff(unit, i)
		if (not name) then
			break
		end
		if (name == buffName) then
			return true
		end
	end
end

-- XPerl_CombatFlashSet
function XPerl_CombatFlashSet(self, elapsed, argNew, argGreen)
	if (not conf.combatFlash) then
		self.PlayerFlash = nil
		return
	end

	if (self) then
		if (argNew) then
			self.PlayerFlash = 1.5
			self.PlayerFlashGreen = argGreen
		else
			if (elapsed and self.PlayerFlash) then
				self.PlayerFlash = self.PlayerFlash - elapsed

				if (self.PlayerFlash <= 0) then
					self.PlayerFlash = 0
					self.PlayerFlashGreen = nil
				end
			else
				return
			end
		end

		return true
	end
end

-- XPerl_CombatFlashSetFrames
function XPerl_CombatFlashSetFrames(self)
	if (self.PlayerFlash) then
		local baseColour = self.forcedColour or conf.colour.border

		local r, g, b, a
		if (self.PlayerFlash > 0) then
			local flashOffsetColour = min(self.PlayerFlash,1) / 2
			if (self.PlayerFlashGreen) then
				r = min(1, max(0, baseColour.r - flashOffsetColour))
				g = min(1, max(0, baseColour.g + flashOffsetColour))
			else
				r = min(1, max(0, baseColour.r + flashOffsetColour))
				g = min(1, max(0, baseColour.g - flashOffsetColour))
			end
			b = min(1, max(0, baseColour.b - flashOffsetColour))
			a = min(1, max(0, baseColour.a + flashOffsetColour))
		else
			r, g, b, a = baseColour.r, baseColour.g, baseColour.b, baseColour.a
			self.PlayerFlash = false
		end

		for i, frame in pairs(self.FlashFrames) do
			frame:SetBackdropBorderColor(r, g, b, a)
		end
	end
end

local ArcaneExclusions = XPerl_ArcaneExclusions

local getShow
function XPerl_DebufHighlightInit()
	-- We also re-set the colours here so that we highlight best colour per class
	if (playerClass == "MAGE") then
		getShow = function(Curses)
			local show
			if (not conf.highlightDebuffs.class) then
				show = Curses.Magic or Curses.Curse or Curses.Poison or Curses.Disease
			end
			return Curses.Curse or show
		end

	elseif (playerClass == "DRUID") then
		getShow = function(Curses)
			local show
			if (not conf.highlightDebuffs.class) then
				show = Curses.Magic or Curses.Curse or Curses.Poison or Curses.Disease
			end
			return Curses.Curse or Curses.Poison or show
		end

	elseif (playerClass == "PRIEST") then
		getShow = function(Curses)
			local show
			if (not conf.highlightDebuffs.class) then
				show = Curses.Magic or Curses.Curse or Curses.Poison or Curses.Disease
			end
			return Curses.Magic or Curses.Disease or show
		end

	elseif (playerClass == "WARLOCK") then
		getShow = function(Curses)
			local show
			if (not conf.highlightDebuffs.class) then
				show = Curses.Magic or Curses.Curse or Curses.Poison or Curses.Disease
			end
			return Curses.Magic or show
		end

	elseif (playerClass == "PALADIN") then
		getShow = function(Curses)
			local show
			if (not conf.highlightDebuffs.class) then
				show = Curses.Magic or Curses.Curse or Curses.Poison or Curses.Disease
			end
			return Curses.Magic or Curses.Poison or Curses.Disease or show
		end

	elseif (playerClass == "SHAMAN") then
		getShow = function(Curses)
			local show
			if (not conf.highlightDebuffs.class) then
				show = Curses.Magic or Curses.Curse or Curses.Poison or Curses.Disease
			end
			return Curses.Poison or Curses.Disease or show
		end

	elseif (playerClass == "ROGUE") then
		getShow = function(Curses)
			local show
			if (not conf.highlightDebuffs.class) then
				show = Curses.Magic or Curses.Curse or Curses.Poison or Curses.Disease
			end
			return Curses.Poison or show
		end
	else
		getShow = function(Curses)
			return Curses.Magic or Curses.Curse or Curses.Poison or Curses.Disease
		end
	end

	XPerl_DebufHighlightInit = nil
end

local bgDef = {bgFile = "Interface\\Addons\\XPerl\\Images\\XPerl_FrameBack",
	       edgeFile = "",
	       tile = true, tileSize = 32, edgeSize = 16,
	       insets = { left = 5, right = 5, top = 5, bottom = 5 }
		}
local normalEdge = "Interface\\Tooltips\\UI-Tooltip-Border"
local curseEdge = "Interface\\Addons\\XPerl\\Images\\XPerl_Curse"

-- XPerl_CheckDebuffs
local Curses = setmetatable({}, {__mode = "k"})		-- 2.2.6 - Now re-using static table to save garbage memory creation
function XPerl_CheckDebuffs(self, unit, resetBorders)
	if (not self.FlashFrames) then
		return
	end

	local high = conf.highlightDebuffs.enable or (self == XPerl_Target and conf.target.highlightDebuffs.enable)

	if (resetBorders or not high or not getShow) then
		-- Reset the frame edges back to normal in case they changed options while debuffed.
		self.forcedColour = nil
		bgDef.edgeFile = self.edgeFile or normalEdge
		bgDef.edgeSize = self.edgeSize or 16
		bgDef.insets.left = self.edgeInsets or 5
		bgDef.insets.top = self.edgeInsets or 5
		bgDef.insets.right = self.edgeInsets or 5
		bgDef.insets.bottom = self.edgeInsets or 5
		for i, f in pairs(self.FlashFrames) do
			f:SetBackdrop(bgDef)
			f:SetBackdropColor(conf.colour.frame.r, conf.colour.frame.g, conf.colour.frame.b, conf.colour.frame.a)
			f:SetBackdropBorderColor(conf.colour.border.r, conf.colour.border.g, conf.colour.border.b, conf.colour.border.a)
		end
		return
	end

	if (not unit) then
		unit = self:GetAttribute("unit")
		if (not unit) then
			return
		end
	end

	Curses.Magic, Curses.Curse, Curses.Poison, Curses.Disease = nil, nil, nil, nil

	local show
	local debuffCount = 0
	local _, unitClass = UnitClass(unit)

	for i = 1, 40 do
		local debuffName,_, debuff, debuffStack, debuffType = UnitDebuff(unit, i)
		if (not debuff) then
			break
		end

		if (debuffType) then
			local exclude = ArcaneExclusions[debuffName]
			if (not exclude or (type(exclude) == "table" and not exclude[unitClass])) then
				Curses[debuffType] = debuffType
				debuffCount = debuffCount + 1
			end
		end
	end

	if (debuffCount > 0) then
		-- 2.2.6 - Very (very very) slight speed optimazation by having a function per class which is set at startup
		show = getShow(Curses)
	end

	local colour, borderColour
	if (show) then
		colour = DebuffTypeColor[show]
		colour.a = 1

		if (conf.highlightDebuffs.border) then
			borderColour = colour
		else
			borderColour = conf.colour.border
		end
	else
		colour = conf.colour.frame
		borderColour = conf.colour.border
	end

	if (show and conf.highlightDebuffs.frame) then
		self.forcedColour = borderColour
		bgDef.edgeFile = curseEdge
	else
		self.forcedColour = nil
		--bgDef.edgeFile = normalEdge

		bgDef.edgeFile = self.edgeFile or normalEdge
		bgDef.edgeSize = self.edgeSize or 16
		bgDef.insets.left = self.edgeInsets or 5
		bgDef.insets.top = self.edgeInsets or 5
		bgDef.insets.right = self.edgeInsets or 5
		bgDef.insets.bottom = self.edgeInsets or 5
	end

	for i, f in pairs(self.FlashFrames) do
		if (not conf.highlightDebuffs.frame) then
			colour = conf.colour.frame
		end
		f:SetBackdrop(bgDef)
		f:SetBackdropColor(colour.r, colour.g, colour.b, colour.a)
		f:SetBackdropBorderColor(borderColour.r, borderColour.g, borderColour.b, borderColour.a)
	end
end

-- XPerl_GetSavePositionTable
function XPerl_GetSavePositionTable(create)

	local name = UnitName("player")
	local realm = GetRealmName()

	if (not XPerlConfigNew.savedPositions) then
		if (not create) then
			return
		end
		XPerlConfigNew.savedPositions = {}
	end
	local c = XPerlConfigNew.savedPositions
	if (not c[realm]) then
		if (not create) then
			return
		end
		c[realm] = {}
	end
	if (not c[realm][name]) then
		if (not create) then
			return
		end
		c[realm][name] = {}
	end
	local table = c[realm][name]

	return table
end


-- XPerl_SavePosition
function XPerl_SavePosition(self, onlyIfEmpty)
	local name = self:GetName()
	if (name) then
		local s = self:GetScale()
		local t = self:GetTop()
		local l = self:GetLeft()
		local h = self:IsResizable() and self:GetHeight()
		local w = self:IsResizable() and self:GetWidth()

		local table = XPerl_GetSavePositionTable(true)

		if (not onlyIfEmpty or (onlyIfEmpty and not table[name])) then
			if (t and l) then
				if (not table[name]) then
					table[name] = {}
				end
				table[name].top = t * s
				table[name].left = l * s
				table[name].height = h
				table[name].width = w
			else
				table[name] = nil
			end
		else
			if (table[name] and not self:IsUserPlaced()) then
				XPerl_RestorePosition(self)
			end
		end
	end
end

-- XPerl_RestorePosition
function XPerl_RestorePosition(self)
	if (XPerlConfigNew.savedPositions) then
		local name = self:GetName()
		if (name) then
			local table = XPerl_GetSavePositionTable()
			if (table) then
				local pos = table[name]
				if (pos and pos.left and pos.top) then
					self:ClearAllPoints()
					self:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", pos.left / self:GetScale(), pos.top / self:GetScale())

					if (pos.height and pos.width) then
						if (self:IsResizable()) then
							self:SetHeight(pos.height)
							self:SetWidth(pos.width)
						else
							pos.height, pos.width = nil, nil
						end
					end

					self:SetUserPlaced(true)
				end
			end
		end
	end
end

-- XPerl_RestorePosition
function XPerl_RestoreAllPositions()
	local table = XPerl_GetSavePositionTable()
	if (table) then
		for k,v in pairs(table) do
			if (k == "XPerl_Frame" or k == "XPerl_RaidMonitor_Frame" or k == "XPerl_Check" or k == "XPerl_AdminFrame" or k == "XPerl_Assists_Frame") then
				-- Fix for a wrong name with versions 2.3.2 and 2.3.2a
				-- It was using XPerl_Frame instead of XPerl_MTList_Anchor
				-- and XPerl_RaidMonitor_Frame instead of XPerl_RaidMonitor_Anchor
				-- And now a change to XPerl_Check to XPerl_CheckAnchor and XPerl_AdminFrame to XPerl_AdminFrameAnchor
				table[k] = nil
			else
				local frame = getglobal(k)
				if (frame) then
					if (v.left and v.top) then
						frame:ClearAllPoints()
						frame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", v.left / frame:GetScale(), v.top / frame:GetScale())
						if (v.height and v.width) then
							if (frame:IsResizable()) then
								frame:SetHeight(v.height)
								frame:SetWidth(v.width)
							else
								v.height, v.width = nil, nil
							end
						end
						frame:SetUserPlaced(true)
					end
				end
			end
		end
	end
end

-- true indicates the buff is valid on anyone, otherwise a class string dictates we only count it if the player with the buff matches
local BuffExceptions = {
	--ALL = {Spell_Misc_Food = true},	-- Enable this for food
	PRIEST = {Spell_Nature_ResistNature = true, Spell_Nature_Rejuvenation = true, Spell_Holy_PrayerOfMendingtga = true},
	DRUID = {Spell_Holy_Renew = true},		-- Priest regens
	WARLOCK = {Spell_Shadow_SoulGem = true},	-- Soulstone Resurrection
	HUNTER = {Spell_Nature_RavenForm = "HUNTER", Ability_Mount_JungleTiger = "HUNTER", Ability_Mount_WhiteTiger = true, Ability_Mount_PinkTiger = "HUNTER", Ability_Hunter_AspectOfTheMonkey = "HUNTER", Spell_Nature_ProtectionformNature = true, Ability_Rogue_FeignDeath = "HUNTER", Ability_Hunter_RunningShot = "HUNTER", Ability_TrueShot = true, Ability_Hunter_BeastTraining = "WARRIOR"},
	ROGUE = {Ability_Stealth = "ROGUE", Spell_Shadow_ShadowWard = "ROGUE", Ability_Vanish = "ROGUE", Ability_Rogue_Sprint = "ROGUE"},
	SHAMAN = {Spell_Nature_ManaRegenTotem = true, Spell_Nature_UndyingStrength = true, Spell_Nature_SkinOfEarth = true,
		Spell_Nature_GroundingTotem = true, Spell_Nature_InvisibilityTotem = true, Spell_Nature_ManaRegenTotem = true,
		Spell_Nature_StoneSkinTotem = true, Spell_Nature_SlowingTotem = true, Spell_Nature_EarthBindTotem = true,
		Spell_Nature_HealingWay = true, Spell_Nature_NatureResistanceTotem = true, Spell_FrostResistanceTotem_01 = true,
		Spell_FireResistanceTotem_01 = true, Spell_Nature_Brilliance = true, Spell_Nature_EarthBind = true,
		INV_Spear_04 = true, Spell_Nature_HealingWay = true},
	PALADIN = {Spell_Holy_DevotionAura = true, Spell_Holy_AuraOfLight = true, Spell_Holy_MindSooth = true,
		Spell_Shadow_SealOfKings = true, Spell_Holy_MindVision = true, Spell_Frost_WizardMark = true,
		Spell_Fire_SealOfFire = true, Spell_Holy_CrusaderAura = true},
}

local DebuffExceptions = {
	ALL = {INV_Misc_Bandage_08 = true},		-- Recently Bandaged
	PRIEST = {Spell_Holy_AshesToAshes = true},	-- Weakened Soul
	PALADIN = {Spell_Holy_RemoveCurse = true}	-- Forbearance
}

--local SeasonalDebuffs = {				-- TODO Ignore these option
--	INV_Misc_Branch_01,				-- Mistletoe
--}

-- BuffException
--local showInfo
local function BuffException(unit, index, flag, func, exceptions)
	local name, rank, buff, count, debuffType, dur, max, isMine
	if (not flag or flag == 0 or flag == "0") then
		-- Not filtered, just return it
		name, rank, buff, count, debuffType, dur, max, isMine = func(unit, index)
		return name, rank, buff, count, debuffType, dur, max, isMine, index
	end

	name, rank, buff, count, debuffType, dur, max, isMine = func(unit, index, 1)
	if (buff) then
		-- We need the index of the buff unfiltered later for tooltips
		for i = 1,40 do
			local name1, rank1, buff1, count1, debuffType1, dur1, max1 = func(unit, i)
			if (not name1) then
				break
			end
			if (name == name1 and rank == rank1 and buff == buff1 and count == count1) then
				index = i
				break
			end
		end

		return name, rank, buff, count, debuffType, dur, max, isMine, index
	end

	-- See how many filtered buffs WoW has returned by default
	local normalBuffFilterCount = 0
	for i = 1,40 do
		name = func(unit, i, 1)
		if (not name) then
			normalBuffFilterCount = i - 1
			break
		end
	end

	-- Nothing found by default, so look for exceptions
	local unitClass = select(2, UnitClass(unit))
	local foundValid = 0
	local classExceptions = exceptions[playerClass]
	local allExceptions = exceptions.ALL
	for i = 1,40 do
		name, rank, buff, count, debuffType, dur, max, isMine = func(unit, i)
		if (not buff) then
			break
		end

		if (strsub(buff, 1, 16) == "Interface\\Icons\\") then
			local test = strsub(buff, 17)
			local good

			if (classExceptions) then
				good = classExceptions[test]
			end
			if (not good and allExceptions) then
				good = allExceptions[test]
			end

			-- Enable this for potions
			--if (not good) then
			--	if (exceptions == BuffExceptions and strsub(test, 1, 11) == "INV_Potion_") then
			--		good = true
			--	end
			--end

			if (good and type(good) == "string" and good ~= unitClass) then
				good = nil
			end

			if (good) then
				foundValid = foundValid + 1
				if (foundValid + normalBuffFilterCount == index) then
					return name, rank, buff, count, debuffType, dur, max, isMine, i
				end
			end
		end
	end
end

-- Fix the odd difference in return values introduced with WoW 2.1.0
-- UnitBuff returns:   Name, Rank, Buff, Count, Duration, MaxDuration
-- UnitDebuff returns: Name, Rank, Buff, Count, debuffType, Duration, MaxDuration
local function XUnitBuff(unit, num, cureCast)
	local name, rank, buff, count, dur, max, isMine = UnitBuff(unit, num, cureCast)
	return name, rank, buff, count, "", dur, max, isMine
end

-- XPerl_UnitBuff
function XPerl_UnitBuff(unit, index, flag)
	return BuffException(unit, index, flag, XUnitBuff, BuffExceptions)
end

-- XPerl_UnitBuff
function XPerl_UnitDebuff(unit, index, flag)
	return BuffException(unit, index, flag, UnitDebuff, DebuffExceptions)
end

-- XPerl_TooltipSetUnitBuff
-- Retreives the index of the actual unfiltered buff, and uses this on unfiltered tooltip call
function XPerl_TooltipSetUnitBuff(self, unit, ind, flag)
	local name, rank, buff, count, _, dur, max, _, index = BuffException(unit, ind, flag, UnitBuff, BuffExceptions)
	if (name and index) then
		self:SetUnitBuff(unit, index)
	end
end

-- XPerl_TooltipSetUnitDebuff
-- Retreives the index of the actual unfiltered debuff, and uses this on unfiltered tooltip call
function XPerl_TooltipSetUnitDebuff(self, unit, ind, flag)
	local name, rank, buff, count, debuffType, dur, max, _, index = BuffException(unit, ind, flag, UnitDebuff, DebuffExceptions)
	if (name and index) then
		self:SetUnitDebuff(unit, index)
	end
end

local colourList = {
	[XPERL_LOC_CLASS_WARRIOR]	= "WARRIOR",
	[XPERL_LOC_CLASS_MAGE]		= "MAGE",
	[XPERL_LOC_CLASS_ROGUE]		= "ROGUE",
	[XPERL_LOC_CLASS_DRUID]		= "DRUID",
	[XPERL_LOC_CLASS_HUNTER]	= "HUNTER",
	[XPERL_LOC_CLASS_SHAMAN]	= "SHAMAN",
	[XPERL_LOC_CLASS_PRIEST]	= "PRIEST",
	[XPERL_LOC_CLASS_WARLOCK]	= "WARLOCK",
	[XPERL_LOC_CLASS_PALADIN]	= "PALADIN",
	[XPERL_LOC_CLASS_WARRIORFEM or XPERL_LOC_CLASS_WARRIOR]	= "WARRIOR",
	[XPERL_LOC_CLASS_MAGEFEM or XPERL_LOC_CLASS_MAGE]		= "MAGE",
	[XPERL_LOC_CLASS_ROGUEFEM or XPERL_LOC_CLASS_ROGUE]		= "ROGUE",
	[XPERL_LOC_CLASS_DRUIDFEM or XPERL_LOC_CLASS_DRUID]		= "DRUID",
	[XPERL_LOC_CLASS_HUNTERFEM or XPERL_LOC_CLASS_HUNTER]	= "HUNTER",
	[XPERL_LOC_CLASS_SHAMANFEM or XPERL_LOC_CLASS_SHAMAN]	= "SHAMAN",
	[XPERL_LOC_CLASS_PRIESTFEM or XPERL_LOC_CLASS_PRIEST]	= "PRIEST",
	[XPERL_LOC_CLASS_WARLOCKFEM or XPERL_LOC_CLASS_WARLOCK] = "WARLOCK",
	[XPERL_LOC_CLASS_PALADINFEM or XPERL_LOC_CLASS_PALADIN]	= "PALADIN",
}

-- XPerl_GuildStatusUpdate()
function XPerl_GuildStatusUpdate()
	if (conf.colour.class or conf.tooltip.xperlInfo) then
		local myZone = GetRealZoneText()

		local guildOffset = FauxScrollFrame_GetOffset(GuildListScrollFrame)
		for i=1, GUILDMEMBERS_TO_DISPLAY, 1 do
			local guildIndex = guildOffset + i

			local button = getglobal("GuildFrameButton"..i);
			button.guildIndex = guildIndex;
			local name, rank, rankIndex, level, class, zone, note, officernote, online = GetGuildRosterInfo(guildIndex)
			if (not name) then
				break
			end

			if (conf.tooltip.xperlInfo and XPerl_GetUsage) then
				local u = XPerl_GetUsage(name)

				if (u) then
					if (online) then
						getglobal("GuildFrameButton"..i.."Name"):SetTextColor(1, 0.4, 0.4)
						--getglobal("GuildFrameGuildStatusButton"..i.."Name"):SetTextColor(1, 0.4, 0.4)
					else
						getglobal("GuildFrameButton"..i.."Name"):SetTextColor(0.5, 0.2, 0.2)
						--getglobal("GuildFrameGuildStatusButton"..i.."Name"):SetTextColor(0.5, 0.2, 0.2)
					end
				end
			end

			if (conf.colour.class and conf.colour.guildList) then
				class = colourList[class]

				if (class) then
					local color = XPerl_GetClassColour(class)
					if (color) then
						if (online) then
							getglobal("GuildFrameButton"..i.."Class"):SetTextColor(color.r, color.g, color.b)
						else
							getglobal("GuildFrameButton"..i.."Class"):SetTextColor(color.r / 2, color.g / 2, color.b / 2)
						end
					end
				end

				if (zone == myZone) then
					if (online) then
						getglobal("GuildFrameButton"..i.."Zone"):SetTextColor(0, 1, 0)
					else
						getglobal("GuildFrameButton"..i.."Zone"):SetTextColor(0, 0.5, 0)
					end
				end

				local color = GetDifficultyColor(level)
				if (online) then
					getglobal("GuildFrameButton"..i.."Level"):SetTextColor(color.r, color.g, color.b)
				else
					getglobal("GuildFrameButton"..i.."Level"):SetTextColor(color.r / 2, color.g / 2, color.b / 2)
				end
			end
		end
	end
end

hooksecurefunc("GuildStatus_Update", XPerl_GuildStatusUpdate)

-- XPerl_WhoList_Update
function XPerl_WhoList_Update()
	if (conf.colour.class and conf.colour.guildList) then
		local numWhos, totalCount = GetNumWhoResults()
		local whoOffset = FauxScrollFrame_GetOffset(WhoListScrollFrame)

		local myZone = GetRealZoneText()
		local myRace = UnitRace("player")
		local myGuild = GetGuildInfo("player")

		for i=1, WHOS_TO_DISPLAY, 1 do
			local name, guild, level, race, class, zone = GetWhoInfo(whoOffset + i)
			local color
			if (not name) then
				break
			end

			getglobal("WhoFrameButton"..i.."Name"):SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
			if (conf.tooltip.xperlInfo and XPerl_GetUsage) then
				local u = XPerl_GetUsage(name)
				if (u) then
					getglobal("WhoFrameButton"..i.."Name"):SetTextColor(1, 0.4, 0.4)
				end
			end

			if (conf.colour.class and conf.colour.guildList) then
				if (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 1) then
					-- Zone
					if (zone == myZone) then
						getglobal("WhoFrameButton"..i.."Variable"):SetTextColor(0, 1, 0)
					else
						getglobal("WhoFrameButton"..i.."Variable"):SetTextColor(1, 1, 1)
					end
				elseif (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 2) then
					if (guild == myGuild) then
						getglobal("WhoFrameButton"..i.."Variable"):SetTextColor(0, 1, 0)
					else
						getglobal("WhoFrameButton"..i.."Variable"):SetTextColor(1, 1, 1)
					end

				elseif (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 3) then
					if (race == myRace) then
						getglobal("WhoFrameButton"..i.."Variable"):SetTextColor(0, 1, 0)
					else
						getglobal("WhoFrameButton"..i.."Variable"):SetTextColor(1, 1, 1)
					end
				end

				getglobal("WhoFrameButton"..i.."Class"):SetTextColor(1, 1, 1)
				class = colourList[class]
				if (class) then
					local color = XPerl_GetClassColour(class)
					if (color) then
						getglobal("WhoFrameButton"..i.."Class"):SetTextColor(color.r, color.g, color.b)
					end
				end

				local color = GetDifficultyColor(level)
				getglobal("WhoFrameButton"..i.."Level"):SetTextColor(color.r, color.g, color.b)
			else
				getglobal("WhoFrameButton"..i.."Variable"):SetTextColor(1, 1, 1)
				getglobal("WhoFrameButton"..i.."Level"):SetTextColor(1, 1, 1)
				getglobal("WhoFrameButton"..i.."Class"):SetTextColor(1, 1, 1)
			end
		end
	end
end

hooksecurefunc("WhoList_Update", XPerl_WhoList_Update)

----------------------
-- Fading Bar Stuff --
----------------------
local fadeBars = {}
local freeFadeBars = {}
local tempDisableFadeBars

function XPerl_NoFadeBars(tempDisable)
	tempDisableFadeBars = tempDisable
end

-- CheckOnUpdate
local function CheckOnUpdate()
	if (next(fadeBars)) then
		XPerl_Globals:SetScript("OnUpdate", XPerl_BarUpdate)
	else
		XPerl_Globals:SetScript("OnUpdate", nil)
	end
end

-- XPerl_BarUpdate
--local speakerTimer = 0
--local speakerCycle = 0
function XPerl_BarUpdate()
	local did
	for k,v in pairs(fadeBars) do
		if (k:IsShown()) then
			v:SetAlpha(k.fadeAlpha)
			k.fadeAlpha = k.fadeAlpha - (arg1 / conf.bar.fadeTime)

			local r, g, b = v.tex:GetVertexColor()
			v:SetStatusBarColor(r, g, b)
		else
			-- Not shown, so end it
			k.fadeAlpha = 0
		end

		if (k.fadeAlpha <= 0) then
			tinsert(freeFadeBars, v)
			fadeBars[k] = nil
			k.fadeAlpha = nil
			k.fadeBar = nil
			v:SetValue(0)
			v:Hide()
			v.tex = nil
			did = true
		end
	end

	if (did) then
		CheckOnUpdate()
	end
end

-- GetFreeFader
local function GetFreeFader(parent)
	local bar = freeFadeBars[1]
	if (bar) then
		tremove(freeFadeBars, 1)
		bar:SetParent(parent)
	else
		bar = CreateFrame("StatusBar", nil, parent)
	end

	if (bar) then
		fadeBars[parent] = bar
		CheckOnUpdate()

		bar.tex = parent.tex

		local tex = parent:GetStatusBarTexture()
		bar:SetStatusBarTexture(tex:GetTexture())

		local r, g, b = bar.tex:GetVertexColor()
		bar:SetStatusBarColor(r, g, b)

		bar:SetFrameLevel(parent:GetFrameLevel())

		bar:ClearAllPoints()
		bar:SetPoint("TOPLEFT", 0, 0)
		bar:SetPoint("BOTTOMRIGHT", 0, 0)
		bar:SetAlpha(1)

		return bar
	end
end

-- XPerl_StatusBarSetValue
function XPerl_StatusBarSetValue(self, val)
	if (not tempDisableFadeBars and conf.bar.fading and self:GetName()) then
		local min, max = self:GetMinMaxValues()
		local current = self:GetValue()

		if (val < current and val <= max and val >= min) then
			local bar = fadeBars[self]

			if (not bar) then
				bar = GetFreeFader(self)
			end

			if (bar) then
				if (not self.fadeAlpha) then
					self.fadeAlpha = self:GetParent():GetAlpha()
					bar:SetValue(current)
				end

				bar:SetMinMaxValues(min, max)
				bar:SetAlpha(self.fadeAlpha)
				bar:Show()
			end
		end
	end

	XPerl_OldStatusBarSetValue(self, val)
end

-- XPerl_RegisterClickCastFrame
function XPerl_RegisterClickCastFrame(self)
	if (not ClickCastFrames) then
		ClickCastFrames = {}
	end
	ClickCastFrames[self] = true
end

function XPerl_UnregisterClickCastFrame(self)
	if (ClickCastFrames) then
		ClickCastFrames[self] = nil
	end
end

function XPerl_SecureUnitButton_OnLoad(self, unit, menufunc)
	self:SetAttribute("*type1", "target")
	self:SetAttribute("type2", "menu")
	if (unit) then
		self:SetAttribute("unit", unit)
	end
	self.menu = menufunc
	XPerl_RegisterClickCastFrame(self)
end

------------------------------------------------------------------------------
-- Generic menus

-- XPerl_GenericDropDown_OnLoad
function XPerl_GenericDropDown_OnLoad(self)
	UIDropDownMenu_Initialize(self, XPerl_GenericDropDown_Initialize, "MENU")
	tinsert(UnitPopupFrames, "XPerl_DropDown")
end

-- XPerl_GenericDropDown_Initialize
function XPerl_GenericDropDown_Initialize()
	local unitid = XPerl_DropDown.unit or "player"
	local menu
	local name
	local id

	if ( UnitIsUnit(unitid, "player") ) then
		menu = "SELF"
	elseif ( UnitIsUnit(unitid, "pet") ) then
		menu = "PET"
	elseif ( UnitIsPlayer(unitid) ) then
		id = UnitInRaid(unitid)
		if ( id ) then
			menu = "RAID_PLAYER"		-- "RAID" ?
		elseif ( UnitInParty(unitid) ) then
			menu = "PARTY"
		else
			menu = "PLAYER"
		end
	else
		menu = "RAID_TARGET_ICON"
		name = RAID_TARGET_ICON
	end
	if (menu) then
		UnitPopup_ShowMenu(XPerl_DropDown, menu, unitid, name, id)
	end
end

-- XPerl_ShowGenericMenu
-- self, unit, button are passed from secure template handler (SecureTemplates.lua, line 288 in SecureActionButton_OnClick)
function XPerl_ShowGenericMenu(self, unit, button, anchor)
	HideDropDownMenu(1)

	if (not unit) then
		unit = SecureButton_GetUnit(self)
	end
	XPerl_DropDown.unit = unit

	if (XPerl_DropDown.unit) then
		XPerl_DropDown.id = strmatch(XPerl_DropDown.unit or "", "(%d+)")
		XPerl_DropDown.displayMode = "MENU"

		XPerl_DropDown.initialize = XPerl_GenericDropDown_Initialize

		if (not anchor) then
			if (self.statsFrame) then
				anchor = self.statsFrame:GetName()
			elseif (self:GetParent().statsFrame) then
				anchor = self:GetParent().statsFrame:GetName()
			end
		end

--ChatFrame7:AddMessage("Generic menu for "..tostring(unit).." anchor: "..tostring(anchor))

		ToggleDropDownMenu(1, nil, XPerl_DropDown, anchor or "cursor", 0, 0)
	end
end

------------------------------------------------------------------------------
-- XPerl_GetBuffButton
local buffIconCount = 0
function XPerl_GetBuffButton(self, buffnum, debuff, createIfAbsent)
	debuff = (debuff or 0)
	local buffType, buffList		--, buffFrame

	if (debuff == 1) then
		--buffFrame = self.debuffFrame
		buffType = "DeBuff"
		buffList = self.buffFrame.debuff
		if (not buffList) then
			self.buffFrame.debuff = {}
			buffList = self.buffFrame.debuff
		end
	else
		--buffFrame = self.buffFrame
		buffType = "Buff"
		buffList = self.buffFrame.buff
		if (not buffList) then
			self.buffFrame.buff = {}
			buffList = self.buffFrame.buff
		end
	end

	local button = buffList and buffList[buffnum]

	if (not button and createIfAbsent) then
		local setup = self.buffSetup
		local parent = self.buffFrame

		if (debuff == 1 and setup.debuffParent) then
			parent = self.debuffFrame
		end

		buffIconCount = buffIconCount + 1
		button = CreateFrame("Button", "XPerlBuff"..buffIconCount, parent, format("XPerl_Cooldown_%sTemplate", buffType))
		button:Hide()
		button:SetID(buffnum)
		--button.cooldown.noCooldownCount = true				-- OmniCC to NOT show cooldown

		if (setup.rightClickable) then
			button:RegisterForClicks("RightButtonUp")
		end

		local size = self.conf.buffs.size
		if (debuff == 1) then
			size = self.conf.debuffs.size or (size * (1 + (setup.debuffSizeMod * debuff)))
		end
		button:SetScale(size / 32)

		if (setup.onCreate) then
			setup.onCreate(button)
		end

		if (debuff == 1) then
			--buffFrame.UpdateTooltip = setup.updateTooltipDebuff
			button.UpdateTooltip = setup.updateTooltipDebuff
			for k,v in pairs (setup.debuffScripts) do
				button:SetScript(k, v)
			end
		else
			--buffFrame.UpdateTooltip = setup.updateTooltipBuff
			button.UpdateTooltip = setup.updateTooltipBuff
			for k,v in pairs (setup.buffScripts) do
				button:SetScript(k, v)
			end
		end
		buffList[buffnum] = button

		button:ClearAllPoints()
		if (buffnum == 1) then
			if (debuff == 1) then
				if (setup.debuffAnchor1) then
					setup.debuffAnchor1(self, button)
				end
			else
				if (setup.buffAnchor1) then
					setup.buffAnchor1(self, button)
				end
			end
		else
			button:SetPoint("TOPLEFT", buffList[buffnum - 1], "TOPRIGHT", 1 + debuff, 0)
		end
	end

	return button
end

-- BuffCooldownDisplay
local function BuffCooldownDisplay(self)
	local t = GetTime()
	if (t > self.endTime - 1) then
		self.countdown:SetText(strsub(format("%.1f", max(0, self.endTime - t)), 2, 10))
		self.countdown:Show()
	elseif (t > self.endTime - conf.buffs.countdownStart) then
		self.countdown:SetText(max(0, floor(self.endTime - t)))
		self.countdown:Show()
	else
		self.countdown:Hide()
	end
end

-- XPerl_CooldownFrame_SetTimer(self, start, duration, enable)
function XPerl_CooldownFrame_SetTimer(self, start, duration, enable)
	if ( start > 0 and duration > 0 and enable > 0) then
		self:SetCooldown(start, duration)
		self.endTime = start + duration

		if (conf.buffs.countdown) then
			self:SetScript("OnUpdate", BuffCooldownDisplay)
		else
			self:SetScript("OnUpdate", nil)
			self.countdown:Hide()
		end

		self:Show()
	else
		self:Hide()
	end
end

-- XPerl_Unit_BuffSpacing
local function XPerl_Unit_BuffSpacing(self)

	local w = self.statsFrame:GetWidth()
	if (self.portraitFrame and self.portraitFrame:IsShown()) then
		w = w - 2 + self.portraitFrame:GetWidth()
	end
	if (self.levelFrame and self.levelFrame:IsShown()) then
		w = w - 2 + self.levelFrame:GetWidth()
	end
	if (not self.buffSpacing) then
		self.buffSpacing = XPerl_GetReusableTable()
	end
	self.buffSpacing.rowWidth = w

	local srs = 0
	if (not self.conf.buffs.above) then
		if (not self.statsFrame.manaBar or not self.statsFrame.manaBar:IsShown()) then
			srs = 10
		end

		if (self.creatureTypeFrame and self.creatureTypeFrame:IsShown()) then
			srs = srs + self.creatureTypeFrame:GetHeight() - 2
		end
	end

	if (srs > 0) then
		self.buffSpacing.smallRowHeight = srs
		self.buffSpacing.smallRowWidth = self.statsFrame:GetWidth()
	else
		self.buffSpacing.smallRowHeight = 0
		self.buffSpacing.smallRowWidth = w
	end
end

-- WieghAnchor(self, at)
local function WieghAnchor(self)
	if (not self.TOPLEFT or self.conf.flip ~= self.lastFlip or self.conf.buffs.above ~= self.lastAbove) then
		self.lastFlip = self.conf.flip
		self.lastAbove = self.conf.buffs.above

		local left, right, top, bottom
		if (self.conf.flip) then
			left, right = "RIGHT", "LEFT"
			self.SPACING = -1
		else
			left, right = "LEFT", "RIGHT"
			self.SPACING = 1
		end
		if (self.conf.buffs.above) then
			top, bottom = "BOTTOM", "TOP"
			self.VSPACING = 1
		else
			top, bottom = "TOP", "BOTTOM"
			self.VSPACING = -1
		end

		self.TOPLEFT = top..left
		self.TOPRIGHT = top..right
		self.BOTTOMLEFT = bottom..left
		self.BOTTOMRIGHT = bottom..right
	end
end

-- XPerl_Unit_BuffPositionsType
local function XPerl_Unit_BuffPositionsType(self, list, useSmallStart, buffSizeBase)
	local prevBuff, reusedSpace, hideFrom
	local firstOfRow = nil
	local prevRow, prevRowI = list[1], 1
	if (not prevRow) then
		return
	end
	local above = self.conf.buffs.above
	local colPoint, curRow, rowsHeight = 0, 1, 0
	local rowSize = (useSmallStart and self.buffSpacing.smallRowWidth) or self.buffSpacing.rowWidth
	local maxRows = self.conf.buffs.rows or 99
	local decrementMaxRowsIfLastIsBig			-- Descriptive variable names ftw... If only upvalues took no actual memory space for the name... :(

	for i = 1,#list do
		if (curRow > maxRows) then
			hideFrom = i
			break
		end

		if (rowsHeight >= self.buffSpacing.smallRowHeight) then
			rowSize = self.buffSpacing.rowWidth
		end

		local buff = list[i]
		if (i > 1 and not buff:IsShown()) then
			break
		end

		local buffSize = (buff.big and (buffSizeBase * 2)) or buffSizeBase

		buff:ClearAllPoints()
		if (i == 1) then
			prevRow, prevRowI = buff, 1

			if (buff.big) then
				if (curRow == maxRows) then
					maxRows = maxRows + 1
					decrementMaxRowsIfLastIsBig = true
				end
			end

			if (self.prevBuff) then
				buff:SetPoint(self.TOPLEFT, self.prevBuff, self.BOTTOMLEFT, 0, self.VSPACING)
			else
				buff:SetPoint(self.TOPLEFT, 0, 0)
			end
		elseif (firstOfRow) then
			firstOfRow = nil
			if (not buff.big and prevRow.big and not reusedSpace) then
				-- Previous row starts with a big buff at start, so we try to use the odd space between rows
				-- for normal size buffs instead of starting a new row and having a buff width of wasted space.
				-- So we get:
				--	1123456
				--	11789AB
				--	CDEF
				-- Instead of:
				--	1123456
				--	11
				--	789ABCD
				--	EF

				local tempColPoint = (buffSizeBase * 2) + 1
				local j = prevRowI
				while (j < #list) do
					local temp = list[j + 1]
					if (temp and temp.big) then
						tempColPoint = tempColPoint + (buffSizeBase * 2) + 1
						j = j + 1
					else
						break
					end
				end

				if (tempColPoint < rowSize - buffSizeBase) then		--  and rowsHeight - buffSizeBase - 1 >= self.buffSpacing.smallRowHeight
					local prevRowBig, prevRowBigI = list[j], j
					colPoint = tempColPoint
					buff:SetPoint(self.BOTTOMLEFT, prevRowBig, self.BOTTOMRIGHT, self.SPACING, 0)
				else
					buff:SetPoint(self.TOPLEFT, prevRow, self.BOTTOMLEFT, 0, self.VSPACING)
					prevRow, prevRowI = buff, i
				end
				reusedSpace = true
			else
				buff:SetPoint(self.TOPLEFT, prevRow, self.BOTTOMLEFT, 0, self.VSPACING)
				prevRow, prevRowI = buff, i
				reusedSpace = nil

				if (buff.big) then
					if (curRow == maxRows) then
						maxRows = maxRows + 1
						decrementMaxRowsIfLastIsBig = true
					end
				end
			end
		else
			buff:SetPoint(self.TOPLEFT, prevBuff, self.TOPRIGHT, self.SPACING, 0)
		end

		colPoint = colPoint + buffSize + 1

		local nextBuff = list[i + 1]
		local nextBuffSize = buffSize
		if (nextBuff) then
			nextBuffSize = (nextBuff.big and (buffSizeBase * 2)) or buffSizeBase
		end

		if (self.conf.buffs.wrap and colPoint + nextBuffSize + 1 > rowSize) then
			if (buff.big and decrementMaxRowsIfLastIsBig) then
				decrementMaxRowsIfLastIsBig = nil
				maxRows = maxRows - 1
			end

			colPoint = 0
			curRow = curRow + 1
			if (prevRow.big) then
				rowsHeight = rowsHeight + (buffSize * 2) + 1
			else
				rowsHeight = rowsHeight + buffSize + 1
			end
			firstOfRow = true
		end

		prevBuff = buff
	end

	if (hideFrom) then
		for i = hideFrom,#list do
			list[i]:Hide()
		end
	end
	if (useSmallStart) then
		self.hideFrom1 = hideFrom
	else
		self.hideFrom2 = hideFrom
	end

	self.prevBuff = prevRow
end

-- XPerl_Unit_BuffPositions
function XPerl_Unit_BuffPositions(self, buffList1, buffList2, size1, size2)
	local optMix = format("%d%d%d%d%d%d%d", self.perlBuffs or 0, self.perlDebuffs or 0, self.perlBuffsMine or 0, self.perlDebuffsMine or 0, UnitCanAttack("player", self.partyid) or 2, (UnitManaMax(self.partyid) > 0 and 1) or 0, (self.creatureTypeFrame and self.creatureTypeFrame:IsVisible() and 1) or 0)
	if (optMix ~= self.buffOptMix) then
		WieghAnchor(self)

		self.buffOptMix = optMix
		self.prevBuff = nil

		if (self.GetBuffSpacing) then
			self:GetBuffSpacing(self)
		else
			XPerl_Unit_BuffSpacing(self)
		end

		-- De-anchor first 2 because faction changes can mess up the order of things.
		if (buffList1 and buffList1[1]) then buffList1[1]:ClearAllPoints() end
		if (buffList2 and buffList2[1]) then buffList2[1]:ClearAllPoints() end

		if (buffList1) then
			XPerl_Unit_BuffPositionsType(self, buffList1, true, size1)
		end
		if (buffList2) then
			XPerl_Unit_BuffPositionsType(self, buffList2, false, size2)
		end
	else
		if (self.hideFrom1 and buffList1) then
			for i = self.hideFrom1,#buffList1 do
				buffList1[i]:Hide()
			end
		end
		if (self.hideFrom2 and buffList2) then
			for i = self.hideFrom2,#buffList2 do
				buffList2[i]:Hide()
			end
		end
	end
end

-- XPerl_Unit_UpdateBuffs(self)
function XPerl_Unit_UpdateBuffs(self, maxBuffs, maxDebuffs, castableOnly, curableOnly)
	local buffs, debuffs, buffsMine, debuffsMine = 0, 0, 0, 0
	local partyid = self.partyid

	if (UnitExists(partyid)) then
		if (not maxBuffs) then maxBuffs = 24 end
		if (not maxDebuffs) then maxDebuffs = 40 end
		local lastIcon = 0

		XPerl_GetBuffButton(self, 1, 0, true)
		XPerl_GetBuffButton(self, 1, 1, true)

		if (self.conf.buffs.enable and maxBuffs and maxBuffs > 0) then
			self.buffFrame:Show()
			for buffnum = 1,maxBuffs do
				local name, rank, buff, count, duration, timeLeft, isMine
				if (partyid == "player") then
					local buffIndex, untilCancelled = GetPlayerBuff(buffnum, "HELPFUL")
					if (buffIndex > 0) then
						name, rank = GetPlayerBuffName(buffIndex)
						if (name) then
							buff = GetPlayerBuffTexture(buffIndex)
							count = GetPlayerBuffApplications(buffIndex)
							timeLeft = GetPlayerBuffTimeLeft(buffIndex)
							duration = timeLeft
							isMine = true

							if (conf.buffs.countdown) then
								-- We don't get full time duration from the API, but for our own cast buffs we can from UnitBuff
								local match = name.."_"..rank.."_"..buff
								for k = 1,40 do
									local name2, rank2, buff2, count2, duration2, timeLeft2 = UnitBuff(partyid, k)
									if (not name2) then
										break
									end
									local match2 = name2.."_"..rank2.."_"..buff2
									if (match == match2) then
										duration = duration2
										break
									end
								end
							end
						end
					end
				else
					local _
					name, rank, buff, count, _, duration, timeLeft, isMine = XPerl_UnitBuff(partyid, buffnum, castableOnly)
				end

				if (buff) then
					local button = XPerl_GetBuffButton(self, buffnum, 0, true)
					button:SetAlpha(1)

					buffs = buffs + 1

					button.icon:SetTexture(buff)
					if (count > 1) then
						button.count:SetText(count)
						button.count:Show()
					else
						button.count:Hide()
					end

					-- Handle cooldowns
					if (button.cooldown) then
						if (duration and conf.buffs.cooldown) then
							local start = GetTime() - (duration - timeLeft)
							XPerl_CooldownFrame_SetTimer(button.cooldown, start, duration, 1)
						else
							button.cooldown:Hide()
						end
					end

					lastIcon = buffnum
					button:Show()

					if (self.conf.buffs.big and isMine) then
						buffsMine = buffsMine + 1
						button.big = true
						button:SetScale((self.conf.buffs.size * 2) / 32)
					else
						button.big = nil
						button:SetScale(self.conf.buffs.size / 32)
					end
				else
					local button = self.buffFrame.buff and self.buffFrame.buff[buffnum]
					if (button) then
						lastIcon = buffnum
						button.expireTime = nil
						button.big = nil
						button:Hide()
					else
						break
					end
				end
			end
			for buffnum = lastIcon + 1,40 do
				local button = self.buffFrame.buff and self.buffFrame.buff[buffnum]
				if (button) then
					button.expireTime = nil
					button:Hide()
				else
					break
				end
			end
		else
			self.buffFrame:Hide()
		end

		if (self.conf.debuffs.enable and maxDebuffs and maxDebuffs > 0) then
			self.debuffFrame:Show()
			lastIcon = 0
			for buffnum = 1,maxDebuffs do
				--local name, rank, debuff, debuffApplications, debuffType, duration, timeLeft = UnitDebuff(partyid, buffnum, curableOnly)

				local name, rank, debuff, debuffApplications, debuffType, duration, timeLeft, isMine
				if (partyid == "player") then
					local buffIndex, untilCancelled = GetPlayerBuff(buffnum, "HARMFUL")
					if (buffIndex > 0) then
						name, rank = GetPlayerBuffName(buffIndex)
						if (name) then
							debuff = GetPlayerBuffTexture(buffIndex)
							debuffApplications = GetPlayerBuffApplications(buffIndex)
							debuffType = GetPlayerBuffDispelType(buffIndex)
							timeLeft = GetPlayerBuffTimeLeft(buffIndex)
							duration = timeLeft
							isMine = true

							if (conf.buffs.countdown) then
								local match = format("%s_%s_%s", name, rank, debuff)
								for k = 1,40 do
									local name2, rank2, debuff2, debuffApplications2, debuffType2, duration2, timeLeft2 = UnitDebuff(partyid, k)
									if (not name2) then
										break
									end
									local match2 = format("%s_%s_%s", name2, rank2, debuff2)
									if (match == match2) then
										duration = duration2
										break
									end
								end
							end
						end
					end
				else
					name, rank, debuff, debuffApplications, debuffType, duration, timeLeft, isMine = XPerl_UnitDebuff(partyid, buffnum, curableOnly)
				end

				if (debuff) then
					local button = XPerl_GetBuffButton(self, buffnum, 1, true)
					button:SetAlpha(1)

					debuffs = debuffs + 1

					button.icon:SetTexture(debuff)
					if ((debuffApplications or 0) > 1) then
						button.count:SetText(debuffApplications)
						button.count:Show()
					else
						button.count:Hide()
					end

					local borderColor = DebuffTypeColor[(debuffType or "none")]
					button.border:SetVertexColor(borderColor.r, borderColor.g, borderColor.b)

					-- Handle cooldowns
					if (button.cooldown) then
						if (duration and conf.buffs.cooldown) then
							local start = GetTime() - (duration - timeLeft)
							XPerl_CooldownFrame_SetTimer(button.cooldown, start, duration, 1)
						else
							button.cooldown:Hide()
						end
					end

					lastIcon = buffnum
					button:Show()

					if (self.conf.debuffs.big and isMine) then
						debuffsMine = debuffsMine + 1
						button.big = true
						button:SetScale((self.conf.debuffs.size * 2) / 32)
					else
						button.big = nil
						button:SetScale(self.conf.debuffs.size / 32)
					end
				else
					local button = self.buffFrame.debuff and self.buffFrame.debuff[buffnum]
					if (button) then
						lastIcon = buffnum
						button.big = nil
						button.expireTime = nil
						button:Hide()
					else
						break
					end
				end
			end
			for buffnum = lastIcon + 1,40 do
				local button = self.buffFrame.debuff and self.buffFrame.debuff[buffnum]
				if (button) then
					button.expireTime = nil
					button:Hide()
				else
					break
				end
			end
		else
			self.debuffFrame:Hide()
		end
	end

	self.perlBuffs = buffs
	self.perlDebuffs = debuffs

	if (self.conf.buffs.big) then
		self.perlBuffsMine = buffsMine
		self.perlDebuffsMine = debuffsMine
	else
		self.perlBuffsMine, self.perlDebuffsMine = nil, nil
	end
end

-- XPerl_SetBuffSize
function XPerl_SetBuffSize(self)
	local sizeBuff = self.conf.buffs.size
	local sizeDebuff = (self.conf.debuffs and self.conf.debuffs.size) or (sizeBuff * (1 + self.buffSetup.debuffSizeMod))

	local buff
	for i = 1,40 do
		buff = self.buffFrame.buff and self.buffFrame.buff[i]
		if (buff) then
			buff:SetScale(sizeBuff / 32)
		end

		buff = self.buffFrame.debuff and self.buffFrame.debuff[i]
		if (buff) then
			buff:SetScale(sizeDebuff / 32)
		end

		buff = self.buffFrame.tempEnchant and self.buffFrame.tempEnchant[i]
		if (buff) then
			buff:SetScale(sizeBuff / 32)
		end
	end
end

-- XPerl_Update_RaidIcon
function XPerl_Update_RaidIcon(self, unit)
	local index = GetRaidTargetIndex(unit)
	if (index) then
		SetRaidTargetIconTexture(self, index)
		self:Show()
	else
		self:Hide()
	end
end

------------------------------------------------------------------------------
-- Flashing frames handler. Is hidden when there's nothing to do.
local FlashFrame = CreateFrame("Frame", "XPerl_FlashFrame")
FlashFrame.list = {}

-- XPerl_FrameFlash_OnUpdate(self, elapsed)
function XPerl_FrameFlash_OnUpdate(self, elapsed)
	for k,v in pairs(self.list) do
		if (k.frameFlash.out) then
			k.frameFlash.alpha = k.frameFlash.alpha - elapsed
			if (k.frameFlash.alpha < 0.2) then
				k.frameFlash.alpha = 0.2
				k.frameFlash.out = nil

				if (k.frameFlash.method == "out") then
					XPerl_FrameFlashStop(k)
				end
			end
		else
			k.frameFlash.alpha = k.frameFlash.alpha + elapsed
			if (k.frameFlash.alpha > 1) then
				k.frameFlash.alpha = 1
				k.frameFlash.out = true

				if (k.frameFlash.method == "in") then
					XPerl_FrameFlashStop(k)
				end
			end
		end

		if (k.frameFlash) then
			k:SetAlpha(k.frameFlash.alpha)
		end
	end
end

FlashFrame:SetScript("OnUpdate", XPerl_FrameFlash_OnUpdate)

-- XPerl_FrameFlash
function XPerl_FrameFlash(self)
	if (not FlashFrame.list[self]) then
		if (self.frameFlash) then
			error("X-Perl ["..self:GetName()..".frameFlash is set with no entry in FlashFrame.list]")
		end

		self.frameFlash = XPerl_GetReusableTable()
		self.frameFlash.out = true
		self.frameFlash.alpha = 1
		self.frameFlash.shown = self:IsShown()
		--self.frameFlash = {out = true, alpha = 1, shown = self:IsShown()}

		FlashFrame.list[self] = true
		FlashFrame:Show()
		self:Show()
	end
end

-- XPerl_FrameIsFlashing(self)
function XPerl_FrameIsFlashing(self)
	return self.frameFlash		--FlashFrame.list[self]
end

-- XPerl_FrameFlashStop
function XPerl_FrameFlashStop(self, method)
	if (not self.frameFlash) then
		return
	end

	if (method) then
		self.frameFlash.method = method
		return
	end

	if (not self.frameFlash.shown) then
		self:Hide()
	end

	XPerl_FreeTable(self.frameFlash)
	self.frameFlash = nil

	self:SetAlpha(1)

	FlashFrame.list[self] = nil

	if (not next(FlashFrame.list)) then
		FlashFrame:Hide()
	end
end

-- XPerl_ProtectedCall
function XPerl_ProtectedCall(func, self)
	if (func) then
		if (InCombatLockdown()) then
			if (self) then
				tinsert(XPerl_OutOfCombatQueue, {func, self})
			else
				tinsert(XPerl_OutOfCombatQueue, func)
			end
		else
			func(self)
		end
	end
end

-- nextMember(last)
function XPerl_NextMember(_, last)
	if (last) then
		local raidCount = GetNumRaidMembers()
		if (raidCount > 0) then
			local i = tonumber(strmatch(last, "^raid(%d+)"))
			if (i and i < raidCount) then
				i = i + 1
				local unitName, rank, group, level, _, unitClass, zone, online, dead = GetRaidRosterInfo(i)
				return "raid"..i, unitName, unitClass, group, zone, online, dead
			end
		else
			local partyCount = GetNumPartyMembers()
			if (partyCount > 0) then
				local id
				if (last == "player") then
					id = "party1"
				else
					local i = tonumber(strmatch(last, "^party(%d+)"))
					if (i and i < partyCount) then
						i = i + 1
						id = "party"..i
					end
				end

				if (id) then
					return id, UnitName(id), select(2, UnitClass(id)), 1, "", UnitIsConnected(id), UnitIsDeadOrGhost(id)
				end
			end
		end
	else
		if (GetNumRaidMembers() > 0) then
			local unitName, rank, group, level, _, unitClass, zone, online, dead = GetRaidRosterInfo(1)
			return "raid1", unitName, unitClass, group, zone, online, dead
		else
			return "player", UnitName("player"), select(2, UnitClass("player")), 1, GetRealZoneText(), 1, UnitIsDeadOrGhost("player")
		end
	end
end

-- PerlSetPortrait3D
local function XPerlSetPortrait3D(self, argUnit)
	self:ClearModel()
	self:SetUnit(argUnit)

	-- Workaround for the Dragonmaw illusion. They set the cameras wrong. 1 is 0, 0 is 1
	if (strfind(UnitName(argUnit), "Dragonmaw Peon") or strfind(UnitName(argUnit), "Dragonmaw Transporter") or XPerl_HasBuff(argUnit, "Dragonmaw Illusion")) then
		self:SetCamera(1)
	else
		self:SetCamera(0)
	end
end

-- XPerl_Unit_UpdatePortrait()
function XPerl_Unit_UpdatePortrait(self)
	if (self.conf.portrait) then
		local pf = self.portraitFrame
		if (self.conf.portrait3D and UnitIsVisible(self.partyid)) then
			pf.portrait:Hide()
			pf.portrait3D:Show()
			XPerlSetPortrait3D(pf.portrait3D, self.partyid)
		else
			pf.portrait:Show()
			pf.portrait3D:Hide()
			SetPortraitTexture(pf.portrait, self.partyid)
		end
	end
end

-- XPerl_Unit_UpdateLevel
function XPerl_Unit_UpdateLevel(self)
	local level = UnitLevel(self.partyid)
	local color = GetDifficultyColor(level)
	if (self.levelFrame) then
		self.levelFrame.text:SetTextColor(color.r,color.g,color.b)
		self.levelFrame.text:SetText(level)
	elseif (self.nameFrame.level) then
		if (level == 0) then
			level = ""
		end
		self.nameFrame.level:SetTextColor(color.r,color.g,color.b)
		self.nameFrame.level:SetText(level)
	end
end

-- XPerl_Unit_GetHealth
function XPerl_Unit_GetHealth(self)
	local partyid = self.partyid
	local hp, hpMax = XPerl_UnitHealth(partyid), UnitHealthMax(partyid)

	if (hp > hpMax) then
		if (UnitIsDeadOrGhost(partyid)) then
			hp = 0
		else
			hp = hpMax
		end
	end

	return hp or 0, hpMax or 1, (hpMax == 100)
end

-- XPerl_Unit_OnEnter
function XPerl_Unit_OnEnter(self)
	XPerl_PlayerTip(self)
	if (self.highlight) then
		self.highlight:Select()
	end
	XPerl_Tutorial(self.tutorialPage)

	if (self.statsFrame and self.statsFrame.healthBar and self.statsFrame.healthBar.text and not self.statsFrame.healthBar.text:IsShown()) then
		self.hideValues = true
		self.statsFrame.healthBar.text:Show()
		if (self.statsFrame.manaBar) then
			self.statsFrame.manaBar.text:Show()
		end
		if (self.statsFrame.xpBar and self.statsFrame.xpBar:IsShown()) then
			self.statsFrame.xpBar.text:Show()
		end
		if (self.statsFrame.repBar and self.statsFrame.repBar:IsShown()) then
			self.statsFrame.repBar.text:Show()
		end
	end
end

-- XPerl_Unit_OnLeave
function XPerl_Unit_OnLeave(self)
	XPerl_PlayerTipHide()
	if (self.highlight) then
		self.highlight:Deselect()
	end

	if (self.hideValues) then
		self.hideValues = nil

		self.statsFrame.healthBar.text:Hide()
		if (self.statsFrame.manaBar) then
			self.statsFrame.manaBar.text:Hide()
		end
		if (self.statsFrame.xpBar and self.statsFrame.xpBar:IsShown()) then
			self.statsFrame.xpBar.text:Hide()
		end
		if (self.statsFrame.repBar and self.statsFrame.repBar:IsShown()) then
			self.statsFrame.repBar.text:Hide()
		end
	end
end

-- XPerl_Unit_SetBuffTooltip
function XPerl_Unit_SetBuffTooltip(self)
	if (conf.tooltip.enableBuffs and XPerl_TooltipModiferPressed(true)) then
		if (not conf.tooltip.buffHideInCombat or not InCombatLockdown()) then
			local frame = self:GetParent():GetParent()
			local partyid = frame.partyid		--:GetAttribute("unit")
			if (partyid) then
				GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT",0,0)
				XPerl_TooltipSetUnitBuff(GameTooltip, partyid, self:GetID(), frame.conf.buffs.castable)
				XPerl_ToolTip_AddBuffDuration(self, partyid, self:GetID())
			end
		end
	end
end

-- XPerl_Unit_SetDeBuffTooltip
function XPerl_Unit_SetDeBuffTooltip(self)
	if (conf.tooltip.enableBuffs and XPerl_TooltipModiferPressed(true)) then
		if (not conf.tooltip.hideInCombat or not InCombatLockdown()) then
			local frame = self:GetParent():GetParent()
			local partyid = frame.partyid		--:GetAttribute("unit")
			if (partyid) then
				GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT",0,0)
				XPerl_TooltipSetUnitDebuff(GameTooltip, partyid, self:GetID(), frame.conf.debuffs.curable)
				XPerl_ToolTip_AddDebuffDuration(self, partyid, self:GetID(), frame.conf.debuffs.curable)
			end
		end
	end
end

-- XPerl_Unit_UpdateReadyState
function XPerl_Unit_UpdateReadyState(self)
	local status = conf.showReadyCheck and self.partyid and GetReadyCheckStatus(self.partyid)
	if (status) then
		self.statsFrame.ready:Show()
		if (status == "ready") then
			self.statsFrame.ready.check:SetTexture(READY_CHECK_READY_TEXTURE)
		elseif (status == "waiting") then
			self.statsFrame.ready.check:SetTexture(READY_CHECK_WAITING_TEXTURE)
		else -- "notready"
			self.statsFrame.ready.check:SetTexture(READY_CHECK_NOT_READY_TEXTURE)
		end
	else
		self.statsFrame.ready:Hide()
	end
end

-- XPerl_SwitchAnchor(self, new)
-- Changes anchored corner without actually moving the frame

-- XPerl_SwitchAnchor
function XPerl_SwitchAnchor(self, New)
	if (not self:GetPoint(2)) then
		local a1, f, a2, x, y = self:GetPoint(1)

		if (a1 == a2 and New ~= a1) then
			local parent = self:GetParent()
			local newV = strmatch(New, "TOP") or strmatch(New, "BOTTOM")
			local newH = strmatch(New, "LEFT") or strmatch(New, "RIGHT")

			if (newV == "TOP") then
				y = -(768 - (self:GetTop() * self:GetEffectiveScale())) / self:GetEffectiveScale()
			elseif (newV == "BOTTOM") then
				y = self:GetBottom()
			else
				y = self:GetBottom() + self:GetHeight() / 2
			end

			if (newH == "LEFT") then
				x = self:GetLeft()
			elseif (newV == "RIGHT") then
				x = self:GetRight()
			else
				x = self:GetLeft() + self:GetWidth() / 2
			end

			self:ClearAllPoints()
			self:SetPoint(New, f, New, x, y)
		end
	end
end

---------------------------------
-- Scaling frame corner thingy --
---------------------------------
-- Seems a convoluted way of doing things, rather than just anchoring bottomleft, topright.. but
-- doing that introduces a really ugly latency between the anchor moving and the frame scaling because
-- the OnSizeChanged event is fired on the frame after the actual resize took place.

local scaleIndication

local function scaleMouseDown(self)

	GameTooltip:Hide()

	if (self.resizable and IsShiftKeyDown()) then
		self.sizing = true
	elseif (self.scalable) then
		self.scaling = true
	end

	if (not scaleIndication) then
		scaleIndication = CreateFrame("Frame", nil, UIParent)
		scaleIndication:SetWidth(100)
		scaleIndication:SetHeight(18)
		scaleIndication.text = scaleIndication:CreateFontString(nil, "OVERLAY", "GameFontNormal")
		scaleIndication.text:SetAllPoints()
		scaleIndication.text:SetJustifyH("LEFT")
	end

	scaleIndication:Show()
	scaleIndication:ClearAllPoints()
	scaleIndication:SetPoint("LEFT", self, "RIGHT", 4, 0)

	if (self.scaling) then
		scaleIndication.text:SetFormattedText("%.1f%%", self.frame:GetScale() * 100)
	else
		scaleIndication.text:SetFormattedText("%dx%d", self.frame:GetWidth(), self.frame:GetHeight())
	end

	self.anchor:StartSizing(self.resizeTop and "TOPRIGHT")

	self.oldBdBorder = {self.frame:GetBackdropBorderColor()}
	self.frame:SetBackdropBorderColor(1, 1, 0.5, 1)
end

local function scaleMouseUp(self)
	self.anchor:StopMovingOrSizing()

	scaleIndication:Hide()

	XPerl_SavePosition(self.anchor)

	if (self.resizeTop) then
		XPerl_SwitchAnchor(self.anchor, "BOTTOMLEFT")
	end

	if (self.scaling) then
		if (self.onScaleChanged) then
			self:onScaleChanged(self.frame:GetScale())
		elseif (self.onSizeChanged) then
			self:onSizeChanged(self.frame:GetWidth(), self.frame:GetHeight())
		end
	end

	if (self.oldBdBorder) then
		self.frame:SetBackdropBorderColor(unpack(self.oldBdBorder))
		self.oldBdBorder = nil
	end

	self.scaling = nil
	self.sizing = nil
end

local function scaleMouseChange(self)
	if (self.corner.sizing) then
		self.corner.frame:SetWidth(self:GetWidth() / self.corner.frame:GetScale())
		self.corner.frame:SetHeight(self:GetHeight() / self.corner.frame:GetScale())

		self.corner.startSize.w = self.corner.frame:GetWidth()
		self.corner.startSize.h = self.corner.frame:GetHeight()

		if (scaleIndication and scaleIndication:IsShown()) then
			scaleIndication.text:SetFormattedText("|c00FFFF80%d|c00808080x|c00FFFF80%d", self.corner.frame:GetWidth(), self.corner.frame:GetHeight())
		end

	elseif (self.corner.scaling) then
		local w = self:GetWidth()
		if (w) then
			self.corner.scaling = nil
			local ratio = self.corner.frame:GetWidth() / self.corner.frame:GetHeight()
			local s = min(self.corner.maxScale, max(self.corner.minScale, w / self.corner.startSize.w))	-- New Scale

			w = self.corner.startSize.w * s		-- Set height and width of anchor window to match ratio of actual
			if (self.corner.resizeTop) then
				XPerl_SwitchAnchor(self, "BOTTOMLEFT")
				local bottom, left = self:GetBottom(), self:GetLeft()
				self:SetWidth(w)
				self:SetHeight(w / ratio)
			else
				self:SetWidth(w)
				self:SetHeight(w / ratio)
			end

			if (scaleIndication and scaleIndication:IsShown()) then
				scaleIndication.text:SetFormattedText("%.1f%%", s * 100)
			end

			self.corner.frame:SetScale(s)
			self.corner.scaling = true
		end
	end
end

-- scaleMouseEnter
local function scaleMouseEnter(self)
	self.tex:SetVertexColor(1, 1, 1, 1)
	GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
	if (self.scalable) then
		GameTooltip:SetText(XPERL_DRAGHINT1, nil, nil, nil, nil, 1)
	end
	if (self.resizable) then
		GameTooltip:AddLine(XPERL_DRAGHINT2, nil, nil, nil, 1)
	end
	GameTooltip:Show()
end

-- scaleMouseLeave
local function scaleMouseLeave(self)
	self.tex:SetVertexColor(1, 1, 1, 0.5)
	GameTooltip:Hide()
end

-- XPerl_RegisterScalableFrame
function XPerl_RegisterScalableFrame(self, anchorFrame, minScale, maxScale, resizeTop, resizable, scalable)
	if (scalable == nil) then
		scalable = true
	end

	if (not self.corner) then
		self.corner = CreateFrame("Frame", nil, self)
		self.corner:SetFrameLevel(self:GetFrameLevel() + 3)
		self.corner:EnableMouse(true)
		self.corner:SetScript("OnMouseDown", scaleMouseDown)
		self.corner:SetScript("OnMouseUp", scaleMouseUp)
		self.corner:SetScript("OnEnter", scaleMouseEnter)
		self.corner:SetScript("OnLeave", scaleMouseLeave)
		self.corner:SetHeight(12)
		self.corner:SetWidth(12)

		anchorFrame:SetScript("OnSizeChanged", scaleMouseChange)
		anchorFrame.corner = self.corner

		self.corner.tex = self.corner:CreateTexture(nil, "BORDER")
		self.corner.tex:SetTexture("Interface\\Addons\\XPerl\\images\\XPerl_Elements")
		self.corner.tex:SetAllPoints()
		self.corner.tex:SetVertexColor(1, 1, 1, 0.5)

		self.corner.anchor = anchorFrame
		self.corner.frame = self
	end

	self:SetMinResize(10, 10)

	self.corner.scalable = scalable
	self.corner.resizable = resizable
	self.corner.resizeTop = resizeTop
	self.corner.minScale = minScale or 0.4
	self.corner.maxScale = maxScale or 5
	self.corner.startSize = {w = self:GetWidth(), h = self:GetHeight()}

	local bgDef = self:GetBackdrop()

	self.corner:ClearAllPoints()
	if (resizeTop) then
		self.corner.tex:SetTexCoord(0.78125, 1, 0.5, 0.703125)
		self.corner:SetPoint("TOPRIGHT", -bgDef.insets.right, -bgDef.insets.top)
		self.corner:SetHitRectInsets(0, -6, -6, 0)		-- So the click area extends over the tooltip border
	else
		self.corner.tex:SetTexCoord(0.78125, 1, 0.78125, 1)
		self.corner:SetPoint("BOTTOMRIGHT", -bgDef.insets.right, bgDef.insets.bottom)
		self.corner:SetHitRectInsets(0, -6, 0, -6)		-- So the click area extends over the tooltip border
	end

	self.corner.scaling = true
	scaleMouseChange(anchorFrame)
	self.corner.scaling = nil
end

-- HealComm/LibQuickHealth interface
XPerl_UnitHealth = UnitHealth
XPerl_ExpectedUnitHealth = XPerl_UnitHealth
function XPerl_SetExpectedHealth()
end

if (LibStub or LoadAddOn("LibStub")) then
	local lqh = 1
	LoadAddOn("LibQuickHealth-2.0")
	local QuickHealth = LibStub("LibQuickHealth-2.0", true)
	if (QuickHealth) then
		lqh = 2
	else
		LoadAddOn("LibQuickHealth-1.0")
		QuickHealth = LibStub("LibQuickHealth-1.0", true)
	end

	if (QuickHealth) then
		QuickHealth.RegisterCallback("XPerlHealthCallback", "HealthUpdated",
			function(event, GUID, newHealth)
				if (XPerl_Raid_Frame and conf.raid.enable and GetNumRaidMembers() > 0) then
					local f = XPerl_Raid_GetUnitFrameByGUID(GUID)
					if (f) then
						XPerl_Raid_UpdateHealth(f)
					end
				end

				if (XPerl_party1 and GetNumPartyMembers() > 0) then
					local f = XPerl_Party_GetUnitFrameByGUID(GUID)
					if (f) then
						XPerl_Party_UpdateHealth(f)
					end
				end

				if (XPerl_Player and UnitGUID("player") == GUID) then
					XPerl_Player_UpdateHealth(XPerl_Player)
				end

				if (XPerl_Player_Pet and UnitGUID("pet") == GUID) then
					XPerl_Player_Pet_UpdateHealth(XPerl_Player_Pet)
				end

				if (XPerl_Target and UnitGUID("target") == GUID) then
					XPerl_Target_SetHealth(XPerl_Target)
				end

				if (XPerl_Focus and UnitGUID("focus") == GUID) then
					XPerl_Target_SetHealth(XPerl_Focus)
				end
			end
		)

		if (lqh == 2) then
			XPerl_UnitHealth = QuickHealth.UnitHealth
		else
			function XPerl_UnitHealth(...)
				return QuickHealth:UnitHealth(...)
			end
		end
	end

	-- HealComm interface
	LoadAddOn("LibHealComm-3.0")
	local HealComm = LibStub("LibHealComm-3.0", true)

	if (HealComm) then
		local myHeals = {}
		local hcMiniFrame = CreateFrame("Frame", nil)
		hcMiniFrame:RegisterEvent("RAID_ROSTER_UPDATE")
		hcMiniFrame:RegisterEvent("PARTY_MEMBERS_CHANGED")
		hcMiniFrame:SetScript("OnEvent", function()
			local temp = new()

			if (GetNumRaidMembers() > 0) then
				for i = 1,GetNumRaidMembers() do
					local id = "raid"..i
					local guid = UnitGUID(id)
					if (guid) then
						temp[guid] = true
						id = "raidpet"..i
						guid = UnitGUID(id)
						if (guid) then
							temp[guid] = true
						end
					end
				end
			elseif (GetNumPartyMembers() > 0) then
				for i = 0,GetNumPartyMembers() do
					local id = i == 0 and "player" or "party"..i
					local guid = UnitGUID(id)
					if (guid) then
						temp[guid] = true
						id = i == 0 and "pet" or "raidpet"..i
						guid = UnitGUID(id)
						if (guid) then
							temp[guid] = true
						end
					end
				end
			else
				myHeals = {}
			end

			for guid in pairs(myHeals) do
				if (not temp[guid]) then
					myHeals[guid] = nil
				end
			end

			del(temp)
		end)

		local function unitFullName(unit)
		    local name, realm = UnitName(unit)
	    	if (realm and realm ~= "") then
		        return name .. "-" .. realm
    		else
	        	return name
	    	end
		end

		local guidFrame = CreateFrame("Frame")
		guidFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
		local playerGUID, targetGUID, focusGUID, petGUID, targettargetGUID

		guidFrame:SetScript("OnEvent", function(self, event, ...)
			local f = self[event]
			if (f) then f(self, ...) end
		end)
		
		function guidFrame:PLAYER_ENTERING_WORLD()
			self:UnregisterEvent("PLAYER_ENTERING_WORLD")
			self:RegisterEvent("PLAYER_LEAVING_WORLD")
			self:RegisterEvent("PLAYER_TARGET_CHANGED")
			self:RegisterEvent("PLAYER_FOCUS_CHANGED")
			self:RegisterEvent("UNIT_PET")
			playerGUID = UnitGUID("player")
			self:GetGUIDs()
			self.PLAYER_ENTERING_WORLD = nil
		end

		function guidFrame:PLAYER_LEAVING_WORLD()
			targetGUID, focusGUID, targettargetGUID = nil, nil, nil
		end

		function guidFrame:GetGUIDs()
			petGUID = UnitGUID("pet")
			targetGUID = UnitGUID("target")
			targettargetGUID = UnitGUID("targettarget")
			focusGUID = UnitGUID("focus")
		end

		guidFrame.PLAYER_TARGET_CHANGED = guidFrame.GetGUIDs
		guidFrame.PLAYER_FOCUS_CHANGED = guidFrame.GetGUIDs

		function guidFrame:UNIT_PET(unit)
			if (unit == "player") then
				self:GetGUIDs()
			end
		end

		local function UpdateIncomingHeals(healerFullName, healSize, ...)
			for i = 1,select("#",...) do
				local name = select(i, ...)
				local guid = UnitGUID(name)
				local f

				if (guid and healSize and UnitIsUnit("player", healerFullName)) then
					myHeals[guid] = healSize
				end

				if (XPerl_Raid_GetUnitFrameByGUID) then
					f = XPerl_Raid_GetUnitFrameByGUID(guid)
					if (f) then
						XPerl_Raid_UpdateHealth(f)
					end
				end

				if (XPerl_Party_GetUnitFrameByGUID) then
					f = XPerl_Party_GetUnitFrameByGUID(guid)
					if (f) then
						XPerl_Party_UpdateHealth(f)
					end
				end

				if (XPerl_Player_UpdateHealth and guid == playerGUID) then
					XPerl_Player_UpdateHealth(XPerl_Player)
				elseif (XPerl_Player_Pet_UpdateHealth and guid == petGUID) then
					XPerl_Player_Pet_UpdateHealth(XPerl_Player)
				elseif (XPerl_Party_Pet_GetUnitFrameByGUID) then
					f = XPerl_Party_Pet_GetUnitFrameByGUID(guid)
					if (f) then
						XPerl_Party_Pet_UpdateHealth(f)
					end
				end

				if (XPerl_Target_SetHealth and guid == targetGUID) then
					XPerl_Target_SetHealth(XPerl_Target)
				end
				if (XPerl_Target_SetHealth and guid == focusGUID) then
					XPerl_Target_SetHealth(XPerl_Focus)
				end
				if (XPerl_Target_SetHealth and guid == targettargetGUID) then
					XPerl_Target_SetHealth(XPerl_TargetTarget)
				end
			end
		end

		-- XPerl_ExpectedUnitHealth
		function XPerl_ExpectedUnitHealth(unit)
			local health = XPerl_UnitHealth(unit)
			local incoming = HealComm:UnitIncomingHealGet(unit, GetTime() + 100.0) or 0
			incoming = incoming + (myHeals[UnitGUID(unit)] or 0)
			if (incoming > 0) then
				local effective = incoming * HealComm:UnitHealModifierGet(unit)
				return health + effective
			end

			return health
		end

		-- XPerl_SetExpectedHealth
		function XPerl_SetExpectedHealth(self)
			local bar = self.statsFrame.expectedHealth
			if (bar) then
				local expectedHealth = XPerl_ExpectedUnitHealth(self.partyid)
				if (expectedHealth == XPerl_UnitHealth(self.partyid)) then
					bar:Hide()
				else
					local healthMax = UnitHealthMax(self.partyid)
					bar:Show()
					bar:SetMinMaxValues(0, healthMax)
					bar:SetValue(expectedHealth)
				end
			end
		end

		local function xperl_HealComm_DirectHealStart(event, healerFullName, healSize, endTime, ...)
			--ChatFrame1:AddMessage(format("xperl_HealComm_DirectHealStart(%s, %s, %s, %s, %s)", tostring(event), tostring(healerFullName), tostring(healSize), tostring(endTime), tostring(...)))
			UpdateIncomingHeals(healerFullName, healSize, ...)
		end
		local function xperl_HealComm_DirectHealStop(event, healerFullName, healSize, succeeded, ...)
			--ChatFrame1:AddMessage(format("xperl_HealComm_DirectHealStop(%s, %s, %s, %s, %s)", tostring(event), tostring(healerFullName), tostring(healSize), tostring(endTime), tostring(...)))
			UpdateIncomingHeals(healerFullName, 0, ...)
		end
		local function xperl_HealComm_DirectHealDelayed(event, healerFullName, healSize, endTime, ...)
			--ChatFrame1:AddMessage(format("xperl_HealComm_DirectHealDelayed(%s, %s, %s, %s, %s)", tostring(event), tostring(healerFullName), tostring(healSize), tostring(endTime), tostring(...)))
			UpdateIncomingHeals(healerFullName, healSize, ...)
		end
		local function xperl_HealComm_HealModifierUpdate(event, unit, targetFullName, healModifier)
			--ChatFrame1:AddMessage(format("xperl_HealComm_HealModifierUpdate(%s, %s, %s, %s, %s)", tostring(event), tostring(healerFullName), tostring(healSize), tostring(endTime)))
			UpdateIncomingHeals(unit, nil, targetFullName)
		end

		HealComm.RegisterCallback("XPerlHealCommCallback", "HealComm_DirectHealStart", xperl_HealComm_DirectHealStart)
		HealComm.RegisterCallback("XPerlHealCommCallback", "HealComm_DirectHealStop", xperl_HealComm_DirectHealStop)
		HealComm.RegisterCallback("XPerlHealCommCallback", "HealComm_DirectHealDelayed", xperl_HealComm_DirectHealDelayed)
		HealComm.RegisterCallback("XPerlHealCommCallback", "HealComm_HealModifierUpdate", xperl_HealComm_HealModifierUpdate)
	end
end
-- End of HealComm/LibQuickHealth
