
-- The "modified attribute" takes the form of: modifier-name-button
-- The modifier is one of "shift-", "ctrl-", "alt-", and the button is a number from 1 through 5.
--
-- For example, you could set an action that is used for unmodified left click like this:
-- self:SetAttribute("action1", value);
-- You could set an action that is used for shift-right-click like this:
-- self:SetAttribute("shift-action2", value);
--
-- You can also use a wildcard in the place of the modifier or button to denote an attribute
-- that is used for any modifier or any button.
--
-- For example, you could set an action that is used for any left click like this:
-- self:SetAttribute("*action1", value);
-- You could set an action that is used for shift-click with any button like this:
-- self:SetAttribute("shift-action*", value);
--
ATTRIBUTE_NOOP = "";
-- You can exclude an action by explicitly setting its value to ATTRIBUTE_NOOP
--
-- For example, you could set an action that was used for all clicks except ctrl-left-click:
-- self:SetAttribute("*action*", value);
-- self:SetAttribute("shift-action1", ATTRIBUTE_NOOP);
--
-- Setting the attribute by itself is equivalent to *attribute*

-- Internal helper function for modifier parsing
local strsplit = strsplit;
local function ParseSplitModifierString(...)
	for i=1, select('#', ...) do
		local mod, rep = strsplit(':', (select(i, ...)));
		if ( IsModifiedClick(mod) ) then
			return rep or mod:lower();
		end
	end
end

-- Given a modifier string which consists of one or more modifier
-- clauses separated by commas, return the id of the first matching
-- clause. If no modifiers match then nil is returned.
--
-- A modifier clause is of the form MODIFIER or the form MODIFIER:prefix
--
-- e.g.
--
-- "SELFCAST:self,CTRL"
--
-- will return 'self' if the self cast modifier is held down, or 'ctrl' if
-- the control key is held down
function SecureButton_ParseModifierString(msg)
	return ParseSplitModifierString(strsplit(',', msg));
end

-- Get the current modifier prefix for a frame (optional), if the frame has
-- a modifiers attribute defined, then it will be parsed using
-- SecureButton_ParseModifiersString and used as the prefix.
--
-- If no frame is specified, or the frame has no modifiers property, then
-- the old style prefix functionality is used.
function SecureButton_GetModifierPrefix(frame)
	-- Handle optional frame modifiers attribute
	if ( frame ) then
		local modlist = frame:GetAttribute("modifiers");
		if ( modlist ) then
			local prefix = SecureButton_ParseModifierString(modlist);
			if ( prefix ) then
				return prefix .. "-";
			end
		end
	end

	local prefix = "";
	if ( IsShiftKeyDown() ) then
		prefix = "shift-"..prefix;
	end
	if ( IsControlKeyDown() ) then
		prefix = "ctrl-"..prefix;
	end
	if ( IsAltKeyDown() ) then
		prefix = "alt-"..prefix;
	end
	return prefix;
end

function SecureButton_GetButtonSuffix(button)
	local suffix = "";
	if ( button == "LeftButton" ) then
		suffix = "1";
	elseif ( button == "RightButton" ) then
		suffix = "2";
	elseif ( button == "MiddleButton" ) then
		suffix = "3";
	elseif ( button == "Button4" ) then
		suffix = "4";
	elseif ( button == "Button5" ) then
		return "5";
	elseif ( button and button ~= "" ) then
		suffix = "-" .. tostring(button);
	end
	return suffix;
end

function SecureButton_GetModifiedAttribute(frame, name, button, prefix, suffix)
	if ( not prefix ) then
		prefix = SecureButton_GetModifierPrefix(frame);
	end
	if ( not suffix ) then
		suffix = SecureButton_GetButtonSuffix(button);
	end
	local value = frame:GetAttribute(prefix, name, suffix);
	if ( not value and (frame:GetAttribute("useparent-"..name) or
						frame:GetAttribute("useparent*")) ) then
		local parent = frame:GetParent();
		if ( parent ) then
			value = SecureButton_GetModifiedAttribute(parent, name, button, prefix, suffix);
		end
	end
	if ( value == ATTRIBUTE_NOOP ) then
		value = nil;
	end
	return value;
end
function SecureButton_GetAttribute(frame, name)
	return SecureButton_GetModifiedAttribute(frame, name, nil, "", "");
end

function SecureButton_GetModifiedUnit(self, button)
	local unit = SecureButton_GetModifiedAttribute(self, "unit", button);
	if ( unit ) then
		local unitsuffix = SecureButton_GetModifiedAttribute(self, "unitsuffix", button);
		if ( unitsuffix ) then
			unit = unit .. unitsuffix;
			-- map raid1pet to raidpet1
			unit = gsub(unit, "^([^%d]+)([%d]+)[pP][eE][tT]", "%1pet%2");
		end
		return unit;
	end
	if ( SecureButton_GetModifiedAttribute(self, "checkselfcast", button) ) then
		if ( IsModifiedClick("SELFCAST") ) then
			return "player";
		end
	end
	if ( SecureButton_GetModifiedAttribute(self, "checkfocuscast", button) ) then
		if ( IsModifiedClick("FOCUSCAST") ) then
			return "focus";
		end
	end
