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

local XPerl_Raid_Events = {}
local RaidGroupCounts = {0,0,0,0,0,0,0,0,0}
local myGroup = 0
local FrameArray = {}		-- List of raid frames indexed by raid ID
local RaidPositions = {}	-- Back-matching of unit names to raid ID
local ResArray = {}		-- List of currently active resserections in progress
local buffUpdates = {}		-- Queue for buff updates after a roster change
local raidLoaded
local rosterUpdated
local rosterUpdateCount = 0
local rosterUpdateFirstAt
local rosterUpdateLastAt
local percD = "%d"..PERCENT_SYMBOL
local lastNamesList, lastName, lastWith, lastNamesCount		-- Stores with/without buff list (OnUpdate optimization)

local ROSTER_SINGLE_DELAY = 0.05
local ROSTER_BURST_QUIET = 0.12
local ROSTER_BURST_MAX = 0.35

local new, del, copy = XPerl_GetReusableTable, XPerl_FreeTable, XPerl_CopyTable

local format = format
local strsub = strsub
local GetNumRaidMembers = GetNumRaidMembers
local UnitHealth = UnitHealth
local UnitHealthMax = UnitHealthMax
local UnitIsConnected = UnitIsConnected
local UnitIsDead = UnitIsDead
local UnitIsDeadOrGhost = UnitIsDeadOrGhost
local UnitIsGhost = UnitIsGhost
local UnitMana = UnitMana
local UnitManaMax = UnitManaMax
local UnitName = UnitName
local UnitPowerType = UnitPowerType

local function ClearRosterUpdateBurst()
	rosterUpdated = nil
	rosterUpdateCount = 0
	rosterUpdateFirstAt = nil
	rosterUpdateLastAt = nil
end

local function QueueRosterUpdate()
	local now = GetTime()
	if (rosterUpdated and rosterUpdateLastAt and now - rosterUpdateLastAt <= ROSTER_BURST_QUIET) then
		rosterUpdateCount = rosterUpdateCount + 1
	else
		rosterUpdateCount = 1
		rosterUpdateFirstAt = now
	end

	rosterUpdated = true
	rosterUpdateLastAt = now
end

local function ShouldFlushRosterUpdate()
	if (not rosterUpdated) then
		return false
	end

	local now = GetTime()
	if (rosterUpdateCount <= 1) then
		return now - (rosterUpdateFirstAt or now) >= ROSTER_SINGLE_DELAY
	end

	if (now - (rosterUpdateLastAt or now) >= ROSTER_BURST_QUIET) then
		return true
	end

	return now - (rosterUpdateFirstAt or now) >= ROSTER_BURST_MAX
end

-- TODO - Watch for:   ERR_FRIEND_OFFLINE_S = "%s has gone offline."

local conf, rconf
XPerl_RequestConfig(function(newConf) conf = newConf rconf = conf.raid end, "$Revision: 188 $")

XPERL_RAIDGRP_PREFIX	= "XPerl_Raid_Grp"

-- Hold some raid roster information (AFK, DND etc.)
-- Is also stored between sessions to maintain timers and flags
XPerl_Roster = {}

-- Uses some variables from FrameXML\RaidFrame.lua:
-- MAX_RAID_MEMBERS = 40
-- NUM_RAID_GROUPS = 8
-- MEMBERS_PER_RAID_GROUP = 5

-- This array will be built up on the fly whenever we pick up a localized class name that we don't already have.
-- The UnitClass function returns both english and localized versions, so it won't matter where the mod is run.
local ClassNames = {}
function XPerl_Raid_GetLocalisedClassName(class)
	return ClassNames[class]
end

local localGroups = {WARRIOR = XPERL_LOC_CLASS_WARRIOR, MAGE = XPERL_LOC_CLASS_MAGE, PRIEST = XPERL_LOC_CLASS_PRIEST, WARLOCK = XPERL_LOC_CLASS_WARLOCK, DRUID = XPERL_LOC_CLASS_DRUID, ROGUE = XPERL_LOC_CLASS_ROGUE, HUNTER = XPERL_LOC_CLASS_HUNTER, SHAMAN = XPERL_LOC_CLASS_SHAMAN, PALADIN = XPERL_LOC_CLASS_PALADIN}
--local groupIndex = {WARRIOR = 1, MAGE = 2, PRIEST = 3, WARLOCK = 4, DRUID = 5, ROGUE = 6, HUNTER = 7, SHAMAN = 8, PALADIN = 9}

local resSpells  = {
	[GetSpellInfo(2006)] = true,			-- ["Resurrection"] = true,
	[GetSpellInfo(2008)] = true,			-- ["Ancestral Spirit"] = true,
	[GetSpellInfo(20484)] = true,			-- ["Rebirth"] = true,
	[GetSpellInfo(7328)] = true,			-- ["Redemption"] = true
}

local hotSpells = XPERL_HIGHLIGHT_SPELLS.hotSpells

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

local raidHeaders = {}

-- XPerl_Raid_OnLoad
function XPerl_Raid_OnLoad(self)
	local events = {"CHAT_MSG_ADDON",	-- "CHAT_MSG_RAID", "CHAT_MSG_RAID_LEADER", "CHAT_MSG_PARTY",
			"PLAYER_ENTERING_WORLD", "VARIABLES_LOADED", "RAID_ROSTER_UPDATE", "UNIT_FACTION",
			"UNIT_DYNAMIC_FLAGS", "UNIT_FLAGS", "UNIT_AURA", "UNIT_HEALTH", "UNIT_MAXHEALTH",
			"UNIT_NAME_UPDATE", "PLAYER_FLAGS_CHANGED", "UNIT_COMBAT", "UNIT_SPELLCAST_START",
			"UNIT_SPELLCAST_STOP", "UNIT_SPELLCAST_FAILED", "UNIT_SPELLCAST_INTERRUPTED",
			"READY_CHECK", "READY_CHECK_CONFIRM", "READY_CHECK_FINISHED", "RAID_TARGET_UPDATE",
			"PLAYER_LOGIN"
			}
			
	for i,event in pairs(events) do
		self:RegisterEvent(event)
	end

	for i = 1,9 do			-- Fix for WoW 2.1 UNIT_NAME_UPDATE issue
		getglobal("XPerl_Raid_Grp"..i):UnregisterEvent("UNIT_NAME_UPDATE")
		tinsert(raidHeaders, getglobal(XPERL_RAIDGRP_PREFIX..i))
	end

	self.time = 0
	self.Array = {}

	XPerl_RegisterOptionChanger(function()
		if (raidLoaded) then
			XPerl_RaidTitles()
		end

		XPerl_Raid_Set_Bits(XPerl_Raid_Frame)

		if (raidLoaded) then
			SkipHighlightUpdate = true
			XPerl_Raid_UpdateDisplayAll()
			SkipHighlightUpdate = nil
		end
	end, "Raid")

	XPerl_Raid_OnLoad = nil
end

-- XPerl_Raid_HeaderOnLoad
function XPerl_Raid_HeaderOnLoad(self)
	self:RegisterForDrag("LeftButton")
	self.text = getglobal(self:GetName().."TitleText")
	self.virtual = getglobal(self:GetName().."Virtual")
	XPerl_RegisterUnitText(self.text)
	--XPerl_SavePosition(self, true)
end

-- CreateManaBar
local function CreateManaBar(self)
	local sf = self.statsFrame
	sf.manaBar = CreateFrame("StatusBar", sf:GetName().."manaBar", sf, "XPerlRaidStatusBar")
	sf.manaBar:SetScale(0.7)
	sf.manaBar:SetWidth(70)
	sf.manaBar:SetPoint("TOPLEFT", sf.healthBar, "BOTTOMLEFT", 0, 0)
	sf.manaBar:SetPoint("BOTTOMRIGHT", sf.healthBar, "BOTTOMRIGHT", 0, -7)
	sf.manaBar:SetStatusBarColor(0, 0, 1)
end

-- Setup1RaidFrame
local function Setup1RaidFrame(self)
	if (rconf.mana) then
		if (not self.statsFrame.manaBar) then
			CreateManaBar(self)
		end

		if (not InCombatLockdown()) then
			self:SetHeight(43)
		end
		self.statsFrame:SetHeight(26)
		self.statsFrame.manaBar:Show()
	else
		if (not InCombatLockdown()) then
			self:SetHeight(38)
		end
		self.statsFrame:SetHeight(21)
		if (self.statsFrame.manaBar) then
			self.statsFrame.manaBar:Hide()
		end
	end

	if (rconf.percent) then
		self.statsFrame.healthBar.text:Show()
		if (self.statsFrame.manaBar) then
			self.statsFrame.manaBar.text:Show()
		end
	else
		self.statsFrame.healthBar.text:Hide()
		if (self.statsFrame.manaBar) then
			self.statsFrame.manaBar.text:Hide()
		end
	end

	if (XPerl_Voice) then
		XPerl_Voice:Register(self, true)
	end
end

-- ShowPopup
function XPerl_Raid_ShowPopup(self)
	local me = self
	if (not self.nameFrame and self:GetParent().nameFrame == self) then
		me = self:GetParent()
	end

	HideDropDownMenu(1)
	FriendsDropDown.initialize = XPerl_RaidFrameDropDown_Initialize
	FriendsDropDown.displayMode = "MENU"

	-- Must use 'this' for these:
	this.unit = SecureButton_GetUnit(me)
	this.name, this.server = UnitName(this.unit)
	this.id = strmatch(this.unit, "(%d+)")

	ToggleDropDownMenu(1, nil, FriendsDropDown, me.statsFrame:GetName(), 0, 0)

	-- this.unit, this.name, this.server, this.id = nil, nil, nil, nil
end

-- SetFrameArray
local function SetFrameArray(self, value)
	for k,v in pairs(FrameArray) do
		if (v == self) then
			FrameArray[k] = nil
			break
		end
	end

	self.partyid = value

	if (value) then
		FrameArray[value] = self
	end
end

-- XPerl_Raid_UpdateName
local function XPerl_Raid_UpdateName(self)
	local partyid = self:GetAttribute("unit")
	if (not partyid) then
		partyid = SecureButton_GetUnit(self)
		if (not partyid) then
			self.lastName, self.lastID = nil, nil
			return
		end
	end

	local name = UnitName(partyid)
	self.lastName, self.lastID = name, partyid -- These stored, so we can at least make a small effort in reducing workload on attribute changes.

	if (name) then
                self.nameFrame.text:SetText(name)

		if (self.pet) then
			local color = conf.ColourReactionNone
			self.nameFrame.text:SetTextColor(color.r, color.g, color.b)
		else
			XPerl_ColourFriendlyUnit(self.nameFrame.text, partyid)
		end
	end
end

-- XPerl_Raid_CheckFlags
local function XPerl_Raid_CheckFlags(partyid)

	local unitName = UnitName(partyid)
	local resser

	for i,name in pairs(ResArray) do
		if (name == unitName) then
			resser = i
			break
		end
	end

	if (resser) then
		-- Verify they're dead..
		if (UnitIsDeadOrGhost(partyid)) then
			return {flag = resser..XPERL_RAID_RESSING, bgcolor = {r = 0, g = 0.5, b = 1}}
		end

		ResArray[resser] = nil
	end

	local unitInfo = XPerl_Roster[unitName]
	if (unitInfo) then
		if (unitInfo.ressed) then
			if (UnitIsDead(partyid)) then
				if (unitInfo.ressed == 2) then
					return {flag = XPERL_LOC_SS_AVAILABLE, bgcolor = {r = 0, g = 1, b = 0.5}}
				elseif (unitInfo.ressed == 3) then
					return {flag = XPERL_LOC_ACCEPTEDRES, bgcolor = {r = 0, g = 0.5, b = 1}}
				else
					return {flag = XPERL_LOC_RESURRECTED, bgcolor = {r = 0, g = 0.5, b = 1}}
				end
			else
				unitInfo.ressed = nil
				XPerl_Raid_UpdateManaType(FrameArray[partyid], true)
			end

		elseif (unitInfo.afk) then
			if (UnitIsAFK(partyid)) then
				if (conf.showAFK) then
					return {flag = XPERL_RAID_AFK}
				end
			else
				unitInfo.afk = nil
			end
		end
	end
end

-- XPerl_Raid_UpdateManaType
function XPerl_Raid_UpdateManaType(self, skipFlags)
	if (rconf.mana) then
		local partyid = self:GetAttribute("unit")
		if (not partyid) then
			partyid = SecureButton_GetUnit(self)
			if (not partyid) then
				return
			end
			return
		end

		local flags
		if (not skipFlags) then
			flags = XPerl_Raid_CheckFlags(partyid)
		end
		if (not flags) then
			XPerl_SetManaBarType(self)
		end
	end
end

-- XPerl_Raid_ShowFlags
local function XPerl_Raid_ShowFlags(self, flags)
	local r, g, b
	local flag
	if (type(flags) == "string") then
		flag = flags
		flags = nil
	else
		flag = flags.flag
	end

	if (flags and flags.bgcolor) then
		r, g, b = flags.bgcolor.r, flags.bgcolor.g, flags.bgcolor.b
	else
		r, g, b = 0.5, 0.5, 0.5
	end

	self.statsFrame:SetGrey(r, g, b)

	if (flags and flags.color) then
		r, g, b = flags.color.r, flags.color.g, flags.color.b
	else
		r, g, b = 1, 1, 1
	end

	self.statsFrame.healthBar.text:SetText(flag)
	self.statsFrame.healthBar.text:SetTextColor(r, g, b)
	self.statsFrame.healthBar.text:Show()
	del(flags)
end

-- XPerl_Raid_UpdateHealth
function XPerl_Raid_UpdateHealth(self)
	local partyid = self.partyid
	if (not partyid) then
		return
	end

	local health = XPerl_UnitHealth(partyid)
	local healthmax = UnitHealthMax(partyid)

	if (health > healthmax) then
		-- New glitch with 1.12.1
		if (UnitIsDeadOrGhost(partyid)) then
			health = 0
		else
			health = healthmax
		end
	end

	self.statsFrame.healthBar:SetMinMaxValues(0, healthmax)
	if (conf.bar.inverse) then
		self.statsFrame.healthBar:SetValue(healthmax - health)
	else
		self.statsFrame.healthBar:SetValue(health)
	end

	if (not rconf.percent) then
		if (self.statsFrame.healthBar.text:IsShown()) then
			self.statsFrame.healthBar.text:Hide()
		end
	end

	XPerl_SetExpectedHealth(self)

	local name = UnitName(partyid)
	local myRoster = XPerl_Roster[name]
	if (name and UnitIsConnected(partyid)) then
		self.disco = nil
		if (myRoster and myRoster.fd) then
			if (not UnitIsFeignDeath(partyid)) then
				myRoster.fd = nil
			end
		end

		local flags = XPerl_Raid_CheckFlags(partyid)
		if (flags) then
			XPerl_Raid_ShowFlags(self, flags)

			if (UnitIsDeadOrGhost(partyid)) then
				self.dead = true
				XPerl_Raid_UpdateName(self)
			end
			return

		elseif (UnitIsDead(partyid) or (myRoster and myRoster.fd and conf.showFD)) then
			if (myRoster and myRoster.fd) then		--UnitIsFeignDeath(partyid)) then
				XPerl_NoFadeBars(true)
				self.statsFrame.healthBar.text:SetText(XPERL_LOC_FEIGNDEATH)
				--XPerl_SetSmoothBarColor(self.statsFrame.healthBar, 0)
				self.statsFrame:SetGrey()
				XPerl_NoFadeBars()
			else
				self.dead = true
				XPerl_Raid_ShowFlags(self, XPERL_LOC_DEAD)
				XPerl_Raid_UpdateName(self)
			end

		elseif (UnitIsGhost(partyid)) then
			self.dead = true
			XPerl_Raid_ShowFlags(self, XPERL_LOC_GHOST)
			XPerl_Raid_UpdateName(self)

		else
			if (self.dead or (myRoster and ((myRoster.fd and conf.showFD) or myRoster.ressed))) then
				XPerl_Raid_UpdateManaType(self, true)
			end
			self.dead = nil

			local percentHp = health / healthmax
			if (rconf.healerMode.enable) then
				self.statsFrame.healthBar.text:SetText(-(healthmax - health))
			else
				if (rconf.values) then
					self.statsFrame.healthBar.text:SetFormattedText("%d/%d", health, healthmax)
				else
					self.statsFrame.healthBar.text:SetFormattedText(percD, (percentHp + 0.005) * 100)
				end
			end

			-- XPerl_SetSmoothBarColor(self.statsFrame.healthBar, percentHp)
			XPerl_ColourHealthBar(self, percentHp, partyid)

			if (self.statsFrame.greyMana) then
				self.statsFrame.greyMana = nil
				if (myRoster) then
					myRoster.resCount = nil
					myRoster.ressed = nil
				end
				XPerl_Raid_UpdateManaType(self, true)
			end
		end
	else
		self.disco = true
		self.dead = nil
		XPerl_Raid_ShowFlags(self, XPERL_LOC_OFFLINE)

		if (name and myRoster and not myRoster.offline) then
			myRoster.offline = GetTime()
			myRoster.afk = nil
			myRoster.dnd = nil
		end
	end
end

-- XPerl_Raid_UpdateMana
local function XPerl_Raid_UpdateMana(self)
	if (rconf.mana) then
		if (not self.statsFrame.manaBar) then
			CreateManaBar(self)
		end

		local partyid = self.partyid
		if (not partyid) then
			return
		end

		local mana = UnitMana(partyid)
		local manamax = UnitManaMax(partyid)

		if (rconf.manaPercent and UnitPowerType(partyid) == 0 and not self.pet) then
			if (rconf.values) then			-- TODO rconf.manavalues
			 	self.statsFrame.manaBar.text:SetFormattedText("%d/%d", mana, manamax)
		 	else
		 		local pmanaPct = (mana * 100.0) / manamax
				self.statsFrame.manaBar.text:SetFormattedText(percD, pmanaPct)	-- XPerl_Percent[floor(pmanaPct)])
			end
		else
			self.statsFrame.manaBar.text:SetText("")
		end

		self.statsFrame.manaBar:SetMinMaxValues(0, manamax)
		self.statsFrame.manaBar:SetValue(mana)
	end
end

-- onAttrChanged
local function onAttrChanged(self, name, value)
	if (name == "unit") then
		if (value) then
			SetFrameArray(self, value)
			if (self.lastID ~= value or self.lastName ~= UnitName(value)) then
				XPerl_Raid_UpdateDisplay(self)
			end
		else
			buffUpdates[self] = nil
			SetFrameArray(self)
			self.lastID = nil
			self.lastName = nil
		end
	end
end

-- XPerl_Raid_Single_OnLoad
function XPerl_Raid_Single_OnLoad(self)
	XPerl_SetChildMembers(self)
	self:RegisterForClicks("AnyUp")

	self.edgeFile = "Interface\\Addons\\XPerl\\images\\XPerl_ThinEdge"
	self.edgeSize = 10
	self.edgeInsets = 2

	XPerl_RegisterHighlight(self.highlight, 2)

	XPerl_RegisterPerlFrames(self, {self.nameFrame, self.statsFrame})
	self.FlashFrames = {self.nameFrame, self.statsFrame}

	--self:SetScript("OnAttributeChanged", onAttrChanged)
	--XPerl_RegisterClickCastFrame(self)
	--XPerl_RegisterClickCastFrame(self.nameFrame)
end

-- XPerl_Raid_CombatFlash
local function XPerl_Raid_CombatFlash(self, elapsed, argNew, argGreen)
	if (XPerl_CombatFlashSet(self, elapsed, argNew, argGreen)) then
		XPerl_CombatFlashSetFrames(self)
	end
end

-- XPerl_GetRaidPosition
function XPerl_GetRaidPosition(findName)
	return RaidPositions[findName]
end

-- XPerl_Raid_GetUnitFrameByName
function XPerl_Raid_GetUnitFrameByName(findName)
	-- Used by teamspeak module
	local id = RaidPositions[findName]
	if (id) then
		return FrameArray[id]
	end
end

-- XPerl_Raid_GetUnitFrameByName
function XPerl_Raid_GetUnitFrameByUnit(unit)
	return FrameArray[unit]
end

-- XPerl_Raid_GetFrameArray
function XPerl_Raid_GetFrameArray()
	return FrameArray
end

-- UpdateUnitByName
local function UpdateUnitByName(name,flagsOnly)
	local id = RaidPositions[name]
	if (id) then
		local frame = FrameArray[id]
		if (frame and frame:IsShown()) then
			if (flagsOnly) then
				XPerl_Raid_UpdateHealth(frame)
			else
				XPerl_Raid_UpdateDisplay(frame)
			end
		end
	end
end

-- XPerl_Raid_HighlightCallback(updateName)
local function XPerl_Raid_HighlightCallback(self, updateGUID)
	local f = XPerl_Raid_GetUnitFrameByGUID(updateGUID)
	if (f) then
		XPerl_Highlight:SetHighlight(f, updateGUID)
	end
end