end
function SecureButton_GetUnit(self)
	local unit = SecureButton_GetAttribute(self, "unit");
	if ( unit ) then
		local unitsuffix = SecureButton_GetAttribute(self, "unitsuffix");
		if ( unitsuffix ) then
			unit = unit .. unitsuffix;
			-- map raid1pet to raidpet1
			unit = gsub(unit, "^([^%d]+)([%d]+)[pP][eE][tT]", "%1pet%2");
		end
		return unit;
	end
end

function SecureButton_GetEffectiveButton(self)
	-- We will be returning to implement this later
	return "LeftButton";
end

--
-- SecureActionButton
--
-- SecureActionButtons allow you to map different combinations of modifiers and buttons into
-- actions which are executed when the button is clicked.
--
-- For example, you could set up the button to respond to left clicks by targeting the focus:
-- self:SetAttribute("unit", "focus");
-- self:SetAttribute("type1", "target");
--
-- You could set up all other buttons to bring up a menu like this:
-- self:SetAttribute("type*", "menu");
-- self.showmenu = menufunc;
--
-- SecureActionButtons are also able to perform different actions depending on whether you can
-- attack the unit or assist the unit associated with the action button.  It does so by mapping
-- mouse buttons into "virtual buttons" based on the state of the unit. For example, you can use
-- the following to cast "Mind Blast" on a left click and "Shadow Word: Death" on a right click
-- if the unit can be attacked:
-- self:SetAttribute("harmbutton1", "nuke1");
-- self:SetAttribute("type-nuke1", "spell");
-- self:SetAttribute("spell-nuke1", "Mind Blast");
-- self:SetAttribute("harmbutton2", "nuke2");
-- self:SetAttribute("type-nuke2", "spell");
-- self:SetAttribute("spell-nuke2", "Shadow Word: Death");
--
-- In this example, we use the special attribute "harmbutton" which is used to map a virtual
-- button when the unit is attackable. We also have the attribute "helpbutton" which is used
-- when the unit can be assisted.
--
-- Although it may not be immediately obvious, we are able to use this new virtual button
-- to set up very complex click behaviors on buttons. For example, we can define a new "heal"
-- virtual button for all friendly left clicks, and then set the button to cast "Flash Heal"
-- on an unmodified left click and "Renew" on a ctrl left click:
-- self:SetAttribute("*helpbutton1", "heal");
-- self:SetAttribute("*type-heal", "spell");
-- self:SetAttribute("spell-heal", "Flash Heal");
-- self:SetAttribute("ctrl-spell-heal", "Renew");
--
-- This system is very powerful, and provides a good layer of abstraction for setting up
-- a button's click behaviors.