-- GetBuffButton(self, buffnum, debuff, createIfAbsent)
-- debuff must be 1 or 0, as it's used in size calc
local buffIconCount = 0
local function GetBuffButton(self, buffnum, createIfAbsent)

	local button = self.buffFrame.buff and self.buffFrame.buff[buffnum]

	if (not button and createIfAbsent) then
		buffIconCount = buffIconCount + 1
		button = CreateFrame("Button", "XPerlRBuff"..buffIconCount, self.buffFrame, "XPerl_BuffTemplate")
		button:SetID(buffnum)

		if (not self.buffFrame.buff) then
			self.buffFrame.buff = {}
		end
		self.buffFrame.buff[buffnum] = button

		button:SetHeight(10)
		button:SetWidth(10)

		button.icon:SetTexCoord(0.078125, 0.921875, 0.078125, 0.921875)

		button:SetScript("OnEnter", XPerl_Raid_SetBuffTooltip)
		button:SetScript("OnLeave", function()
						lastNamesList, lastName, lastWith = nil, nil, nil
						XPerl_PlayerTipHide()
					end)
	end

	return button
end

-- GetShowCast
local function GetShowCast(self)
	if (rconf.buffs.enable) then
		return "b", rconf.buffs.castable
	elseif (rconf.debuffs.enable) then
		return "d", rconf.buffs.castable
	end
end

-- UpdateBuffs
local function UpdateBuffs(self)
	local partyid = self.partyid
	if (not partyid) then
		return
	end

	local bf = self.buffFrame

	XPerl_CheckDebuffs(self, partyid)
	XPerl_ColourFriendlyUnit(self.nameFrame.text, partyid)

	local buffCount = 0
	local maxBuff = 8 - ((abs(1 - (rconf.mana or 0)) * 2) * (rconf.buffs.right or 0))

	local show, cureCast = GetShowCast(self)
	self.debuffsForced = nil
	if (show) then
		if (show == "b") then
			if (rconf.buffs.untilDebuffed) then
				local name, rank, buff = XPerl_UnitDebuff(partyid, 1, cureCast)
				if (name) then
					self.debuffsForced = true
					show = "d"
				end
			end
		end

		for buffnum=1,maxBuff do
			local name, rank, buff, count
			if (show == "b") then
				name, rank, buff = XPerl_UnitBuff(partyid, buffnum, cureCast)
			else
				name, rank, buff = XPerl_UnitDebuff(partyid, buffnum, cureCast)
			end
			local button = GetBuffButton(self, buffnum, buff)	-- 'buff' flags whether to create icon
			if (button) then
				if (buff) then
					buffCount = buffCount + 1

					button.icon:SetTexture(buff)
					if (not button:IsShown()) then
						button:Show()
					end
				else
					if (button:IsShown()) then
						button:Hide()
					end
				end
			end
		end
		for buffnum=maxBuff+1,8 do
                        local button = bf.buff and bf.buff[buffnum]
			if (button) then
				if (button:IsShown()) then
	 				button:Hide()
				end
			end
		end
	end

	if (buffCount > 0) then
		bf:ClearAllPoints()
		if (not bf:IsShown()) then
			bf:Show()
		end
		local id = self:GetID()

		if (rconf.buffs.right) then
			bf:SetPoint("BOTTOMLEFT", self.statsFrame, "BOTTOMRIGHT", -1, 1)

			if (rconf.buffs.inside) then
				if (buffCount > 3 + (rconf.mana or 0)) then
					self.statsFrame:SetWidth(60)
				else
					self.statsFrame:SetWidth(70)
				end
			else
				self.statsFrame:SetWidth(80)
			end

			bf.buff[1]:ClearAllPoints()
			bf.buff[1]:SetPoint("BOTTOMLEFT", 0, 0)
			for i = 2,buffCount do
				if (i > buffCount) then break end

				local buffI = bf.buff[i]
				buffI:ClearAllPoints()

				if (i == 4 + (rconf.mana or 0)) then
					if (rconf.buffs.inside) then
						buffI:SetPoint("BOTTOMLEFT", 0, 0)
						bf.buff[1]:SetPoint("BOTTOMLEFT", buffI, "BOTTOMRIGHT", 0, 0)
					else
						buffI:SetPoint("BOTTOMLEFT", bf.buff[i-(4 - abs(1 - (rconf.mana or 0)))], "BOTTOMRIGHT", 0, 0)
					end
				else
					buffI:SetPoint("BOTTOMLEFT", bf.buff[i - 1], "TOPLEFT", 0, 0)
				end
			end
		else
			self.statsFrame:SetWidth(80)

			bf:SetPoint("TOPLEFT", self.statsFrame, "BOTTOMLEFT", 0, 1)

			local prevBuff
			for i = 1,buffCount do
	  			local buff = bf.buff[i]
				buff:ClearAllPoints()
				if (prevBuff) then
					buff:SetPoint("TOPLEFT", prevBuff, "TOPRIGHT", 0, 0)
				else
					buff:SetPoint("TOPLEFT", 0, 0)
				end
				prevBuff = buff
			end
		end
	else
		self.statsFrame:SetWidth(80)
		if (bf:IsShown()) then
			bf:Hide()
		end
	end

	local myRoster = XPerl_Roster[UnitName(partyid)]
	if (myRoster) then
		local _,class = UnitClass(partyid)
		if (class == "HUNTER") then
			if (UnitIsFeignDeath(partyid)) then
				if (not myRoster.fd) then
					myRoster.fd = GetTime()
					XPerl_Raid_UpdateHealth(self)
				end
			elseif (myRoster.fd) then
				myRoster.fd = nil
				XPerl_Raid_UpdateHealth(self)
			end
		end
	end
end

------------------
-- Buffs stuffs --
------------------

-- Scans a unit's buffs and sets up initial buff time remaining entries
local debuffTimers = {	Spell_Nature_SpiritArmor = 15 * 60,	-- Mark of Nature
			Spell_Frost_ChainsOfIce = 15 * 60,	-- Mark of Frost
			INV_Misc_Bandage_08 = 60,		-- Recently Bandaged
			Spell_Shadow_DeathScream = 10 * 60,	-- Resurrection Sickness
			Spell_Holy_RemoveCurse = 60,		-- Forbearance
}

-- CheckRosterBuffs
-- Check buffs exist in roster that we've received, if not then strip them out
local function CheckRosterBuffs(unitName)
	local id = RaidPositions[unitName]
	if (id) then
		if (UnitIsConnected(id)) then
			local buffs = XPerl_Roster[unitName].Buffs
			if (buffs) then
				for buffName,buffTimeEnd in pairs(buffs) do
					if (buffTimeEnd < GetTime()) then
						buffs[buffName] = nil
					end
				end
			end

			buffs = XPerl_Roster[unitName].Debuffs
			if (buffs) then
				for buffName,buffTimeEnd in pairs(buffs) do
					if (buffTimeEnd < GetTime()) then
						buffs[buffName] = nil
					end
				end
			end
		end
	end
end

-- UpdateRosterBuffs
local function UpdateRosterBuffs(self)
	local unit = self.partyid
	if (not unit) then
		return
	end

	local myRoster = XPerl_Roster[UnitName(unit)]

	if (myRoster) then
		if (not myRoster.Buffs) then
			myRoster.Buffs = new()
		end
		if (not myRoster.Debuffs) then
			myRoster.Debuffs = new()
		end

		local oldAuras = new()
		local oldDebuffs = new()
		for k, v in pairs(myRoster.Buffs) do
			oldAuras[k] = 1
		end
		for k,v in pairs(myRoster.Debuffs) do
			oldDebuffs[k] = 1
		end

		local buffName, rank, buff = UnitBuff(unit, 1)
		local num = 1
		while (buffName) do
			oldAuras[buffName] = nil
			if (not myRoster.Buffs[buffName]) then
				-- We store the fade time of raid member's buffs

				local ctBuff = XPerl_CTBuffTextures[buffName]
				if (ctBuff) then
					local duration = ctBuff[2]

					if (not duration) then
						duration = 5*60
					end

					myRoster.Buffs[buffName] = GetTime() + duration
				end
			end

			num = num + 1
			buffName, rank, buff = UnitBuff(unit, num)
		end

		buffName, rank, buff = UnitDebuff(unit, 1)
		num = 1
		while (buffName) do
			buff = strsub(buff, 17)
			local duration = debuffTimers[buff]

			if (duration) then
				oldDebuffs[buff] = nil
				if (not myRoster.Debuffs[buff]) then
					myRoster.Debuffs[buff] = GetTime() + duration
				end
			end

			num = num + 1
			buffName, rank, buff = UnitDebuff(unit, num)
		end

		for k, v in pairs(oldAuras) do
			myRoster.Buffs[k] = nil
		end
		for k,v in pairs(oldDebuffs) do
			myRoster.Debuffs[k] = nil
		end

		del(oldAuras)
		del(oldDebuffs)

		if (not next(myRoster.Debuffs)) then
			del(myRoster.Debuffs)
			myRoster.Debuffs = nil
		end

		if (not next(myRoster.Buffs)) then
			del(myRoster.Buffs)
			myRoster.Buffs = nil
		end
	end

	CheckRosterBuffs(unit)
end

-- XPerl_Raid_UpdateCombat
local function XPerl_Raid_UpdateCombat(self)
	local partyid = self.partyid
	if (not partyid) then
		return
	end
	if (UnitExists(partyid) and UnitAffectingCombat(partyid)) then
		self.nameFrame.combatIcon:Show()
	else
		self.nameFrame.combatIcon:Hide()
	end
	if (UnitIsVisible(partyid) and UnitIsCharmed(partyid)) then
		self.nameFrame.warningIcon:Show()
	else
		self.nameFrame.warningIcon:Hide()
	end
end

-- XPerl_Raid_UpdatePlayerFlags(self)
local function XPerl_Raid_UpdatePlayerFlags(self, partyid)
	if (not partyid) then
		partyid = self:GetAttribute("unit")
	end
	local f = FrameArray[partyid]
	if (f) then
		self = f

		local unitName = UnitName(partyid)
		if (unitName) then
			local unitInfo = XPerl_Roster[unitName]
			if (unitInfo) then
				local change
				if (UnitIsAFK(partyid)) then
					if (not unitInfo.afk) then
						change = true
						unitInfo.afk = GetTime()
						unitInfo.dnd = nil
					end
				elseif (UnitIsDND(partyid)) then
					if (not unitInfo.dnd) then
						change = true
						unitInfo.dnd = GetTime()
						unitInfo.afk = nil
					end
				else
					if (unitInfo.afk or unitInfo.dnd) then
						unitInfo.afk, unitInfo.dnd = nil, nil
						change = true
					end
				end

				if (change) then
					local flags = XPerl_Raid_CheckFlags(partyid)
					if (flags) then
						XPerl_Raid_ShowFlags(self, flags)
					else
						XPerl_Raid_UpdateMana(self)
						XPerl_Raid_UpdateHealth(self)
					end
				end
			end
		end
	end
end

-- XPerl_Raid_ShowRaidGroup
local function XPerl_Raid_ShowRaidGroup(show)
	if (rconf.group[show] and rconf.enable and (show < 9 or rconf.sortByClass)) then
		raidHeaders[show]:Show()
	else
		raidHeaders[show]:Hide()
	end
end

-- XPerl_Raid_OnUpdate
function XPerl_Raid_OnUpdate(self, elapsed)
	if (ShouldFlushRosterUpdate()) then
		ClearRosterUpdateBurst()
		XPerl_Raid_Position(self)
		if (XPerl_Custom) then
			XPerl_Custom:UpdateUnits()
		end
		if (GetNumRaidMembers() == 0) then
			ResArray = {}
			XPerl_Roster = {}
			buffUpdates = {}
			return
		end
	end

	local updateHighlights, someUpdate
	local enemyUnitList

	self.time = self.time + elapsed
	if (self.time >= 0.2) then
		self.time = 0
		someUpdate = true
	end

	for i,frame in pairs(FrameArray) do
		if (frame:IsShown()) then
			if (frame.PlayerFlash) then
				XPerl_Raid_CombatFlash(frame, arg1, false)
			end

			if (someUpdate) then
				local unit = frame.partyid	-- frame:GetAttribute("unit")
				if (unit) then
					local name = UnitName(unit)
					if (name) then
						local myRoster = XPerl_Roster[name]
						if (myRoster) then
							if (frame.statsFrame.greyMana) then
								if (myRoster.offline and UnitIsConnected(unit)) then
									XPerl_Raid_UpdateHealth(frame)
								end
							else
								if (not myRoster.offline and not UnitIsConnected(unit)) then
									XPerl_Raid_UpdateHealth(frame)
								end
							end
						end
					end

					XPerl_UpdateSpellRange(frame, unit, true)
				end
			end
		end
	end

	local i = 1
	for k,v in pairs(buffUpdates) do
		UpdateBuffs(k)
		buffUpdates[k] = nil
		i = i + 1
		if (i > 5) then
			break
		end
	end
end

-- XPerl_Raid_RaidTargetUpdate
local function XPerl_Raid_RaidTargetUpdate(self)
	local icon = self.nameFrame.raidIcon
	local raidIcon = GetRaidTargetIndex(self.partyid)

	if (raidIcon) then
		if (not icon) then
			icon = self.nameFrame:CreateTexture(nil, "OVERLAY")
			self.nameFrame.raidIcon = icon
			icon:SetTexture("Interface\\TargetingFrame\\UI-RaidTargetingIcons")
			icon:SetPoint("LEFT")
			icon:SetWidth(16)
			icon:SetHeight(16)
		else
			icon:Show()
		end
		SetRaidTargetIconTexture(icon, raidIcon)
	elseif (icon) then
		icon:Hide()
	end
end

-------------------------
-- The Update Function --
-------------------------
function XPerl_Raid_UpdateDisplayAll()
	for k,v in pairs(FrameArray) do
		if (v:IsShown()) then
			XPerl_Raid_UpdateDisplay(v)
		end
	end
end

-- XPerl_Raid_UpdateDisplay
function XPerl_Raid_UpdateDisplay(self)
	-- Health must be updated after mana, since ctra flag checks are done here.
	if (rconf.mana) then
		XPerl_Raid_UpdateManaType(self)
		XPerl_Raid_UpdateMana(self)
	end
	XPerl_Raid_UpdatePlayerFlags(self)
	XPerl_Raid_UpdateHealth(self)		-- <<< -- AFTER MANA -- <<< --
	XPerl_Raid_UpdateName(self)
	XPerl_Raid_UpdateCombat(self)
	XPerl_Unit_UpdateReadyState(self)
	XPerl_Raid_RaidTargetUpdate(self)

	buffUpdates[self] = true		-- UpdateBuffs(self)

	if (not SkipHighlightUpdate) then
		XPerl_Highlight:SetHighlight(self)
	end

	if (XPerl_Voice) then
		XPerl_Voice:UpdateVoice(self)
	end
end

-- HideShowRaid
function XPerl_Raid_HideShowRaid()
	local singleGroup
	if (XPerl_Party_SingleGroup) then
		if (conf.party.smallRaid) then
			singleGroup = XPerl_Party_SingleGroup()
		end
	end
	for i = 1,9 do
		if (rconf.group[i] == 1 and rconf.enable and (i < 9 or rconf.sortByClass) and not singleGroup) then
			if (not raidHeaders[i]:IsShown()) then
				raidHeaders[i]:Show()
			end
		else
			if (raidHeaders[i]:IsShown()) then
				raidHeaders[i]:Hide()
			end
		end
	end
end

-------------------
-- Event Handler --
-------------------

-- XPerl_Raid_OnEvent
function XPerl_Raid_OnEvent(self, event, unit, ...)
	local func = XPerl_Raid_Events[event]
	if (func) then
		if (strfind(event, "^UNIT_")) then
			local f = FrameArray[unit]
			if (f) then
				func(f, unit, ...)
			end
		else
			func(self, unit, ...)
		end
	else
XPerl_ShowMessage("EXTRA EVENT")
	end
end

-- VARIABLES_LOADED
function XPerl_Raid_Events:VARIABLES_LOADED()
	self:UnregisterEvent("VARIABLES_LOADED")

	if (GetNumRaidMembers() == 0) then
		ResArray = {}
		XPerl_Roster = {}
	else
		local myRoster = XPerl_Roster[UnitName("player")]
		if (myRoster) then
			myRoster.afk, myRoster.dnd, myRoster.ressed, myRoster.resCount = nil, nil, nil, nil
		end
	end

	XPerl_Highlight:Register(XPerl_Raid_HighlightCallback, self)

	XPerl_Raid_Events.VARIABLES_LOADED = nil
end

-- XPerl_Raid_Events:PLAYER_ENTERING_WORLDsmall()
function XPerl_Raid_Events:PLAYER_ENTERING_WORLDsmall()
	-- Force a re-draw. Events not processed for anything that happens during
	-- the small time you zone. Some display anomolies can occur from this
	XPerl_Raid_UpdateDisplayAll()

	if (IsInInstance()) then
		LoadAddOn("XPerl_CustomHighlight")
	end
end

-- PLAYER_ENTERING_WORLD
function XPerl_Raid_Events:PLAYER_ENTERING_WORLD()
	--self:UnregisterEvent("PLAYER_ENTERING_WORLD")

	XPerl_Raid_ChangeAttributes()
	XPerl_RaidTitles()

	raidLoaded = true
	ClearRosterUpdateBurst()

	if (GetNumRaidMembers() > 0) then
		XPerl_Raid_Frame:Show()
	end

	if (IsInInstance()) then
		LoadAddOn("XPerl_CustomHighlight")
	end

	XPerl_Raid_Events.PLAYER_ENTERING_WORLD = XPerl_Raid_Events.PLAYER_ENTERING_WORLDsmall
	XPerl_Raid_Events.PLAYER_ENTERING_WORLDsmall = nil
end

do
	local rosterGuids
	-- XPerl_Raid_GetUnitFrameByGUID
	function XPerl_Raid_GetUnitFrameByGUID(guid)
		local unitid = rosterGuids and rosterGuids[guid]
		if (unitid) then
			return FrameArray[unitid]
		end
	end

	local function BuildGuidMap()
		if (GetNumRaidMembers() > 0) then
			rosterGuids = new()
			for i = 1,GetNumRaidMembers() do
				local guid = UnitGUID("raid"..i)
				if (guid) then
					rosterGuids[guid] = "raid"..i
				end
			end
		else
			rosterGuids = del(rosterGuids)
		end
	end

	-- RAID_ROSTER_UPDATE
	function XPerl_Raid_Events:RAID_ROSTER_UPDATE()
		QueueRosterUpdate()
		BuildGuidMap()
		if (GetNumRaidMembers() > 0) then
			XPerl_Raid_Frame:Show()
		end
	end
	
	function XPerl_Raid_Events:PLAYER_LOGIN()
		BuildGuidMap()
	end
end

-- UNIT_FLAGS
function XPerl_Raid_Events:UNIT_FLAGS()
	XPerl_Raid_UpdateCombat(self)
end

XPerl_Raid_Events.UNIT_DYNAMIC_FLAGS = XPerl_Raid_Events.UNIT_FLAGS

function XPerl_Raid_Events:PLAYER_FLAGS_CHANGED(unit)
	XPerl_Raid_UpdatePlayerFlags(self, unit)
end

-- UNIT_FACTION
function XPerl_Raid_Events:UNIT_FACTION()
	XPerl_Raid_UpdateCombat(self)
	XPerl_Raid_UpdateName(self)
end

-- UNIT_COMBAT
function XPerl_Raid_Events:UNIT_COMBAT()
	if (arg2 == "HEAL") then
		XPerl_Raid_CombatFlash(self, 0, true, true)
	elseif (arg4 and arg4 > 0) then
		XPerl_Raid_CombatFlash(self, 0, true)
	end
end

-- UNIT_HEALTH
function XPerl_Raid_Events:UNIT_HEALTH()
	XPerl_Raid_UpdateHealth(self)
	XPerl_Raid_UpdateCombat(self)
end
XPerl_Raid_Events.UNIT_MAXHEALTH = XPerl_Raid_Events.UNIT_HEALTH

function XPerl_Raid_Events:UNIT_DISPLAYPOWER()
	XPerl_Raid_UpdateManaType(self)
	XPerl_Raid_UpdateMana(self)
end

-- UNIT_MANA
function XPerl_Raid_Events:UNIT_MANA()
	if (rconf.mana) then
		XPerl_Raid_UpdateMana(self)
	end
end
XPerl_Raid_Events.UNIT_MAXMANA   = XPerl_Raid_Events.UNIT_MANA
XPerl_Raid_Events.UNIT_RAGE      = XPerl_Raid_Events.UNIT_MANA
XPerl_Raid_Events.UNIT_MAXRAGE   = XPerl_Raid_Events.UNIT_MANA
XPerl_Raid_Events.UNIT_ENERGY    = XPerl_Raid_Events.UNIT_MANA
XPerl_Raid_Events.UNIT_MAXENERGY = XPerl_Raid_Events.UNIT_MANA

-- UNIT_NAME_UPDATE
function XPerl_Raid_Events:UNIT_NAME_UPDATE()
	XPerl_Raid_UpdateName(self)
	XPerl_Raid_UpdateHealth(self)			-- Added 16th May 2007 - Seems they now fire name update to indicate some change in state.
end

-- UNIT_AURA
function XPerl_Raid_Events:UNIT_AURA()
	lastNamesList, lastName, lastWith = nil, nil, nil
	UpdateRosterBuffs(self)
	UpdateBuffs(self)
end

-- READY_CHECK
function XPerl_Raid_Events:READY_CHECK(a, b, c)
	for i,frame in pairs(FrameArray) do
		if (frame.partyid) then
			XPerl_Unit_UpdateReadyState(frame)
		end
	end
end