function SecureActionButton_OnClick(self, button)
	-- Lookup the unit, based on the modifiers and button
	local unit = SecureButton_GetModifiedUnit(self, button);

	-- Remap button suffixes based on the disposition of the unit (contributed by Iriel and Cladhaire)
	if ( unit ) then
		local origButton = button;
		if ( UnitCanAttack("player", unit) )then
			button = SecureButton_GetModifiedAttribute(self, "harmbutton", button) or button;
		elseif ( UnitCanAssist("player", unit) )then
			button = SecureButton_GetModifiedAttribute(self, "helpbutton", button) or button;
		end

		-- The unit may have changed based on button remapping
		if ( button ~= origButton ) then
			unit = SecureButton_GetModifiedUnit(self, button);
		end
	end

	-- Don't do anything if our unit doesn't exist
	if ( unit and unit ~= "none" and not UnitExists(unit) ) then
		return;
	end

	-- Lookup the action type, based on the modifiers and button
	local type = SecureButton_GetModifiedAttribute(self, "type", button);

	-- Perform the requested action!
	if ( type == "actionbar" ) then
		local action = SecureButton_GetModifiedAttribute(self, "action", button);
		if ( action == "increment" ) then
			ActionBar_PageUp();
		elseif ( action == "decrement" ) then
			ActionBar_PageDown();
		elseif ( tonumber(action) ) then
			ChangeActionBarPage(action);
		else
			local a, b = strmatch(action, "^(%d+),%s*(%d+)$");
			if ( GetActionBarPage() == tonumber(a) ) then
				ChangeActionBarPage(b);
			else
				ChangeActionBarPage(a);
			end
		end
	elseif ( type == "action" ) then
		local action = ActionButton_CalculateAction(self, button);
		if ( action ) then
			-- Save macros in case the one for this action is being edited
			securecall("MacroFrame_SaveMacro");

			UseAction(action, unit, button);
		end
	elseif ( type == "pet" ) then
		local action = SecureButton_GetModifiedAttribute(self, "action", button);
		if ( action ) then
			CastPetAction(action, unit);
		end
	elseif ( type == "spell" ) then
		local spell = SecureButton_GetModifiedAttribute(self, "spell", button);
		if ( spell ) then
			CastSpellByName(spell, unit);
		end
	elseif ( type == "item" ) then
		local item = SecureButton_GetModifiedAttribute(self, "item", button);
		if ( not item ) then
			-- Backwards compatibility code, deprecated but still handled for now.
			local bag = SecureButton_GetModifiedAttribute(self, "bag", button);
			local slot = SecureButton_GetModifiedAttribute(self, "slot", button);
			if ( bag and slot ) then
				item = bag.." "..slot;
			else
				item = slot;
			end
		end
		if ( item ) then
			local name, bag, slot = SecureCmdItemParse(item);
			if ( IsEquippableItem(name) and not IsEquippedItem(name) ) then
				EquipItemByName(name);
			else
				SecureCmdUseItem(name, bag, slot, unit);
			end
		end
	elseif ( type == "macro" ) then
		local macro = SecureButton_GetModifiedAttribute(self, "macro", button);
		if ( macro ) then
			-- Save macros in case the one for this action is being edited
			securecall("MacroFrame_SaveMacro");

			RunMacro(macro, button);
		else
			local text = SecureButton_GetModifiedAttribute(self, "macrotext", button);
			if ( text ) then
				RunMacroText(text, button);
			end
		end
	elseif ( type == "cancelaura" ) then
		local index = SecureButton_GetModifiedAttribute(self, "index", button);
		if ( index ) then
			CancelUnitBuff(unit, index);
		else
			local spell = SecureButton_GetModifiedAttribute(self, "spell", button);
			local rank = SecureButton_GetModifiedAttribute(self, "rank", button);
			CancelUnitBuff(unit, spell, rank);
		end
	elseif ( type == "stop" ) then
		if ( SpellIsTargeting() ) then
			SpellStopTargeting();
		end
	elseif ( type == "target" ) then
		if ( unit ) then
			if ( unit == "none" ) then
				ClearTarget();
			elseif ( SpellIsTargeting() ) then
				SpellTargetUnit(unit);
			elseif ( CursorHasItem() ) then
				DropItemOnUnit(unit);
			else
				TargetUnit(unit);
			end
		end
	elseif ( type == "focus" ) then
		FocusUnit(unit);
	elseif ( type == "assist" ) then
		AssistUnit(unit);
	elseif ( type == "maintank" or type == "mainassist" ) then
		local action = SecureButton_GetModifiedAttribute(self, "action", button);
		if ( not action or action == "set" ) then
			SetPartyAssignment(type, unit);
		elseif ( action == "clear" ) then
			ClearPartyAssignment(type, unit);
		elseif ( action == "toggle" ) then
			if ( GetPartyAssignment(type, unit) ) then
				ClearPartyAssignment(type, unit);
			else
				SetPartyAssignment(type, unit);
			end
		end
	elseif ( type == "click" ) then
		local delegate = SecureButton_GetModifiedAttribute(self, "clickbutton", button);
		if ( delegate ) then
			delegate:Click(button);
		end
	elseif ( type == "attribute" ) then
		local frame = SecureButton_GetModifiedAttribute(self, "attribute-frame", button);
		if ( not frame ) then
			frame = self;
		end
		local name = SecureButton_GetModifiedAttribute(self, "attribute-name", button);
		local value = SecureButton_GetModifiedAttribute(self, "attribute-value", button);
		if ( name ) then
			frame:SetAttribute(name, value);
		end
	elseif ( type ) then
		-- Custom action support
		local func = rawget(self, type);
		if ( func ) then
			func(self, unit, button);
		end
	end

	-- Target predefined item, if we just cast a spell that targets an item
	if ( SpellCanTargetItem() ) then
		local bag = SecureButton_GetModifiedAttribute(self, "target-bag", button);
		local slot = SecureButton_GetModifiedAttribute(self, "target-slot", button);
		if ( slot ) then
			if ( bag ) then
				UseContainerItem(bag, slot);
			else
				UseInventoryItem(slot);
			end
		else
			local item = SecureButton_GetModifiedAttribute(self, "target-item", button);
			if ( item ) then
				SpellTargetItem(item);
			end
		end
	end
end

function SecureUnitButton_OnLoad(self, unit, menufunc)
	self:SetAttribute("*type1", "target");
	self:SetAttribute("*type2", "menu");
	self:SetAttribute("unit", unit);
	self.menu = menufunc;
end