XPerl_Raid_Events.READY_CHECK_CONFIRM = XPerl_Raid_Events.READY_CHECK
XPerl_Raid_Events.READY_CHECK_FINISHED = XPerl_Raid_Events.READY_CHECK

-- RAID_TARGET_UPDATE
function XPerl_Raid_Events:RAID_TARGET_UPDATE()
	for i,frame in pairs(FrameArray) do
		if (frame.partyid) then
			XPerl_Raid_RaidTargetUpdate(frame)
		end
	end
end

-- SetRes
local function SetResStatus(resserName, resTargetName, ignoreCounter)

	--frame.beingRessed = true
	local resEnd

	if (resTargetName) then
		ResArray[resserName] = resTargetName
	else
		resEnd = true

		for i,name in pairs(ResArray) do
			if (i == resserName) then
				resTargetName = name
				break
			end
		end

		ResArray[resserName] = nil
	end

	if (resTargetName) then
		local myRoster = XPerl_Roster[resTargetName]
		if (myRoster) then
			if (resEnd and not ignoreCounter) then
				myRoster.ressed = 1
				myRoster.resCount = (myRoster.resCount or 0) + 1
			end
			UpdateUnitByName(resTargetName, true)
		end
	end
end

-- UNIT_SPELLCAST_START
function XPerl_Raid_Events:UNIT_SPELLCAST_START(unit, spell, rank)
	if (ResArray[UnitName(unit)]) then
		-- Flagged as ressing, finish their old cast
		SetResStatus(UnitName(unit))
	end

	local name, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(unit)
	if (resSpells[name]) then
		local u = unit.."target"
		if (UnitExists(u) and UnitIsDead(u)) then
			SetResStatus(UnitName(unit), UnitName(u))
		end
	end
end

-- UNIT_SPELLCAST_STOP
function XPerl_Raid_Events:UNIT_SPELLCAST_STOP(unit)
	if (unit) then
		SetResStatus(UnitName(unit))
	end
end

-- UNIT_SPELLCAST_FAILED
function XPerl_Raid_Events:UNIT_SPELLCAST_FAILED(unit)
	if (unit) then
		SetResStatus(UnitName(unit), nil, true)
	end
end

XPerl_Raid_Events.UNIT_SPELLCAST_INTERRUPTED = XPerl_Raid_Events.UNIT_SPELLCAST_FAILED

-- Direct string matches can be done via table lookup
local QuickFuncs = {
	--AFK	= function(m)	m.afk = GetTime(); m.dnd = nil; end,
	--UNAFK	= function(m)	m.afk = nil; end,
	--DND	= function(m)	m.dnd = GetTime(); m.afk = nil; end,
	--UNDND	= function(m)	m.dnd = nil; end,
	RESNO	= function(m,n) SetResStatus(n) end,
	RESSED	= function(m)	m.ressed = 1; end,
	CANRES	= function(m)	m.ressed = 2; end,
	NORESSED= function(m)
		if (m.ressed) then
			m.ressed = 3
		else
			m.ressed = nil
		end
		m.resCount = nil
	end,
	SR	= XPerl_SendModules
}

-- DurabilityCheck(msg, author)
-- Quick DUR check for those people who don't have oRA2 and CTRA installed
-- No, I'm not going to replace either mod
local function XPerl_DurabilityCheck(author)
	local durPattern = gsub(DURABILITY_TEMPLATE, "%%d", "(%%d+)")
	local cur, max, broken = 0, 0, 0
	local tip = GameTooltip

	tip:SetOwner(this, "ANCHOR_RIGHT")
	for i = 1, 18 do
		if (GetInventoryItemBroken("player", i)) then
			broken = broken + 1
		end

		tip:SetInventoryItem("player", i)

		for j = 1,tip:NumLines() do
			local line = getglobal(tip:GetName().."TextLeft"..j)
			if (line) then
				local text = line:GetText()
				if (text) then
					local imin, imax = strmatch(text, durPattern)
					if (imin and imax) then
						imin, imax = tonumber(imin), tonumber(imax)
						cur = cur + imin
						max = max + imax
						break
					end
				end
			end
		end
	end

	tip:Hide()

	SendAddonMessage("CTRA", format("DUR %s %s %s %s", cur, max, broken, author), "RAID")
end

-- XPerl_ItemCheckCount
local function XPerl_ItemCheckCount(itemName, author)
	local count = GetItemCount(itemName)
	if (count and count > 0) then
		SendAddonMessage("CTRA", "ITM "..count.." "..itemName.." "..author, "RAID")
	end
end

-- XPerl_ResistsCheck
local function XPerl_ResistsCheck(unitName)
	local str = ""
	for i = 2, 6 do
		str = str.." "..select(2, UnitResistance("player", i))
	end
	SendAddonMessage("CTRA", format("RST%s %s", str, unitName), "RAID")
end

-- ProcessCTRAMessage
local function ProcessCTRAMessage(unitName, msg)
	local myRoster = XPerl_Roster[unitName]

	if (not myRoster) then
		return
	end

	local update = true

	local func = QuickFuncs[msg]
	if (func) then
--ChatFrame7:AddMessage("QuickFuncs["..msg.."]")
		func(myRoster, unitName)
	else
		if (strsub(msg, 1, 4) == "RES ") then
			SetResStatus(unitName, strsub(msg, 5))
			return

 		elseif (strsub(msg, 1, 3) == "RN ") then
			-- Buff durations
			local secsLeft, buffIndex, buffSub = strmatch(msg, "^RN ([^%s]+) ([^%s]+) ([^%s]+)$")

			if (secsLeft and buffIndex and buffSub) then
				secsLeft = tonumber(secsLeft)
				buffIndex = tonumber(buffIndex)
				buffSub = tonumber(buffSub)

				local buff = XPerl_CTBuffArray[buffIndex]

				if (buff) then
					local buffName, delete
					-- Shouldn't come with string and sub index > 0, but it does.. Will investigate when and why
					-- Druid Thorns... /shrug
					if (buffSub == 0 or type(buff.name) == "string") then
						buffName = buff.name
					else
						buffName = buff.name[buffSub]
						if (buffSub == 1) then
							delete = buff.name[2]
						else
							delete = buff.name[1]
						end
					end

					if (buffName) then
						if (not myRoster.Buffs) then
							myRoster.Buffs = new()
						end
						myRoster.Buffs[buffName] = GetTime() + secsLeft
						if (delete) then
							-- Clear a buff's partner buff (can only have 1 of PWF group vs single for example)
							myRoster.Buffs[delete] = nil
						end
						CheckRosterBuffs(unitName)
					end
				end
			end
			update = nil

		elseif (strsub(msg, 1, 3) == "CD ") then
			local num, cooldown = strmatch(msg, "^CD (%d+) (%d+)$")
			if ( num == "1" ) then
				myRoster.Rebirth = GetTime() + tonumber(cooldown)*60
			elseif ( num == "2" ) then
				myRoster.Reincarnation = GetTime() + tonumber(cooldown)*60
			elseif ( num == "3" ) then
				myRoster.Soulstone = GetTime() + tonumber(cooldown)*60
			end
			update = nil

		elseif (strsub(msg, 1, 2) == "V ") then
			myRoster.version = strsub(msg, 3)
			update = nil

		elseif (msg == "DURC") then
			if (not CT_RA_VersionNumber and not oRA) then
				XPerl_DurabilityCheck(unitName)
			end

		elseif (msg == "RSTC") then
			if (not CT_RA_VersionNumber and not oRA) then
				XPerl_ResistsCheck(unitName)
			end

		elseif (strsub(msg, 1, 4) == "ITMC") then
			if (not CT_RA_VersionNumber and not oRA) then
				local itemName = strmatch(msg, "^ITMC (.+)$")
				if (itemName) then
					XPerl_ItemCheckCount(itemName, unitName)
				end
			end
		else
			update = nil
		end
	end

	if (update) then
		UpdateUnitByName(unitName, true)
	end
end

-- ProcessoRAMessage
local function ProcessoRAMessage(unitName, msg)
	local myRoster = XPerl_Roster[unitName]

	if (not myRoster) then
		return
	end

	if (strsub(msg, 1, 5) == "oRAV ") then
		myRoster.oRAversion = strsub(msg, 6)
	end
end

-- XPerl_Raid_Events:CHAT_MSG_RAID
-- Check for AFK/DND flags in chat
--function XPerl_Raid_Events:CHAT_MSG_RAID()
--	local myRoster = XPerl_Roster[arg4]
--	if (myRoster) then
--		if (arg6 == "AFK") then
--			if (not myRoster.afk) then
--				myRoster.afk = GetTime()
--				myRoster.dnd = nil
--			end
--		elseif (arg6 == "DND") then
--			if (not myRoster.dnd) then
--				myRoster.dnd = GetTime()
--				myRoster.afk = nil
--			end
--		else
--			myRoster.dnd, myRoster.afk = nil, nil
--		end
--	end
--end
--XPerl_Raid_Events.CHAT_MSG_RAID_LEADER = XPerl_Raid_Events.CHAT_MSG_RAID
--XPerl_Raid_Events.CHAT_MSG_PARTY = XPerl_Raid_Events.CHAT_MSG_RAID

-- XPerl_ParseCTRA
function XPerl_ParseCTRA(sender, msg, func)
	local arr = new(strsplit("#", msg))
	for i,subMsg in pairs(arr) do
		func(sender, subMsg)
	end
	del(arr)
end

-- CHAT_MSG_ADDON
function XPerl_Raid_Events:CHAT_MSG_ADDON(prefix, msg, channel, sender)
	if (channel == "RAID") then
		if (prefix == "CTRA") then
			XPerl_ParseCTRA(sender, msg, ProcessCTRAMessage)
		elseif (prefix == "oRA") then
			XPerl_ParseCTRA(sender, msg, ProcessoRAMessage)
		end
	end
end

-- SetRaidRoster
local function SetRaidRoster()
	local NewRoster = new()

	del(RaidPositions)
	RaidPositions = new()

	del(RaidGroupCounts)
	RaidGroupCounts = new(0,0,0,0,0,0,0,0,0)

	for i = 1,GetNumRaidMembers() do
		local name, rank, group, level, class, fileName = GetRaidRosterInfo(i)

		if (name) then
			local unit = "raid"..i
			RaidPositions[name] = unit

			if (UnitIsUnit(unit, "player")) then
				myGroup = group
			end

			if (rconf.sortByClass) then
				for j = 1,9 do
					if (rconf.class[j].name == fileName and rconf.class[j].enable) then
						RaidGroupCounts[j] = RaidGroupCounts[j] + 1
						break
					end
				end
			else
				RaidGroupCounts[group] = RaidGroupCounts[group] + 1
			end

			if (fileName and not ClassNames[fileName]) then
				ClassNames[fileName] = class
			end

			local r = XPerl_Roster[name]
			if (r) then
				NewRoster[name] = r
				XPerl_Roster[name] = nil
				r.afk = UnitIsAFK(unit)
				r.dnd = UnitIsDND(unit)
			else
				NewRoster[name] = new()
			end
		end
	end

	if (GetNumRaidMembers() > 0) then
		XPerl_Raid_Frame:Show()
	else
		XPerl_Raid_Frame:Hide()
	end

	del(XPerl_Roster, true)
	XPerl_Roster = NewRoster