function SecureUnitButton_OnClick(self, button)
	local type = SecureButton_GetModifiedAttribute(self, "type", button);
	if ( type == "menu" ) then
		if ( SpellIsTargeting() ) then
			SpellStopTargeting();
			return;
		end
	end
	SecureActionButton_OnClick(self, button);
end

--
-- SecurePartyHeader and SecureRaidGroupHeader contributed with permission by: Esamynn, Cide, and Iriel
--

--[[
List of the various configuration attributes
======================================================
showRaid = [BOOLEAN] -- true if the header should be shown while in a raid
showParty = [BOOLEAN] -- true if the header should be shown while in a party and not in a raid
showPlayer = [BOOLEAN] -- true if the header should show the player when not in a raid
showSolo = [BOOLEAN] -- true if the header should be shown while not in a group (implies showPlayer)
nameList = [STRING] -- a comma separated list of player names (not used if 'groupFilter' is set)
groupFilter = [1-8, STRING] -- a comma seperated list of raid group numbers and/or uppercase class names and/or uppercase roles
strictFiltering = [BOOLEAN] - if true, then characters must match both a group and a class from the groupFilter list
point = [STRING] -- a valid XML anchoring point (Default: "TOP")
xOffset = [NUMBER] -- the x-Offset to use when anchoring the unit buttons (Default: 0)
yOffset = [NUMBER] -- the y-Offset to use when anchoring the unit buttons (Default: 0)
sortMethod = ["INDEX", "NAME"] -- defines how the group is sorted (Default: "INDEX")
sortDir = ["ASC", "DESC"] -- defines the sort order (Default: "ASC")
template = [STRING] -- the XML template to use for the unit buttons
templateType = [STRING] - specifies the frame type of the managed subframes (Default: "Button")
groupBy = [nil, "GROUP", "CLASS", "ROLE"] - specifies a "grouping" type to apply before regular sorting (Default: nil)
groupingOrder = [STRING] - specifies the order of the groupings (ie. "1,2,3,4,5,6,7,8")
maxColumns = [NUMBER] - maximum number of columns the header will create (Default: 1)
unitsPerColumn = [NUMBER or nil] - maximum units that will be displayed in a singe column, nil is infinate (Default: nil)
startingIndex = [NUMBER] - the index in the final sorted unit list at which to start displaying units (Default: 1)
columnSpacing = [NUMBER] - the ammount of space between the rows/columns (Default: 0)
columnAnchorPoint = [STRING] - the anchor point of each new column (ie. use LEFT for the columns to grow to the right)
--]]

function SecureGroupHeader_OnLoad(self)
	self:RegisterEvent("PARTY_MEMBERS_CHANGED");
	self:RegisterEvent("UNIT_NAME_UPDATE");
end

function SecureGroupHeader_OnEvent(self, event, ...)
	if ( (event == "PARTY_MEMBERS_CHANGED" or event == "UNIT_NAME_UPDATE") and self:IsVisible() ) then
		SecureGroupHeader_Update(self);
	end
end

function SecureGroupHeader_OnAttributeChanged(self, name, value)
	if ( self:IsVisible() ) then
		SecureGroupHeader_Update(self);
	end
end

-- relativePoint, xMultiplier, yMultiplier = getRelativePointAnchor( point )
-- Given a point return the opposite point and which axes the point
-- depends on.
local function getRelativePointAnchor( point )
	point = strupper(point);
	if (point == "TOP") then
		return "BOTTOM", 0, -1;
	elseif (point == "BOTTOM") then
		return "TOP", 0, 1;
	elseif (point == "LEFT") then
		return "RIGHT", 1, 0;
	elseif (point == "RIGHT") then
		return "LEFT", -1, 0;
	elseif (point == "TOPLEFT") then
		return "BOTTOMRIGHT", 1, -1;
	elseif (point == "TOPRIGHT") then
		return "BOTTOMLEFT", -1, -1;
	elseif (point == "BOTTOMLEFT") then
		return "TOPRIGHT", 1, 1;
	elseif (point == "BOTTOMRIGHT") then
		return "TOPLEFT", -1, 1;
	else
		return "CENTER", 0, 0;
	end
end

function ApplyUnitButtonConfiguration( ... )
	for i = 1, select("#", ...), 1 do
		local frame = select(i, ...);
		local anchor = frame:GetAttribute("initial-anchor");
		local width = tonumber(frame:GetAttribute("initial-width") or nil);
		local height = tonumber(frame:GetAttribute("initial-height")or nil);
		local scale = tonumber(frame:GetAttribute("initial-scale")or nil);
		local unitWatch = frame:GetAttribute("initial-unitWatch");
		if ( anchor ) then
			local point, relPoint, xOffset, yOffset = strsplit(",", anchor);
			relpoint = relpoint or point;
			xOffset = tonumber(xOffset) or 0;
			yOffset = tonumber(yOffset) or 0;
			frame:SetPoint(point, frame:GetParent(), relPoint, xOffset, yOffset);
		end
		if ( width ) then
			frame:SetWidth(width);
		end
		if ( height ) then
			frame:SetHeight(height);
		end
		if ( scale ) then
			frame:SetScale(scale);
		end
		if ( unitWatch ) then
			if ( unitWatch == "state" ) then
				RegisterUnitWatch(frame, true);
			else
				RegisterUnitWatch(frame);
			end
		end

		-- call this function recursively for the current frame's children
		ApplyUnitButtonConfiguration(frame:GetChildren());
	end
end

local function ApplyConfig( header, newChild, defaultConfigFunction )
	local configFunction = header.initialConfigFunction or defaultConfigFunction;
	if ( type(configFunction) == "function" ) then
		configFunction(newChild);
		return true;
	end
end

function SetupUnitButtonConfiguration( header, newChild, defaultConfigFunction )
	newChild:AllowAttributeChanges();
	if ( securecall(ApplyConfig, header, newChild, defaultConfigFunction) ) then
		ApplyUnitButtonConfiguration(newChild);
	end
end

-- empties tbl and assigns the value true to each key passed as part of ...
local function fillTable( tbl, ... )
	for key in pairs(tbl) do
		tbl[key] = nil;
	end
	for i = 1, select("#", ...), 1 do
		local key = select(i, ...);
		key = tonumber(key) or key;
		tbl[key] = true;
	end
end

-- same as fillTable() except that each key is also stored in
-- the array portion of the table in order
local function doubleFillTable( tbl, ... )
	fillTable(tbl, ...);
	for i = 1, select("#", ...), 1 do
		tbl[i] = select(i, ...);
	end
end

--working tables
local tokenTable = {};
local sortingTable = {};
local groupingTable = {};
local tempTable = {};

-- creates child frames and finished configuring them
local function configureChildren(self)
	local point = self:GetAttribute("point") or "TOP"; --default anchor point of "TOP"
	local relativePoint, xOffsetMult, yOffsetMult = getRelativePointAnchor(point);
	local xMultiplier, yMultiplier =  abs(xOffsetMult), abs(yOffsetMult);
	local xOffset = self:GetAttribute("xOffset") or 0; --default of 0
	local yOffset = self:GetAttribute("yOffset") or 0; --default of 0
	local sortDir = self:GetAttribute("sortDir") or "ASC"; --sort ascending by default
	local columnSpacing = self:GetAttribute("columnSpacing") or 0;
	local startingIndex = self:GetAttribute("startingIndex") or 1;

	local unitCount = #sortingTable;
	local numDisplayed = unitCount - (startingIndex - 1);
	local unitsPerColumn = self:GetAttribute("unitsPerColumn");
	local numColumns;
	if ( unitsPerColumn and numDisplayed > unitsPerColumn ) then
		numColumns = min( ceil(numDisplayed / unitsPerColumn), (self:GetAttribute("maxColumns") or 1) );
	else
		unitsPerColumn = numDisplayed;
		numColumns = 1;
	end
	local loopStart = startingIndex;
	local loopFinish = min((startingIndex - 1) + unitsPerColumn * numColumns, unitCount)
	local step = 1;

	numDisplayed = loopFinish - (loopStart - 1);

	if ( sortDir == "DESC" ) then
		loopStart = unitCount - (startingIndex - 1);
		loopFinish = loopStart - (numDisplayed - 1);
		step = -1;
	end

	-- ensure there are enough buttons
	local needButtons = max(1, numDisplayed);
	if not ( self:GetAttribute("child"..needButtons) ) then
		local buttonTemplate = self:GetAttribute("template");
		local templateType = self:GetAttribute("templateType") or "Button";
		local name = self:GetName();
		if not ( name ) then
			self:Hide();
			return;
		end
		for i = 1, needButtons, 1 do
			local childAttr = "child" .. i;
			if not ( self:GetAttribute(childAttr) ) then
					local newButton = CreateFrame(templateType, name.."UnitButton"..i, self, buttonTemplate);
					SetupUnitButtonConfiguration(self, newButton);
					self:SetAttribute(childAttr, newButton);
					SecureHandlerSetFrameRef(self, childAttr, newButton);
			end
		end
	end

	local columnAnchorPoint, columnRelPoint, colxMulti, colyMulti;
	if ( numColumns > 1 ) then
		columnAnchorPoint = self:GetAttribute("columnAnchorPoint");
		columnRelPoint, colxMulti, colyMulti = getRelativePointAnchor(columnAnchorPoint);
	end

	local buttonNum = 0;
	local columnNum = 1;
	local columnUnitCount = 0;
	local currentAnchor = self;
	for i = loopStart, loopFinish, step do
		buttonNum = buttonNum + 1;
		columnUnitCount = columnUnitCount + 1;
		if ( columnUnitCount > unitsPerColumn ) then
			columnUnitCount = 1;
			columnNum = columnNum + 1;
		end

		local unitButton = self:GetAttribute("child"..buttonNum);
		unitButton:Hide();
		unitButton:ClearAllPoints();
		if ( buttonNum == 1 ) then
			unitButton:SetPoint(point, currentAnchor, point, 0, 0);
			if ( columnAnchorPoint ) then
				unitButton:SetPoint(columnAnchorPoint, currentAnchor, columnAnchorPoint, 0, 0);
			end

		elseif ( columnUnitCount == 1 ) then
			local columnAnchor = self:GetAttribute("child"..(buttonNum - unitsPerColumn));
			unitButton:SetPoint(columnAnchorPoint, columnAnchor, columnRelPoint, colxMulti * columnSpacing, colyMulti * columnSpacing);

		else
			unitButton:SetPoint(point, currentAnchor, relativePoint, xMultiplier * xOffset, yMultiplier * yOffset);
		end
		unitButton:SetAttribute("unit", sortingTable[sortingTable[i]]);
		unitButton:Show();

		currentAnchor = unitButton;
	end
	repeat
		buttonNum = buttonNum + 1;
		local unitButton = self:GetAttribute("child"..buttonNum);
		if ( unitButton ) then
			unitButton:Hide();
			unitButton:SetAttribute("unit", nil);
		end
	until not ( unitButton )

	local unitButton = self:GetAttribute("child1");
	local unitButtonWidth = unitButton:GetWidth();
	local unitButtonHeight = unitButton:GetHeight();
	if ( numDisplayed > 0 ) then
		local width = xMultiplier * (unitsPerColumn - 1) * unitButtonWidth + ( (unitsPerColumn - 1) * (xOffset * xOffsetMult) ) + unitButtonWidth;
		local height = yMultiplier * (unitsPerColumn - 1) * unitButtonHeight + ( (unitsPerColumn - 1) * (yOffset * yOffsetMult) ) + unitButtonHeight;

		if ( numColumns > 1 ) then
			width = width + ( (numColumns -1) * abs(colxMulti) * (width + columnSpacing) );
			height = height + ( (numColumns -1) * abs(colyMulti) * (height + columnSpacing) );
		end

		self:SetWidth(width);
		self:SetHeight(height);
	else
		local minWidth = self:GetAttribute("minWidth") or (yMultiplier * unitButtonWidth);
		local minHeight = self:GetAttribute("minHeight") or (xMultiplier * unitButtonHeight);
		self:SetWidth( max(minWidth, 0.1) );
		self:SetHeight( max(minHeight, 0.1) );
	end