end

-- XPerl_RaidGroupCounts()
function XPerl_RaidGroupCounts()
	return RaidGroupCounts
end

-- XPerl_Raid_Position
function XPerl_Raid_Position(self)
	SetRaidRoster()
	XPerl_RaidTitles()

	if (conf.party.smallRaid) then
		XPerl_Raid_HideShowRaid()
	end
end

--------------------
-- Click Handlers --
--------------------
function XPerl_RaidFrameDropDown_Initialize(ct)
	if (type(ct) ~= "table") then
		ct = nil
	end

	local info
	if (XPerl_MainTanks and type(UIDROPDOWNMENU_MENU_VALUE) == "table" and UIDROPDOWNMENU_MENU_VALUE[1] == "Main Tanks") then
		info = UIDropDownMenu_CreateInfo()
		info.text = XPERL_RAID_DROPDOWN_MAINTANKS
		info.isTitle = 1
		info.notCheckable = 1
		UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL)
		for i = 1,10 do
			info = UIDropDownMenu_CreateInfo()
			info.notCheckable = 1
			if (XPerl_MainTanks[i] and XPerl_MainTanks[i][2] == UIDROPDOWNMENU_MENU_VALUE[2]) then
				info.text = format("|c00FFFF80"..XPERL_RAID_DROPDOWN_REMOVEMT.."|r", i)
				info.value = {UIDROPDOWNMENU_MENU_VALUE[1], UIDROPDOWNMENU_MENU_VALUE[2], i, 1}
			else
				info.text = format(XPERL_RAID_DROPDOWN_SETMT, i)
				info.value = {UIDROPDOWNMENU_MENU_VALUE[1], UIDROPDOWNMENU_MENU_VALUE[2], i}
			end
			info.func = XPerl_MainTankSet_OnClick
			UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL)
		end
		return
	end

	RaidFrameDropDown_Initialize()

	local titleDone
	if (DropDownList1.numButtons == 0 and (IsRaidOfficer() or (ct and CT_RATab_AutoPromotions))) then
		titleDone = true
		info = UIDropDownMenu_CreateInfo()
		info.text = this.name
		if (this.server) then
			info.text = info.text.."-"..this.server
		end
		info.isTitle = 1
		info.notCheckable = 1
		UIDropDownMenu_AddButton(info)
	end

	if (IsRaidOfficer() and XPerl_MainTanks) then
		if (not titleDone and DropDownList1.numButtons > 0) then
			-- We want our MT option above the Cancel option, so we trick the menu into thinking it's got 1 less button
			DropDownList1.numButtons = DropDownList1.numButtons - 1
		end

		info = UIDropDownMenu_CreateInfo()
		info.text = XPERL_RAID_DROPDOWN_MAINTANKS
		info.value = {"Main Tanks", this.name}			-- Must be 'this'
		info.hasArrow = 1
		info.dist = 0
		info.notCheckable = 1
		UIDropDownMenu_AddButton(info)

		-- Re-add the cancel button after our MT option
		info = UIDropDownMenu_CreateInfo()
		info.text = XPERL_CANCEL
		info.value = "CANCEL"
		info.owner = "RAID"
		info.func = UnitPopup_OnClick
		info.notCheckable = 1
		UIDropDownMenu_AddButton(info)
	end

	if (ct and CT_RATab_AutoPromotions) then
		info = UIDropDownMenu_CreateInfo()
		info.text = XPERL_RAID_AUTOPROMOTE
		info.checked = CT_RATab_AutoPromotions[this.name]	-- Must be 'this'
		info.value = this.id					-- Must be 'this'
		info.func = CT_RATab_AutoPromote_OnClick
		UIDropDownMenu_AddButton(info)
	end
end

-- XPerl_MainTankSet_OnClick
function XPerl_MainTankSet_OnClick()
	if (this.value[1] == "Main Tanks") then				-- Must be 'this'
		if (this.value[4]) then
			SendAddonMessage("CTRA", "R "..this.value[2], "RAID")
		else
			SendAddonMessage("CTRA", "SET "..this.value[3].." "..this.value[2], "RAID")
		end
	end
	CloseMenus()
end

-- XPerl_ScaleRaid
function XPerl_ScaleRaid()
	for frame = 1,9 do
		local f = getglobal("XPerl_Raid_Title"..frame)
		if (f) then
			f:SetScale(rconf.scale)
		end
	end
end

-- XPerl_RaidTitles
function XPerl_RaidTitles()

	local singleGroup
	if (XPerl_Party_SingleGroup) then
		if (conf.party.smallRaid) then
			singleGroup = XPerl_Party_SingleGroup()
		end
	end

	local c
	for i = 1,9 do		-- NUM_RAID_GROUPS do
		local confClass = rconf.class[i].name
		local frame = getglobal("XPerl_Raid_Title"..i)
		local titleFrame = frame.text
		local virtualFrame = frame.virtual

		if (not rconf.sortByClass and myGroup == i) then
			c = HIGHLIGHT_FONT_COLOR
		else
			c = NORMAL_FONT_COLOR
		end
		titleFrame:SetTextColor(c.r, c.g, c.b)

		if (rconf.sortByClass) then
			if (ClassNames[confClass]) then
				titleFrame:SetText(ClassNames[confClass])
			else
				titleFrame:SetText(localGroups[confClass])
			end
		else
			titleFrame:SetFormattedText(XPERL_RAID_GROUP, i)
		end

		if (XPerlLocked == 0 or (RaidGroupCounts[i] > 0 and rconf.enable and rconf.group[i] and not singleGroup)) then
			if (XPerlLocked == 0 or rconf.titles) then
				if (not titleFrame:IsShown()) then
					titleFrame:Show()
				end
			else
				if (titleFrame:IsShown()) then
					titleFrame:Hide()
				end
			end

			if (XPerlLocked == 0) then
				virtualFrame:ClearAllPoints()
				if (rconf.anchor == "TOP") then
					virtualFrame:SetPoint("TOPLEFT", frame, "BOTTOMLEFT", 0, 0)
					virtualFrame:SetHeight(((rconf.mana or 0) * 5 + 38) * 5 + (rconf.spacing * 4))
					virtualFrame:SetWidth(80)

				elseif (rconf.anchor == "LEFT") then
					virtualFrame:SetPoint("TOPLEFT", frame, "BOTTOMLEFT", 0, 0)
					virtualFrame:SetHeight((rconf.mana or 0) * 5 + 38)
					virtualFrame:SetWidth(80 * 5 + (rconf.spacing * 4))

				elseif (rconf.anchor == "BOTTOM") then
					virtualFrame:SetPoint("BOTTOMLEFT", frame, "TOPLEFT", 0, 0)
					virtualFrame:SetHeight(((rconf.mana or 0) * 5 + 38) * 5 + (rconf.spacing * 4))
					virtualFrame:SetWidth(80)

				elseif (rconf.anchor == "RIGHT") then
					virtualFrame:SetPoint("TOPRIGHT", frame, "BOTTOMRIGHT", 0, 0)
					virtualFrame:SetHeight((rconf.mana or 0) * 5 + 38)
					virtualFrame:SetWidth(80 * 5 + (rconf.spacing * 4))
				end

				virtualFrame:SetBackdropColor(conf.colour.frame.r, conf.colour.frame.g, conf.colour.frame.b, conf.colour.frame.a)
				virtualFrame:SetBackdropBorderColor(conf.colour.border.r, conf.colour.border.g, conf.colour.border.b, 1)
				virtualFrame:Show()
			else
				virtualFrame:Hide()
			end
		else
			if (virtualFrame:IsShown()) then
				virtualFrame:Hide()
			end
			if (titleFrame:IsShown()) then
				titleFrame:Hide()
			end
		end
	end

	XPerl_ProtectedCall(XPerl_EnableRaidMouse)

	if (XPerl_RaidPets_Align) then
		XPerl_ProtectedCall(XPerl_RaidPets_Align)
	end
end

-- XPerl_EnableRaidMouse()
function XPerl_EnableRaidMouse()
	for i = 1,9 do
		local frame = getglobal("XPerl_Raid_Title"..i)
		if (XPerlLocked == 0) then
			frame:EnableMouse(true)
		else
			frame:EnableMouse(false)
		end
	end
end

-- XPerl_Raid_SetBuffTooltip
function XPerl_Raid_SetBuffTooltip(self)
	if (conf.tooltip.enableBuffs and XPerl_TooltipModiferPressed(true)) then
		if (not conf.tooltip.hideInCombat or not InCombatLockdown()) then
			local parentUnit = self:GetParent():GetParent()
			local partyid = SecureButton_GetUnit(parentUnit)
			if (not partyid) then
				return
			end

			GameTooltip:SetOwner(self,"ANCHOR_BOTTOMRIGHT",30,0)

			local show, cureCast = GetShowCast(parentUnit)
			if (parentUnit.debuffsForced) then
				show = "d"
			end
			if (show == "b") then
				XPerl_TooltipSetUnitBuff(GameTooltip, partyid, self:GetID(), cureCast)
				XPerl_Raid_AddBuffDuration(self, partyid)
			elseif (show == "d") then
				XPerl_TooltipSetUnitDebuff(GameTooltip, partyid, self:GetID(), cureCast)
				XPerl_Raid_AddDebuffDuration(self, partyid, self:GetID(), cureCast)
			end
		end
	end
end

------- XPerl_ToggleRaidBuffs -------
-- Raid Buff Key Binding function --
function XPerl_ToggleRaidBuffs(castable)

	if (castable) then
		if (rconf.buffs.castable == 1) then
			rconf.buffs.castable = 0
			XPerl_Notice(XPERL_KEY_NOTICE_RAID_BUFFANY)
		else
			rconf.buffs.castable = 1
			XPerl_Notice(XPERL_KEY_NOTICE_RAID_BUFFCURECAST)
		end
	else
		if (rconf.buffs.enable) then
			rconf.buffs.enable = nil
			rconf.debuffs.enable = 1
			XPerl_Notice(XPERL_KEY_NOTICE_RAID_DEBUFFS)

		elseif (rconf.debuffs.enable) then
			rconf.buffs.enable = nil
			rconf.debuffs.enable = nil
			XPerl_Notice(XPERL_KEY_NOTICE_RAID_NOBUFFS)

		else
			rconf.buffs.enable = 1
			rconf.debuffs.enable = nil
			XPerl_Notice(XPERL_KEY_NOTICE_RAID_BUFFS)
		end
	end

	for k,v in pairs(FrameArray) do
		if (v:IsShown()) then
			XPerl_Raid_UpdateDisplay(v)
		end
	end