end

local function GetGroupHeaderType(self)
	local type, start, stop;

	local nRaid = GetNumRaidMembers();
	local nParty = GetNumPartyMembers();
	if ( nRaid > 0 and self:GetAttribute("showRaid") ) then
		type = "RAID";
	elseif ( (nRaid > 0 or nParty > 0) and self:GetAttribute("showParty") ) then
		type = "PARTY";
	elseif ( self:GetAttribute("showSolo") ) then
		type = "SOLO";
	end
	if ( type ) then
		if ( type == "RAID" ) then
			start = 1;
			stop = nRaid;
		else
			if ( type == "SOLO" or self:GetAttribute("showPlayer") ) then
				start = 0;
			else
				start = 1;
			end
			stop = nParty;
		end
	end
	return type, start, stop;
end

local function GetGroupRosterInfo(type, index)
	local _, unit, name, subgroup, className, role;
	if ( type == "RAID" ) then
		unit = "raid"..index;
		name, _, subgroup, _, _, className, _, _, _, role = GetRaidRosterInfo(index);
	else
		if ( index > 0 ) then
			unit = "party"..index;
		else
			unit = "player";
		end
		if ( UnitExists(unit) ) then
			name = UnitName(unit);
			_, className = UnitClass(unit);
			if ( GetPartyAssignment("MAINTANK", unit) ) then
				role = "MAINTANK";
			elseif ( GetPartyAssignment("MAINASSIST", unit) ) then
				role = "MAINASSIST";
			end
		end
		subgroup = 1;
	end
	return unit, name, subgroup, className, role;
end

function SecureGroupHeader_Update(self)
	local nameList = self:GetAttribute("nameList");
	local groupFilter = self:GetAttribute("groupFilter");
	local sortMethod = self:GetAttribute("sortMethod");
	local groupBy = self:GetAttribute("groupBy");

	for key in pairs(sortingTable) do
		sortingTable[key] = nil;
	end

	-- See if this header should be shown
	local type, start, stop = GetGroupHeaderType(self);
	if ( not type ) then
		configureChildren(self);
		return;
	end

	if ( not groupFilter and not nameList ) then
		groupFilter = "1,2,3,4,5,6,7,8";
	end

	if ( groupFilter ) then
		-- filtering by a list of group numbers and/or classes
		fillTable(tokenTable, strsplit(",", groupFilter));
		local strictFiltering = self:GetAttribute("strictFiltering"); -- non-strict by default
		for i = start, stop, 1 do
			local unit, name, subgroup, className, role = GetGroupRosterInfo(type, i);
			if ( name and
				 ((not strictFiltering) and
				  (tokenTable[subgroup] or tokenTable[className] or (role and tokenTable[role])) -- non-strict filtering
				 ) or
				  (tokenTable[subgroup] and tokenTable[className]) -- strict filtering
			) then
				tinsert(sortingTable, name);
				sortingTable[name] = unit;
				if ( groupBy == "GROUP" ) then
					groupingTable[name] = subgroup;

				elseif ( groupBy == "CLASS" ) then
					groupingTable[name] = className;

				elseif ( groupBy == "ROLE" ) then
					groupingTable[name] = role;

				end
			end
		end

		if ( groupBy ) then
			local groupingOrder = self:GetAttribute("groupingOrder");
			doubleFillTable(tokenTable, strsplit(",", groupingOrder));
			for k in pairs(tempTable) do
				tempTable[k] = nil;
			end
			for _, grouping in ipairs(tokenTable) do
				grouping = tonumber(grouping) or grouping;
				for k in ipairs(groupingTable) do
					groupingTable[k] = nil;
				end
				for index, name in ipairs(sortingTable) do
					if ( groupingTable[name] == grouping ) then
						tinsert(groupingTable, name);
						tempTable[name] = true;
					end
				end
				if ( sortMethod == "NAME" ) then -- sort by ID by default
					table.sort(groupingTable);
				end
				for _, name in ipairs(groupingTable) do
					tinsert(tempTable, name);
				end
			end
			-- handle units whose group didn't appear in groupingOrder
			for k in ipairs(groupingTable) do
				groupingTable[k] = nil;
			end
			for index, name in ipairs(sortingTable) do
				if not ( tempTable[name] ) then
					tinsert(groupingTable, name);
				end
			end
			if ( sortMethod == "NAME" ) then -- sort by ID by default
				table.sort(groupingTable);
			end
			for _, name in ipairs(groupingTable) do
				tinsert(tempTable, name);
			end

			--copy the names back to sortingTable
			for index, name in ipairs(tempTable) do
				sortingTable[index] = name;
			end

		elseif ( sortMethod == "NAME" ) then -- sort by ID by default
			table.sort(sortingTable);

		end

	else
		-- filtering via a list of names
		doubleFillTable(sortingTable, strsplit(",", nameList));
		for i = start, stop, 1 do
			local unit, name = GetGroupRosterInfo(type, i);
			if ( sortingTable[name] ) then
				sortingTable[name] = unit;
			end
		end
		for i = #sortingTable, 1, -1 do
			local name = sortingTable[i];
			if ( sortingTable[name] == true ) then
				tremove(sortingTable, i);
			end
		end
		if ( sortMethod == "NAME" ) then
			table.sort(sortingTable);
		end

	end

	configureChildren(self);
end