end

-- XPerl_ToggleRaidSort
function XPerl_ToggleRaidSort(New)
	if (not XPerl_Options or not XPerl_Options:IsShown()) then
		if (not InCombatLockdown()) then
			if (New) then
				conf.sortByClass = New == 1
			else
				if (conf.sortByClass) then
					conf.sortByClass = nil
				else
					conf.sortByClass = 1
				end
			end
			XPerl_Raid_ChangeAttributes()
			XPerl_Raid_Position()
			XPerl_Raid_Set_Bits(XPerl_Raid_Frame)
		end
	end
end

-- GetCombatRezzerList()
local normalRezzers = {PRIEST = true, SHAMAN = true, PALADIN = true}
local function GetCombatRezzerList()

	local anyCombat = 0
	local anyAlive = 0
	for i = 1,GetNumRaidMembers() do
		local unit = "raid"..i
		if (normalRezzers[select(2, UnitClass(unit))]) then
			if (UnitAffectingCombat(unit)) then
				anyCombat = anyCombat + 1
			end
			if (not UnitIsDeadOrGhost(unit) and UnitIsConnected(unit)) then
				anyAlive = anyAlive + 1
			end
		end
	end

	-- We only need to know about battle rezzers if any normal rezzers are in combat
	if (anyCombat > 0 or anyAlive < 3) then
		local ret = new()
		local t = GetTime()

		for i = 1,GetNumRaidMembers() do
			local raidid = "raid"..i
			if (not UnitIsDeadOrGhost(raidid) and UnitIsVisible(raidid)) then
				local name, rank, subgroup, level, _, fileName, zone, online, isDead = GetRaidRosterInfo(i)

				local good
				if (not UnitAffectingCombat(raidid)) then
					if (fileName == "PRIEST" or fileName == "SHAMAN" or fileName == "PALADIN") then
						tinsert(ret, {["name"] = name, class = fileName, cd = 0})
					end
				else
					if (fileName == "DRUID") then
						local myRoster = XPerl_Roster[name]

						if (myRoster) then
							if (myRoster.Rebirth and myRoster.Rebirth - t <= 0) then
								myRoster.Rebirth = nil		-- Check for expired cooldown
							end
							if (myRoster.Rebirth) then
								if (myRoster.Rebirth - t < 120) then
									tinsert(ret, {["name"] = name, class = fileName, cd = myRoster.Rebirth - t})
								end
							else
								tinsert(ret, {["name"] = name, class = fileName, cd = 0})
							end
						end
					end
				end
			end
		end

		if (#ret > 0) then
			sort(ret, function(a,b) return a.cd < b.cd end)

			local list = ""
			for k,v in ipairs(ret) do
				local name = XPerlColourTable[v.class]..v.name.."|r"

				if (v.cd > 0) then
					name = name.." (in "..SecondsToTime(v.cd)..")"
				end

				if (list == "") then
					list = name
				else
					list = list..", "..name
				end
			end
			del(ret)
			return list
		else
			del(ret)
			return "|c00FF0000"..NONE.."|r"
		end
	end

	if (anyAlive == 0) then
		return "|c00FF0000"..NONE.."|r"
	elseif (anyCombat == 0) then
		return "|c00FFFFFF"..ALL.."|r"
	end
end

-- XPerl_RaidTipExtra
function XPerl_RaidTipExtra(unitid)

	if (UnitInRaid(unitid)) then
		local unitName = UnitName(unitid)
		local zone
		local name, rank, subgroup, level, class, fileName, zone, online, isDead

		for i = 1,GetNumRaidMembers() do
			name, rank, subgroup, level, class, fileName, zone, online, isDead = GetRaidRosterInfo(i)
			if (name == unitName) then
				break
			end
			zone = ""
		end

		local stats = XPerl_Roster[unitName]
		if (stats) then
			local t = GetTime()

			if (stats.version) then
				if (stats.oRAversion) then
					GameTooltip:AddLine("CTRA "..stats.version.." (oRA "..stats.oRAversion..")", 1, 1, 1)
				else
					GameTooltip:AddLine("CTRA "..stats.version, 1, 1, 1)
				end
			else
				GameTooltip:AddLine(XPERL_RAID_TOOLTIP_NOCTRA, 0.7, 0.7, 0.7)
			end

			if (stats.offline and UnitIsConnected(unitid)) then
				stats.offline = nil
			end
			if (stats.afk and not UnitIsAFK(unitid)) then
				stats.afk = nil
			end
			if (stats.dnd and not UnitIsDND(unitid)) then
				stats.dnd = nil
			end

			if (stats.offline) then
				GameTooltip:AddLine(format(XPERL_RAID_TOOLTIP_OFFLINE, SecondsToTime(t - stats.offline)))

			elseif (stats.afk) then
				GameTooltip:AddLine(format(XPERL_RAID_TOOLTIP_AFK, SecondsToTime(t - stats.afk)))

			elseif (stats.dnd) then
				GameTooltip:AddLine(format(XPERL_RAID_TOOLTIP_DND, SecondsToTime(t - stats.dnd)))

			elseif (stats.fd) then
				if (not UnitIsDead(unitid)) then
					stats.fd = nil
				else
					local x = stats.fd + 360 - t
					if (x > 0) then
						GameTooltip:AddLine(format(XPERL_RAID_TOOLTIP_DYING, SecondsToTime(x)))
					end
				end
			end

			if (stats.Rebirth) then
				if (stats.Rebirth - t > 0) then
					GameTooltip:AddLine(format(XPERL_RAID_TOOLTIP_REBIRTH, SecondsToTime(stats.Rebirth - t)))
				else
					stats.Rebirth = nil
				end

			elseif (stats.Reincarnation) then
				if (stats.Reincarnation - t > 0) then
					GameTooltip:AddLine(format(XPERL_RAID_TOOLTIP_ANKH, SecondsToTime(stats.Reincarnation - t)))
				else
					stats.Reincarnation = nil
				end

			elseif (stats.Soulstone) then
				if (stats.Soulstone - t > 0) then
					GameTooltip:AddLine(format(XPERL_RAID_TOOLTIP_SOULSTONE, SecondsToTime(stats.Soulstone - t)))
				else
					stats.Soulstone = nil
				end
			end

			if (UnitIsDeadOrGhost(unitid) and not UnitIsFeignDeath(unitid)) then
				if (stats.resCount) then
					GameTooltip:AddLine(XPERL_LOC_RESURRECTED.." x"..stats.resCount)
				end

				local Rezzers = GetCombatRezzerList()
				if (Rezzers) then
					GameTooltip:AddLine(XPERL_RAID_RESSER_AVAIL..Rezzers, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
				end
			end
		end

		GameTooltip:Show()
	end
end

-- GetNamesWithoutBuff
local matches = {
	{"Holy_WordFortitude", "Holy_PrayerOfFortitude"},
	{"Holy_MagicalSentry", "Holy_ArcaneIntellect"},
	{"Shadow_AntiShadow", "Holy_PrayerofShadowProtection"},
	{"Holy_DivineSpirit", "Holy_PrayerofSpirit"},
	{"Holy_FistOfJustice", "Holy_GreaterBlessingofKings"},
	{"Holy_SealOfWisdom", "Holy_GreaterBlessingofWisdom"},
	{"Magic_MageArmor", "Magic_GreaterBlessingofKings"},
	{"Holy_SealOfSalvation", "Holy_GreaterBlessingofSalvation"},
	{"Holy_PrayerOfHealing02", "Holy_GreaterBlessingofLight"},
	{"Nature_LightningShield", "Holy_GreaterBlessingofSanctuary"},
	{"Nature_Regeneration", "Nature_GiftoftheWild"}
}
local classOrder = {"WARRIOR", "ROGUE", "HUNTER", "PRIEST", "DRUID", "SHAMAN", "PALADIN", "MAGE", "WARLOCK"}
local checkExpiringClass
local function GetNamesWithoutBuff(spellName, tex, with)
	if (spellName == lastName and with == lastWith and lastNamesList) then
		return lastNamesList, lastNamesCount
	end

	local count = 0
	local names
	local unitName

	local class = select(2, UnitClass("player"))

	if (not checkExpiringClass) then
		if (class == "PRIEST") then
			checkExpiringClass = {Holy_WordFortitude = 2, Holy_PrayerOfFortitude = 2,
					Holy_DivineSpirit = 2, Holy_PrayerofSpirit = 2,
					Shadow_AntiShadow = 2, Holy_PrayerofShadowProtection = 2}

		elseif (class == "DRUID") then
			checkExpiringClass = {Nature_Regeneration = 2, Nature_GiftoftheWild = 2, Nature_Thorns = 1}

		elseif (class == "MAGE") then
			checkExpiringClass = {Holy_MagicalSentry = 2, Holy_ArcaneIntellect = 2, Holy_FlashHeal = 2, Nature_AbolishMagic = 2}

		elseif (class == "PALADIN") then
			checkExpiringClass = {Holy_Excorcism = 2,
					Holy_FistOfJustice = 1, Holy_GreaterBlessingofKings = 1,
					Holy_SealOfWisdom = 1, Holy_GreaterBlessingofWisdom = 1,
					Magic_MageArmor = 1, Magic_GreaterBlessingofKings = 1,
					Holy_SealOfSalvation = 1, Holy_GreaterBlessingofSalvation = 1,
					Holy_PrayerOfHealing02 = 1, Holy_GreaterBlessingofLight = 1,
					Nature_LightningShield = 1, Holy_GreaterBlessingofSanctuary = 1}
		end
	end

	if (IsRaidOfficer()) then
		checkExpiring = {all = 5}
	else
		checkExpiring = checkExpiringClass
	end

	local withList = new()
	local texNoPath = strsub(tex, 23)
	for unitid, unitName, unitClass, group, zone, online, dead in XPerl_NextMember do
		local use

		if (not conf.buffHelper.visible) then
			use = true
		else
			if (rconf.sortByClass) then
				if (rconf.class[group].enable) then
					use = unitClass == rconf.class[group].name
				end
			else
				use = rconf.group[group]
			end
		end

		if (unitName and use and online and not dead) then
			local hasBuff
			for num = 1,25 do
				local name, rank, buffTexture = UnitBuff(unitid, num)
				if (not name) then
					break
				end

				if (name == spellName) then
					hasBuff = true
				else
					local buffTextureNoPath = strsub(buffTexture, 23)
					for dups,pair in pairs(matches) do
						if (texNoPath == pair[1] or texNoPath == pair[2]) then
							if (buffTextureNoPath == pair[1] or buffTextureNoPath == pair[2]) then
								hasBuff = true
								break
							end
						end
					end
				end
				if (hasBuff) then
					if (without and checkExpiring) then
						local found = checkExpiring.all

						if (not found) then
							found = checkExpiring[buffTextureNoPath]
						end

						if (found) then
							local myRoster = XPerl_Roster[unitName]
							if (myRoster and myRoster.Buffs) then
								local expire = myRoster.Buffs[spellName]
								if (expire and expire <= GetTime() + (found*60)) then
									GameTooltip:AddLine(format(XPERL_RAID_TOOLTIP_BUFFEXPIRING, XPerlColourTable[unitClass]..name.."|c", buffName, SecondsToTime(buff - GetTime())), 1, 0.2, 0)
								end
							end
						end
					end
					break
				end
			end

			if ((with and hasBuff) or (not with and not hasBuff)) then
				count = count + 1

				if (conf.buffHelper.sort == "group") then
					if (not withList[group]) then
						withList[group] = new()
					end
					tinsert(withList[group], {class = unitClass, ["name"] = unitName})
				elseif (conf.buffHelper.sort == "class") then
					if (not withList[unitClass]) then
						withList[unitClass] = new()
					end
					tinsert(withList[unitClass], unitName)
				else
					local n = new()
					tinsert(withList, n)
					n.class = unitClass
					n.name = unitName
				end
			end
		end
	end

	-- TODO - Verify the fading buffs part, and sort this too in with this list.

	if (conf.buffHelper.sort == "group") then
		for i = 1,8 do
			local list = withList[i]
			if (list) then
				sort(list, function(a,b) return a.name < b.name end)

				names = (names or "").."|r"..i..": "
				for i,item in ipairs(list) do
					names = names..XPerlColourTable[item.class]..item.name.." "
				end
				names = names.."|r\r"
			end
			del(list)
		end
	elseif (conf.buffHelper.sort == "class") then
		for j,class in pairs(classOrder) do
			local list = withList[class]
			if (list) then
				sort(list)

				for i,name in ipairs(list) do
					if (i == 1) then
						names = (names or "")..XPerlColourTable[class]
					end
					names = (names or "")..name.." "
				end

				names = (names or "").."|r\r"
			end
			del(list)
		end
	else
		sort(withList, function(a,b) return a.name < b.name end)
		for i,item in ipairs(withList) do
			names = (names or "")..XPerlColourTable[item.class]..item.name.." "
		end

		names = (names or "").."\r"

		del(item)
	end

	--for k,v in pairs(withList) do
	--	if (type(v) == "table") then
	--		del(v)
	--		withList[k] = nil
	--	end
	--end
	del(withList)

	lastNamesList = names
	lastName = spellName
	lastWith = with
	lastNamesCount = count

	return names, count
end

-- XPerl_Raid_AddBuffDuration
function XPerl_Raid_AddBuffDuration(self, partyid, buffID)
	if (self and GameTooltip:IsOwned(self)) then
		if (partyid ~= "player") then
			local left
			if (buffID) then
				local name, rank, icon, count, duration, timeLeft = UnitBuff(partyid, buffID)
				if (duration) then
					left = timeLeft
				end
			end

			if (not left and GetNumRaidMembers() > 0) then
				local stats = XPerl_Roster[UnitName(partyid)]

				if (stats and stats.Buffs) then
					local buffName = getglobal("GameTooltipTextLeft1"):GetText()
					if (buffName) then
						local buff = stats.Buffs[buffName]
						if (buff) then
							if (buff > GetTime()) then
								left = buff - GetTime()
							end
						end
					end
				end

				if (left) then
					GameTooltip:AddLine(SecondsToTime(left)..XPERL_RAID_TOOLTIP_REMAINING, 1, 0.82, 0)
				end
			end

		end

		if (GetNumRaidMembers() > 0) then
			if (conf.buffHelper.enable and partyid and UnitInRaid(partyid)) then
				local buffName = getglobal("GameTooltipTextLeft1"):GetText()
				local tex
				if (partyid == "player" and buffID) then
					local buffIndex = GetPlayerBuff(buffID, "HELPFUL")
					if (buffIndex) then
						tex = GetPlayerBuffTexture(buffIndex)
					end
				else
					local icon = getglobal(self:GetName().."Icon")
					if (not icon) then
						icon = self.icon
					end
					if (icon) then
						tex = icon:GetTexture()
					end
				end

				if (buffName and tex) then
					local names, count = GetNamesWithoutBuff(buffName, tex, IsAltKeyDown())
					if (names) then
						if (IsAltKeyDown()) then
							GameTooltip:AddLine(format(XPERL_RAID_TOOLTIP_WITHBUFF, count), 0.3, 1, 0.2)
						else
							GameTooltip:AddLine(format(XPERL_RAID_TOOLTIP_WITHOUTBUFF, count), 1, 0.3, 0.1)
						end

						if (conf.buffHelper.sort) then
							GameTooltip:AddLine(names, 0.5, 0.5, 0.5)
						else
							GameTooltip:AddLine(names, 0.5, 0.5, 0.5, 1)
						end
					end
				end
			end
		end

		GameTooltip:Show()
	end
end

-- XPerl_Raid_AddDebuffDuration
function XPerl_Raid_AddDebuffDuration(self, partyid, id, cureCast)
	if (GameTooltip:IsOwned(self)) then
		if (partyid ~= "player") then
			local left
			if (buffID) then
				local name, rank, icon, count, debuffType, duration, timeLeft = UnitDebuff(partyid, buffID)
				if (duration) then
					left = timeLeft
				end
			end

			if (not left and GetNumRaidMembers() > 0) then
				local stats = XPerl_Roster[UnitName(partyid)]

				if (stats and stats.Debuffs) then
					local buffName = XPerl_UnitDebuff(partyid, id, cureCast)

					if (buffName) then
						buffName = strsub(buffName, 17)
						local buff = stats.Debuffs[buffName]
						if (buff) then
							if (buff > GetTime()) then
								left = buff - GetTime()
							end
						end
					end
				end

				if (left) then
					GameTooltip:AddLine(SecondsToTime(left)..XPERL_RAID_TOOLTIP_REMAINING, 1, 0.82, 0)
				end
			end
		end

		GameTooltip:Show()
	end
end

-- SetMainHeaderAttributes
local function SetMainHeaderAttributes(self)

	self:Hide()

	self.initialConfigFunction = function(self)
		-- This is the only place we're allowed to set attributes whilst in combat

		self:SetScript("OnAttributeChanged", onAttrChanged)
		XPerl_RegisterClickCastFrame(self)
		XPerl_RegisterClickCastFrame(self.nameFrame)

		Setup1RaidFrame(self)

		self:SetAttribute("*type1", "target")
		self:SetAttribute("type2", "menu")
		self.menu = XPerl_Raid_ShowPopup

		-- Does AllowAttributeChange work for children?
		self.nameFrame:SetAttribute("useparent-unit", true)
		self.nameFrame:SetAttribute("*type1", "target")
		self.nameFrame:SetAttribute("type2", "menu")
		self.nameFrame.menu = XPerl_Raid_ShowPopup

		if (rconf.mana) then
			self:SetAttribute("initial-height", 43)
		else
			self:SetAttribute("initial-height", 38)
		end
	end

	if (rconf.sortAlpha) then
		self:SetAttribute("sortMethod", "NAME")
	else
		self:SetAttribute("sortMethod", nil)
	end

	self:SetAttribute("point", rconf.anchor)
	self:SetAttribute("minWidth", 80)
	self:SetAttribute("minHeight", 10)
	local titleFrame = self:GetParent()
	self:ClearAllPoints()
	if (rconf.anchor == "TOP") then
		self:SetPoint("TOP", titleFrame, "BOTTOM", 0, 0)
		self:SetAttribute("xOffset", 0)
		self:SetAttribute("yOffset", -rconf.spacing)
	elseif (rconf.anchor == "LEFT") then
		self:SetPoint("TOPLEFT", titleFrame, "BOTTOMLEFT", 0, 0)
		self:SetAttribute("xOffset", rconf.spacing)
		self:SetAttribute("yOffset", 0)
	elseif (rconf.anchor == "BOTTOM") then
		self:SetPoint("BOTTOM", titleFrame, "TOP", 0, 0)
		self:SetAttribute("xOffset", 0)
		self:SetAttribute("yOffset", rconf.spacing)
	elseif (rconf.anchor == "RIGHT") then
		self:SetPoint("TOPRIGHT", titleFrame, "BOTTOMRIGHT", 0, 0)
		self:SetAttribute("xOffset", -rconf.spacing)
		self:SetAttribute("yOffset", 0)
	end
end

-- XPerl_Raid_SetAttributes
function XPerl_Raid_ChangeAttributes()

	if (InCombatLockdown()) then
		tinsert(XPerl_OutOfCombatQueue, XPerl_Raid_ChangeAttributes)
		return
	end

	rconf.anchor = (rconf and rconf.anchor) or "TOP"

	local function GroupFilter(n)
		if (rconf.sortByClass) then
			if (rconf.class[n].enable) then
				return rconf.class[n].name
			end
			return ""
		else
			local f
			if (rconf.group[n]) then
				f = tostring(n)
			end

			for i = 1,9 do
				if (rconf.class[i].enable) then
					if (not f) then
						f = rconf.class[i].name
					else
						f = f..","..rconf.class[i].name
					end
				end
			end
			return f
		end
	end

	for i = 1,8 + (rconf.sortByClass or 0) do
		local groupHeader = raidHeaders[i]

		-- Hide this when we change attributes, so the whole re-calc is only done once, instead of for every attribute change
		groupHeader:Hide()

		groupHeader:SetAttribute("strictFiltering", not rconf.sortByClass)
		groupHeader:SetAttribute("groupFilter", GroupFilter(i))
		SetMainHeaderAttributes(groupHeader)
	end

	XPerl_Raid_HideShowRaid()
end

-- XPerl_Raid_Set_Bits
function XPerl_Raid_Set_Bits(self)
	if (raidLoaded) then
		XPerl_ProtectedCall(XPerl_Raid_HideShowRaid)
	end
	SkipHighlightUpdate = nil

	XPerl_ScaleRaid()

	for i = 1,9 do
		XPerl_SavePosition(getglobal("XPerl_Raid_Title"..i), true)
	end

	for i,frame in pairs(FrameArray) do
		Setup1RaidFrame(frame)
	end

	local manaEvents = {"UNIT_DISPLAYPOWER", "UNIT_RAGE", "UNIT_MAXRAGE", "UNIT_ENERGY", "UNIT_MAXENERGY", "UNIT_MANA", "UNIT_MAXMANA"}
	for i,event in pairs(manaEvents) do
		if (rconf.mana) then
			self:RegisterEvent(event)
		else
			self:UnregisterEvent(event)
		end
	end
	SkipHighlightUpdate = nil

	if (GetNumRaidMembers() > 0) then
		XPerl_Raid_Frame:Show()
	end
end