--[[
The Pet Header accepts all of the various configuration attributes of the
regular raid header, as well as the following
======================================================
useOwnerUnit = [BOOLEAN] - if true, then the owner's unit string is set on managed's frames "unit" attribute (instead of pet's)
filterOnPet = [BOOLEAN] - if true, then pet names are used when sorting/filtering the list
--]]

function SecureGroupPetHeader_OnLoad(self)
	self:RegisterEvent("PARTY_MEMBERS_CHANGED");
	self:RegisterEvent("UNIT_NAME_UPDATE");
	self:RegisterEvent("UNIT_PET");
end

function SecureGroupPetHeader_OnEvent(self, event, ...)
	if ( (event == "PARTY_MEMBERS_CHANGED" or event == "UNIT_NAME_UPDATE" or event == "UNIT_PET") and self:IsVisible() ) then
		SecureGroupPetHeader_Update(self);
	end
end

function SecureGroupPetHeader_OnAttributeChanged(self, name, value)
	if ( self:IsVisible() ) then
		SecureGroupPetHeader_Update(self);
	end
end

local function GetPetUnit(type, index)
	if ( type == "RAID" ) then
		return "raidpet"..index;
	elseif ( index > 0 ) then
		return "partypet"..index;
	else
		return "pet";
	end
end

function SecureGroupPetHeader_Update(self)
	local nameList = self:GetAttribute("nameList");
	local groupFilter = self:GetAttribute("groupFilter");
	local sortMethod = self:GetAttribute("sortMethod");
	local groupBy = self:GetAttribute("groupBy");
	local useOwnerUnit = self:GetAttribute("useOwnerUnit");
	local filterOnPet = self:GetAttribute("filterOnPet");

	for key in pairs(sortingTable) do
		sortingTable[key] = nil;
	end

	-- See if this header should be shown
	local type, start, stop = GetGroupHeaderType(self);
	if ( not type ) then
		configureChildren(self);
		return;
	end

	if ( not groupFilter and not nameList ) then
		groupFilter = "1,2,3,4,5,6,7,8";
	end

	if ( groupFilter ) then
		-- filtering by a list of group numbers and/or classes
		fillTable(tokenTable, strsplit(",", groupFilter));
		local strictFiltering = self:GetAttribute("strictFiltering"); -- non-strict by default
		for i = start, stop, 1 do
			local unit, name, subgroup, className, role = GetGroupRosterInfo(type, i);
			local petUnit = GetPetUnit(type, i);
			if ( filterOnPet ) then
				name = UnitName(petUnit);
			end
			if not ( useOwnerUnit ) then
				unit = petUnit;
			end
			if ( UnitExists(petUnit) ) then
				if ( name and
					 ((not strictFiltering) and
					  (tokenTable[subgroup] or tokenTable[className] or (role and tokenTable[role])) -- non-strict filtering
					 ) or
					  (tokenTable[subgroup] and tokenTable[className]) -- strict filtering
				) then
					tinsert(sortingTable, name);
					sortingTable[name] = unit;
					if ( groupBy == "GROUP" ) then
						groupingTable[name] = subgroup;

					elseif ( groupBy == "CLASS" ) then
						groupingTable[name] = className;

					elseif ( groupBy == "ROLE" ) then
						groupingTable[name] = role;

					end
				end
			end
		end

		if ( groupBy ) then
			local groupingOrder = self:GetAttribute("groupingOrder");
			doubleFillTable(tokenTable, strsplit(",", groupingOrder));
			for k in pairs(tempTable) do
				tempTable[k] = nil;
			end
			for _, grouping in ipairs(tokenTable) do
				grouping = tonumber(grouping) or grouping;
				for k in ipairs(groupingTable) do
					groupingTable[k] = nil;
				end
				for index, name in ipairs(sortingTable) do
					if ( groupingTable[name] == grouping ) then
						tinsert(groupingTable, name);
						tempTable[name] = true;
					end
				end
				if ( sortMethod == "NAME" ) then -- sort by ID by default
					table.sort(groupingTable);
				end
				for _, name in ipairs(groupingTable) do
					tinsert(tempTable, name);
				end
			end
			-- handle units whose group didn't appear in groupingOrder
			for k in ipairs(groupingTable) do
				groupingTable[k] = nil;
			end
			for index, name in ipairs(sortingTable) do
				if not ( tempTable[name] ) then
					tinsert(groupingTable, name);
				end
			end
			if ( sortMethod == "NAME" ) then -- sort by ID by default
				table.sort(groupingTable);
			end
			for _, name in ipairs(groupingTable) do
				tinsert(tempTable, name);
			end

			--copy the names back to sortingTable
			for index, name in ipairs(tempTable) do
				sortingTable[index] = name;
			end

		elseif ( sortMethod == "NAME" ) then -- sort by ID by default
			table.sort(sortingTable);

		end

	else
		-- filtering via a list of names
		doubleFillTable(sortingTable, strsplit(",", nameList));
		for i = start, stop, 1 do
			local unit, name = GetGroupRosterInfo(type, i);
			local petUnit = GetPetUnit(type, i);
			if ( filterOnPet ) then
				name = UnitName(petUnit);
			end
			if not ( useOwnerUnit ) then
				unit = petUnit;
			end
			if ( sortingTable[name] and UnitExists(petUnit) ) then
				sortingTable[name] = unit;
			end
		end
		for i = #sortingTable, 1, -1 do
			local name = sortingTable[i];
			if ( sortingTable[name] == true ) then
				tremove(sortingTable, i);
			end
		end
		if ( sortMethod == "NAME" ) then
			table.sort(sortingTable);
		end

	end

	if ( useOwnerUnit and filterOnPet ) then
		-- sorting table currently contains pet unit strings and needs to contain owner unit strings
		for i, name in ipairs(sortingTable) do
			local unit = sortingTable[name];
			sortingTable[name] = gsub(unit, "raidpet([%d]+)", "raid%1");
		end

	elseif ( not useOwnerUnit and not filterOnPet ) then
		-- sorting table currently contains owner unit strings and needs to contain pet unit strings
		for i, name in ipairs(sortingTable) do
			local unit = sortingTable[name];
			sortingTable[name] = gsub(unit, "raid([%d]+)", "raidpet%1");
		end

	end

	configureChildren(self);
end
