--[[
	Non Event functions for FlexBar
	Last Modified
		01/09/2005	Initial version
		08/05/2005	Fixed concatenation errors - Ratbert_CP
		08/05/2005	Fixed arethmetic error - Ratbert_CP
		08/06/2005	Fixed spelling error in GetText to GetText() - Shirtan
		08/10/2005  Minor changes for floating buttons, fixed sort for script List	- Sherkhan
		08/12/2005  Reorganized LoadProfile, and many instances where we were applying things globally to all buttons	- Sherkhan
					instead of just to an individual button or group. Hopefully will truly fix Floating Button issue.
					Also made changes to reduce looping for efficiency.
		08/12/2005  Added Text3 Field	- Sherkhan
		08/21/2005	Added commands for showing button information on KeyBinding key pressed
--]]

local util = Utility_Class:New()

-- Function added by cnc: Update  of a button if the text sub for it is set
function FlexBarButton_UpdateItemCount()
	for buttonnum = 1,120 do
		if FBState[buttonnum]["hotkeytext"] ~= nil and string.sub(FBState[buttonnum]["hotkeytext"], 1, 11) == "%itemcount " then
			local item = string.sub(FBState[buttonnum]["hotkeytext"], 12);
			local text = getglobal("FlexBarButton"..buttonnum.."HotKey");
			if(FBBagContents[item] ~= nil) then
				text:SetText(FBBagContents[item]["count"]);
			else
				text:SetText("0");
			end
		end
		if FBState[buttonnum]["text2"] ~= nil and string.sub(FBState[buttonnum]["text2"], 1, 11) == "%itemcount " then
			local item = string.sub(FBState[buttonnum]["text2"], 12);
			local text = getglobal("FlexBarButton"..buttonnum.."Text2");
			if(FBBagContents[item] ~= nil) then
				text:SetText(FBBagContents[item]["count"]);
			else
				text:SetText("0");
			end
		end
		if FBState[buttonnum]["text3"] ~= nil and string.sub(FBState[buttonnum]["text3"], 1, 11) == "%itemcount " then
			local item = string.sub(FBState[buttonnum]["text2"], 12);
			local text = getglobal("FlexBarButton"..buttonnum.."Text3")
			if(FBBagContents[item] ~= nil) then
				text:SetText(FBBagContents[item]["count"]);
			else
				text:SetText("0");
			end
		end
	end
end



function FB_SetTheorycraftData(force)
	-- If a button number is specified we only update that button
	-- Stop if we dont have to do an update
	if(TheoryCraft_DataVersion ~= "1.06") then return; end;
	if(TheoryCraft_UpdatedButtons["FlexbarTC"] == true and force ~= true) then return; end;
	if(FB_DebugFlag == 1) then
		FB_ReportToUser("<< FB_SetTheorycraftData: Flexbar theorycraft text updating >>"); 
	end
	-- Player is in combat, we only update one button per frame then
	if(UnitAffectingCombat("player") or TheoryCraft_Settings["framebyframe"]) then
		if(FB_Currentlyupdating == nil) then FB_Currentlyupdating = 1;
		else FB_Currentlyupdating = FB_Currentlyupdating +1; end;
		-- Last button was updated already, we're done
		if(FB_Currentlyupdating > FBNumButtons) then
			TheoryCraft_UpdatedButtons["FlexbarTC"] = true;
			FB_Currentlyupdating = nil;
			return;
		end
		if(FBState[FB_Currentlyupdating]["hotkeytext"] ~= nil and string.sub(FBState[FB_Currentlyupdating]["hotkeytext"], 1, 4) == "%tc ") then
			getglobal("FlexBarButton"..FB_Currentlyupdating.."HotKey"):SetText(FB_GetSpellDataByID(getglobal("FlexBarButton"..FB_Currentlyupdating):GetID(), string.sub(FBState[FB_Currentlyupdating]["hotkeytext"], 5)));	
		end
		if(FBState[FB_Currentlyupdating]["text2"] ~= nil and string.sub(FBState[FB_Currentlyupdating]["text2"], 1, 4) == "%tc ") then
			getglobal("FlexBarButton"..FB_Currentlyupdating.."Text2"):SetText(FB_GetSpellDataByID(getglobal("FlexBarButton"..FB_Currentlyupdating):GetID(), string.sub(FBState[FB_Currentlyupdating]["text2"], 5)));
		end
		if(FBState[FB_Currentlyupdating]["text3"] ~= nil and string.sub(FBState[FB_Currentlyupdating]["text3"], 1, 4) == "%tc ") then
			getglobal("FlexBarButton"..FB_Currentlyupdating.."Text3"):SetText(FB_GetSpellDataByID(getglobal("FlexBarButton"..FB_Currentlyupdating):GetID(), string.sub(FBState[FB_Currentlyupdating]["text3"], 5)));
		end
	else
		for i = 1, FBNumButtons do
			if(FBState[i]["hotkeytext"] ~= nil and string.sub(FBState[i]["hotkeytext"], 1, 4) == "%tc ") then
				getglobal("FlexBarButton"..i.."HotKey"):SetText(FB_GetSpellDataByID(getglobal("FlexBarButton"..i):GetID(), string.sub(FBState[i]["hotkeytext"], 5)));	
			end
			if(FBState[i]["text2"] ~= nil and string.sub(FBState[i]["text2"], 1, 4) == "%tc ") then
				getglobal("FlexBarButton"..i.."Text2"):SetText(FB_GetSpellDataByID(getglobal("FlexBarButton"..i):GetID(), string.sub(FBState[i]["text2"], 5)));
			end
			if(FBState[i]["text3"] ~= nil and string.sub(FBState[i]["text3"], 1, 4) == "%tc ") then
				getglobal("FlexBarButton"..i.."Text3"):SetText(FB_GetSpellDataByID(getglobal("FlexBarButton"..i):GetID(), string.sub(FBState[i]["text3"], 5)));
			end
		end
		-- Loop is over, we're done
		TheoryCraft_UpdatedButtons["FlexbarTC"] = true;
		return;
	end
end


function FB_GetSpellDataByID(id, data)
  -- taken from cncfanatics' code. This function returns spell data we don't
  -- have in our tooltips. For 2.0, I suggest expanding the tooltip to its
  -- full glory.
  -- XXX: Please optimize me.
  -- TODO: Please optimize me.
	if(id < 1 or id > FBNumButtons) then return(""); end;
	FlexBarScanningTooltip:SetAction(id);
	spelldata = TheoryCraft_GetSpellDataByFrame(FlexBarScanningTooltip);
	if(spelldata == nil)  then return(""); end;
	if(spelldata[data] == nil) then return(""); end;
	if(type(spelldata[data]) == "number") then return(tonumber( string.format("%.".."0".."f", spelldata[data] ) ));
	else return(spelldata[data]); end;
end

function FB_GetSpellNameByID(id)
	if(id < 1 or id > FBNumButtons) then return(""); end;
	FlexBarScanningTooltip:ClearLines()
	FlexBarScanningTooltip:SetAction(id);
	return getglobal("FlexBarScanningTooltipTextLeft".. 1):GetText();
end

function FB_SetKeyBindingName(buttonnum)
	id = getglobal("FlexBarButton" .. buttonnum):GetID();
	spellname = FB_GetSpellNameByID(id);
	if(spellname ~= nil) then
		setglobal("BINDING_NAME_FLEXACTIONBUTTON" .. buttonnum, "Button " .. buttonnum .. "(" .. spellname .. ")");
	else
		setglobal("BINDING_NAME_FLEXACTIONBUTTON" .. buttonnum, "Button " .. buttonnum);
	end
end

function FB_ApplySettings()
-- For each button, apply the saved settings. 
-- Originally every slash command came through here.
-- changed that so that any conditional slash commands
-- call the appropriate apply function only for the buttons
-- they are changing, and save only the setting they are
-- changing to the profile.  The reason being I don't want
-- all the potentially expensive code in scaling etc to execute
-- on conditionals if they don't have to.
	local index
	for index = 1,FBNumButtons do
		FB_Apply_SingleSetting(index)
	end
end

function FB_GetCoordsForGroup(groupnum)
	-- Just update the group coordinates, instead of all groups or all buttons
	local members = FB_GetGroupMembers(groupnum)
	if members.n < 1 then return end

	for index = 1, members.n do
		FB_GetCoords(members[index])
	end
	
end

function FB_Apply_SingleSetting(buttonnum)
	-- Removed all code for Saving anything to the Character Profile from this function as the Save
	-- now takes place on individual move or change actions on a per button basis - Never globally
	-- Apply settings to a single button
	local button, frame = FB_GetWidgets(buttonnum)
	-- Apply hide state
	FB_ApplyHidden(buttonnum)
	-- Apply alpha
	FB_ApplyAlpha(buttonnum)
	-- Apply scale
	FB_ApplyScale(buttonnum)
	-- Apply lock state
	FB_ApplyLocked(buttonnum)
	-- Apply remap
	FB_ApplyRemap(buttonnum)
	-- Apply show grid
	FB_ApplyGrid(buttonnum)
	-- Save button state to character profile
	FB_SaveState(buttonnum)

	FlexBarButton_UpdateUsable(button)

	-- Update hotkey text
	if FBButtonInfoShown == true then 
		FB_ShowSingleButtonInfo(buttonnum)
	else
		FB_TextSub(buttonnum)
		FlexBarButton_UpdateHotkeys(button)
		FB_ApplyTextPosition(buttonnum)
	end
end

function FB_ResyncSettings()
-- For each button, get coords and save to profile. 
-- Coords are only saved by wow for user placed items - the others
-- don't get saved.
	local index
	
	-- check to see if the scale has gotten munged by an alt tab, and reapply settings
	if FlexBar:GetScale() > .25 then
		FB_LoadProfile()
		return
	end
	-- Removed code that would get the button coordinates from here. 
	-- No reason to update the save profile coordinates on a timer.
end

function FB_ReformAllGroups()
	-- For each button, check group status, attach to appropriate frame. 
	-- This function is called as part of FB_LoadProfile Only - otherwise the individual groups or
	-- buttons are set.
	if(FB_DebugFlag == 1) then
		FB_ReportToUser("<< FB_ReformAllGroups: All Groups are Being Reformed >>"); 
	end

	local index
	for index = 1,FBNumButtons do
		-- Check to see that buttons have base coords in the current profile, if not then get some.
		-- This cas can occur when FlexBar is initially installed, or has not been configured for
		-- a character (resetall as well)
		if((not FBState[index]["xcoord"]) or (not FBState[index]["ycoord"])) then
			FB_UpdateButtonLoc(index)
		end
	
		local button, frame = FB_GetWidgets(index)
		if FBState[index]["group"] and (FBState[index]["group"] ~= index) then
			FB_LockButton(index)
			local anchor = FBState[index]["group"]
			
			-- Check to see that anchor button has base coords, if not then get some !
			if((not FBState[anchor]["xcoord"]) or (not FBState[anchor]["ycoord"])) then
				FB_UpdateButtonLoc(anchor)
			end
			
			local x = FBState[index]["xcoord"] - FBState[anchor]["xcoord"]
			local y = FBState[index]["ycoord"] - FBState[anchor]["ycoord"]
			frame:ClearAllPoints()
			frame:SetPoint("BOTTOMRIGHT", "FlexFrame" .. FBState[index]["group"], "BOTTOMRIGHT", x, y)
			FBState[index]["locked"] = true
		elseif FBState[index]["group"] and (FBState[index]["group"] == index) then
			local x = FBState[index]["xcoord"]
			local y = FBState[index]["ycoord"]
			frame:ClearAllPoints()
			frame:SetPoint("BOTTOMRIGHT", "UIParent", "BOTTOMLEFT", x, y)
		else
			local x = FBState[index]["xcoord"]
			local y = FBState[index]["ycoord"]
			frame:ClearAllPoints()
			frame:SetPoint("BOTTOMRIGHT", "UIParent", "BOTTOMLEFT", x, y)
		end
		-- Changed ApplySettings to this call (does a loop once, instead of 2 seperate loops, accomplishes same thing
		FB_Apply_SingleSetting(index)
	end

	-- Generate group data for use InGroupBounds()
	for index = 1, FBNumButtons do
		-- local button, frame = FB_GetWidgets(index) (needless call was being made, values never used here)
		if FBState[index]["group"] == index then
			FBGroupData[index] = FB_GetBoundingButtons(index)
		else
			FBGroupData[index] = nil;
		end
	end
end

function FB_DisbandGroup(group)
	-- For each group button in the group to be disbanded, attach to UIParent. 
	local index
	local members = FB_GetGroupMembers(group)
	
	if members.n < 1 then return end

	if(FB_DebugFlag == 1) then
		FB_ReportToUser("<< FB_DisbandGroup: Group #"..group.." being Disbanded >>"); 
	end

	for index = 1, members.n do
		local button, frame = FB_GetWidgets(members[index])
		local x = FBState[members[index]]["xcoord"]
		local y = FBState[members[index]]["ycoord"]
		frame:ClearAllPoints()
		frame:SetPoint("BOTTOMRIGHT", "UIParent", "BOTTOMLEFT", x, y)
		FB_Apply_SingleSetting(members[index])
	end

	-- Clear group Bounding data
	FBGroupData[group] = nil;
end

function FB_ReformGroup(group)
	-- For each group button, attach to appropriate frame. 
	local index
	local members = FB_GetGroupMembers(group)
	
	if members.n < 1 then return end

	if(FB_DebugFlag == 1) then
		FB_ReportToUser("<< FB_ReformGroup: Group #"..group.." Being Reformed >>"); 
	end

	-- Check to see that buttons have base coords, if not then get some 
	-- The only time no base coordinates will exist is after a resetall or when
	-- FlexBar is initially being configured for a character.
	if ((not FBState[group]["xcoord"]) or (not FBState[group]["ycoord"])) then
		FB_GetCoordsForGroup(group)
	end
	
	for index = 1, members.n do
		local button, frame = FB_GetWidgets(members[index])
		if (FBState[members[index]]["group"] ~= members[index]) then
			-- Group button attach to Anchor with relative coordintates on frame
			FB_LockButton(members[index])
			local x = FBState[members[index]]["xcoord"] - FBState[group]["xcoord"]
			local y = FBState[members[index]]["ycoord"] - FBState[group]["ycoord"]
			frame:ClearAllPoints()
			frame:SetPoint("BOTTOMRIGHT", "FlexFrame" .. FBState[members[index]]["group"], "BOTTOMRIGHT", x, y)
			FBState[members[index]]["locked"] = true
		else
			-- Group Anchor Button
			local x = FBState[members[index]]["xcoord"]
			local y = FBState[members[index]]["ycoord"]
			frame:ClearAllPoints()
			frame:SetPoint("BOTTOMRIGHT", "UIParent", "BOTTOMLEFT", x, y)
		end
		FB_Apply_SingleSetting(members[index])
	end

	-- Generate group data for use InGroupBounds()
	FBGroupData[group] = FB_GetBoundingButtons(group)
end

function FB_UpdateButtonLoc(buttonnum)
	-- The button frame has been moved (MoveABS, MoveREL), update co-ordinates
	if(FB_DebugFlag == 1) then
		FB_ReportToUser("<< FB_UpdateButtonLoc: Button #"..buttonnum.." Update >>"); 
	end
			
	-- Store button
	FB_GetCoords(buttonnum)
end

function FB_RaiseEvent(event, source)
-- raise no events until a profile is loaded.
	FBLastEvent = event
	FBLastSource = source

	if not FBProfileLoaded then return end

-- Respond to events raised throughout the code.
-- Source is the originator of the event
	if (FB_DebugFlag == 3) then
		util:Print(event)
		util:Print(source)
	end

	event = string.lower(event)
-- If event is being watched for, process the commands associated with it.
	if FBQuickEventDispatch[event] then 
		local qd = FBQuickEventDispatch[event]
		if type(source) == "string" then source = string.lower(source) end
		local index, cur_event
		for index, cur_event in ipairs(qd) do
			if 	not source or not cur_event["target"] or
				FB_InTable(source, cur_event["target"]) then
				FBEventArgs = cur_event["args"]
				local dispatch = FBcmd.CommandList[cur_event["command"]]
				dispatch("")
				FBEventArgs = nil
			end
		end
	end
-- Dispatch to extended handlers

	local handlers = FBExtHandlers[string.upper(event)]
	if handlers then
		local index, dispatch
		for index, dispatch in pairs(handlers) do
			dispatch(event, source)
		end
	end

end

-- Group utility functions
function FB_CheckGroups()
-- Check for mouse entering/leaving groups
	local group, value, list
	list = FBEventToggleInfo["boundcheck"][FBEventToggles["boundcheck"].."list"]
	if not list then return end	
	local x,y = GetCursorPosition()
	x = x / UIParent:GetScale()
	y = y / UIParent:GetScale()
	if abs(x-FBCursorLoc.x) < 1 and abs(y-FBCursorLoc.y) < 1 then return end
	for group, value in pairs(list) do
		if 	FB_InGroupBounds(group, x, y) and
			not FB_InGroupBounds(group, FBCursorLoc.x, FBCursorLoc.y) then
			FB_RaiseEvent("MouseEnterGroup", group)
		end
		if 	not FB_InGroupBounds(group, x, y) and
			FB_InGroupBounds(group, FBCursorLoc.x, FBCursorLoc.y) then
			FB_RaiseEvent("MouseLeaveGroup", group)
		end
	end
	FBCursorLoc.x = x
	FBCursorLoc.y = y
end

function FB_InGroupBounds(group, x, y)
-- Check to see if x,y is inside group
	local button, frame = FB_GetWidgets(group)
	local dim = 36 * button:GetScale()
	if 	x < FBState[FBGroupData[group]["left"]]["xcoord"] - 1 or
		x > FBState[FBGroupData[group]["right"]]["xcoord"] + dim or
		y < FBState[FBGroupData[group]["bottom"]]["ycoord"] - dim or
		y > FBState[FBGroupData[group]["top"]]["ycoord"] + 1 then
		return false
	else
		return true
	end
end

function FB_GetBoundingButtons(group)
-- Return a table with the bounding buttons of a group, to
-- speed up in bounds checks.
	local	members = FB_GetGroupMembers(group)
	local 	index
	local 	bounds = {}
	local 	button1=members[1]
	bounds["top"] = button1
	bounds["bottom"] = button1
	bounds["left"] = button1
	bounds["right"] = button1

	for index = 1, members.n do
		if FBState[members[index]]["xcoord"] > FBState[bounds["right"]]["xcoord"] then
			bounds["right"] = members[index]
		end
		if FBState[members[index]]["xcoord"] < FBState[bounds["left"]]["xcoord"] then
			bounds["left"] = members[index]
		end
		if FBState[members[index]]["ycoord"] > FBState[bounds["top"]]["ycoord"] then
			bounds["top"] = members[index]
		end
		if FBState[members[index]]["ycoord"] < FBState[bounds["bottom"]]["ycoord"] then
			bounds["bottom"] = members[index]
		end
	end
	return bounds
end

function FB_GetGroupMembers(group)
-- returns a table with all the members in <group>
	local 	members = {}
	members.n = 0
	local 	index
	for index = 1, FBNumButtons do
		if FBState[index]["group"] == group then
			members.n = members.n + 1
			members[members.n] = index
		end
	end
	return members
end

function FB_VerticalGroup(group, width, padding)
-- Arranges buttons in group vertically, width columns wide with
-- the specified padding between them.
	local members = FB_GetGroupMembers(group)
	if members.n < 1 then return end
	local dim = padding + (38 * (FBState[members[1]]["scale"] or 1))
	local index = 1
	while index <= members.n do
		local index2
		for index2 = 1, width do
			if index <= members.n then
				FB_MoveButtonABS(members[index], 300 + ((index2-1)*dim), 500-(floor((index-1)/width)*dim))
			end
			index = index+1
		end
	end
	FB_ReformGroup(group)
end
			
function FB_HorizontalGroup(group, height, padding)
-- Arranges buttons in group horizontally, height rows high with
-- the specified padding between them.
	local members = FB_GetGroupMembers(group)
	if members.n < 1 then return end
	local dim = padding + (38 * (FBState[members[1]]["scale"] or 1))
	local index = 1
	while index <= members.n do
		local index2
		for index2 = 1, height do
			if index <= members.n then
				FB_MoveButtonABS(members[index], 300+(floor((index-1)/height)*dim), 500-((index2-1)*dim))
			end
			index = index+1
		end
	end
	FB_ReformGroup(group)
end
			
function FB_CircularGroup(group, padding)
-- Arranges group buttons in a circle with the specified padding between them.
-- Starts at 10 O'clock and moves clockwise, with the 7th in the center.   
--       0
--     0   0
--       0 
--     0   0
--       0
-- Max buttons for this is 7
	local coords = {{-1,.5},{0,1},{1,.5},{1,-.5},{0,-1},{-1,-.5},{0,0}}
	local members = FB_GetGroupMembers(group)
	if members.n < 1 or members.n > 7 then return end
	local dim = padding + (38 * (FBState[members[1]]["scale"] or 1))
	for index = 1, members.n do
		FB_MoveButtonABS(members[index], 300+(coords[index][1]*dim), 300+(coords[index][2]*dim))
	end

	FB_ReformGroup(group)
end
			
	
-- Button utility functions
	
function FB_GetWidgets(buttonnum)
-- returns the button and frame (handle) associated with buttonnum
	local	button	= getglobal("FlexBarButton" .. buttonnum)
	local	frame	= getglobal("FlexFrame" .. buttonnum)
	return button, frame
end

function FB_GetButtonNum(frame)
	-- takes either a button or handle (frame) and returns the button number
	local firsti, lasti, num = string.find(frame:GetName(), "(%d+)")
	return num+0
end

function FB_GetCoords(buttonnum)
	local button, frame = FB_GetWidgets(buttonnum)
-- Save the coordinates of the handle - On programmatically moved buttons,
-- WoW won't do it automatically for us across sessions.
	local x = frame:GetRight()
	local y = frame:GetBottom()
	local oldX = FBState[buttonnum]["xcoord"]
	local oldY = FBState[buttonnum]["ycoord"]
	FBState[buttonnum]["xcoord"] = x
	FBState[buttonnum]["ycoord"] = y
	FBSavedProfile[FBProfileName][buttonnum].State["xcoord"] = x
	FBSavedProfile[FBProfileName][buttonnum].State["ycoord"] = y

	FB_UpdateSmartPet(buttonnum)
	
	if(FB_DebugFlag == 2) then
		if ( oldX and oldY ) then
		    if (oldX ~= x) or (oldY ~= y) then
				FB_ReportToUser("<< FB_GetCoords: Moved Button #"..buttonnum.." from ("..oldX..", "..oldY..") to ("..x..","..y..") >>"); 
			else
				FB_ReportToUser("<< FB_GetCoords: ReSet Button #"..buttonnum.." to ("..x..","..y..") >>"); 
			end
		else
			FB_ReportToUser("<< FB_GetCoords: Set Button #"..buttonnum.." to ("..x..","..y..") >>"); 
		end
	end
	FBSavedProfile[FBProfileName][buttonnum].State["ycoord"] = frame:GetBottom()
end

function FB_SaveState(buttonnum)
	local button, frame = FB_GetWidgets(buttonnum)
-- Save the current button state to the current character's profile.
	if FBSavedProfile[FBProfileName] == nil then
		FBSavedProfile[FBProfileName] = {}
	end
	FBSavedProfile[FBProfileName][buttonnum].State = util:TableCopy(FBState[buttonnum])
end

function FB_LoadState(buttonnum)
	local button, frame = FB_GetWidgets(buttonnum)
	
	-- load the current button state from the current character's profile.
	if FBSavedProfile[FBProfileName] == nil then
		return
	end
	if not FBSavedProfile[FBProfileName][buttonnum] then
		FBSavedProfile[FBProfileName][buttonnum] = {}
		FBSavedProfile[FBProfileName][buttonnum].State = {}
		FBSavedProfile[FBProfileName][buttonnum].State["hidden"] = "true"
	end
	FBState[buttonnum] = util:TableCopy(FBSavedProfile[FBProfileName][buttonnum].State)
	FBState[buttonnum]["oldid"] = button:GetID()
	FBIDtoButtonNum[button:GetID()] = buttonnum
	
	-- Move button to its saved location
	if FBState[buttonnum]["xcoord"] and FBState[buttonnum]["ycoord"] then
		local x = FBState[buttonnum]["xcoord"]
		local y = FBState[buttonnum]["ycoord"]

		if(FB_DebugFlag == 1) then
			FB_ReportToUser("<< FB_LoadState: B-"..buttonnum.." to ("..x..","..y..") >>"); 
		end
		
		frame:ClearAllPoints()
		frame:SetPoint("BOTTOMRIGHT", "UIParent", "BOTTOMLEFT", x, y)
	end
end

function FB_MoveButtonABS(button1, x, y)
-- Moves the button and handle so the top left of the button and bottom right of the
-- frame are x pixels to the right of the bottom right corner and y pixels above it.
-- will work with either a button or handle passed to it.
	local button, frame = FB_GetWidgets(button1)
	frame:ClearAllPoints()
	frame:SetPoint("BOTTOMRIGHT", "UIParent", "BOTTOMLEFT", x, y)

	FB_UpdateButtonLoc(button1)
end

function FB_MoveButtonRel(button1, button2, dx, dy)
-- Moves button1's top left point (bottom right of handle) x pixels to the left of
-- and y pixels above button2's top left point (bottom right of it's handle)
-- Leaves button anchored on UIParent.
	local x = FBState[button2]["xcoord"] + dx
	local y = FBState[button2]["ycoord"] + dy
	local button, frame = FB_GetWidgets(button1)
	frame:ClearAllPoints()
	frame:SetPoint("BOTTOMRIGHT", "UIParent", "BOTTOMLEFT", x, y)
	
	FB_UpdateButtonLoc(button1)
end

function FB_LockButton(buttonnum)
	local button, frame = FB_GetWidgets(buttonnum)
-- Lock the button by shrinking the handle underneath the
-- corner of the button.
	if frame:GetHeight() == 1 then return end -- is already locked
	-- If it's user placed, the relative points change.  Reset it so we know that
	-- it is set with it's bottom-right corner relative to the UIParent's bottom left.
	if frame:IsUserPlaced() then
		local x = frame:GetRight()
		local y = frame:GetBottom()
		frame:ClearAllPoints()
		frame:SetPoint("BOTTOMRIGHT", "UIParent", "BOTTOMLEFT", x, y)
	end
	frame:SetHeight(1)
	frame:SetWidth(1)
end

function FB_UnlockButton(buttonnum)
	local button, frame = FB_GetWidgets(buttonnum)
-- unlock button by enlarging the handle to normal size.
	if frame:GetHeight() == 10 then return end -- is already unlocked
	-- If it's user placed, the relative points change.  Reset it so we know that
	-- it is set with it's bottom-right corner relative to the UIParent's bottom left.
	-- While I can't see how a button could be user placed if it's already locked,
	-- better safe than sorry.
	if frame:IsUserPlaced() then
		local x = frame:GetRight()
		local y = frame:GetBottom()
		frame:ClearAllPoints()
		frame:SetPoint("BOTTOMRIGHT", "UIParent", "BOTTOMLEFT", x, y)
	end
	frame:SetHeight(10)
	frame:SetWidth(10)
end

-- Functions called by timers to check status of a variety of things and raise the appropriate events
function FB_ItemEnchantEvents()
-- check for item enchantments
	local 	hasMainHandEnchant, mainHandExpiration, mainHandCharges,
			hasOffHandEnchant, offHandExpiration, offHandCharges = GetWeaponEnchantInfo() 

	if hasMainHandEnchant and not FBItemEnchants["mainhand"] then
		FB_MoneyToggle();	
    FlexBarTooltip:SetOwner(UIParent, "ANCHOR_NONE");
		FlexBarTooltip:SetInventoryItem("player",16)
		local i = 6
		local text = getglobal("FlexBarTooltipTextLeft"..i)
		while text and text:IsVisible() and text:GetText() ~= nil do
			if text and text:GetText() and strfind(text:GetText(),"Durability") then break end
			i = i+1
			text = getglobal("FlexBarTooltipTextLeft"..i)
		end
		local i = i - 1
		local text = getglobal("FlexBarTooltipTextLeft"..i)
		local name = text:GetText()
		name = string.gsub(name," %(%d+ %a+%)","")
		FBItemEnchants["mainhand"] = string.lower(name)
		FBItemEnchants["mainhandcharges"] = mainHandCharges
		FB_RaiseEvent("GainItemBuff",name)
		FBBuffs["itembuffs"]["'"..name.."'"] = true
		FB_MoneyToggle();	
	end
	if not hasMainHandEnchant and FBItemEnchants["mainhand"] then
		FB_RaiseEvent("LoseItemBuff",FBItemEnchants["mainhand"])
		FBItemEnchants["mainhand"] = nil
		FBItemEnchants["mainhandcharges"] = nil
		FBItemEnchants["mainhandtimeleft"] = nil
	end
	if hasoffHandEnchant and not FBItemEnchants["offhand"] then
		FB_MoneyToggle();
		FlexBarTooltip:SetOwner(UIParent, "ANCHOR_NONE");
		FlexBarTooltip:SetInventoryItem("player",17)
		local i = 6
		local text = getglobal("FlexBarTooltipTextLeft"..i)
		while text and text:IsVisible() and text:GetText() ~= nil do
			if text and text:GetText() and strfind(text:GetText(),"Durability") then break end
			i = i+1
			text = getglobal("FlexBarTooltipTextLeft"..i)
		end
		local i = i - 1
		local text = getglobal("FlexBarTooltipTextLeft"..i)
		local name = text:GetText()
		name = string.gsub(name," %(%d+ %a+%)","")
		FBItemEnchants["offhand"] = string.lower(name)
		FBItemEnchants["offhandcharges"] = offHandCharges
		FB_RaiseEvent("GainItemBuff",name)
		FBBuffs["itembuffs"]["'"..name.."'"] = true
		FB_MoneyToggle();	
	end
	if not hasoffHandEnchant and FBItemEnchants["offhand"] then
		FB_RaiseEvent("LoseItemBuff",FBItemEnchants["offhand"])
		FBItemEnchants["offhand"] = nil
		FBItemEnchants["offhandcharges"] = nil
		FBItemEnchants["offhandtimeleft"] = nil
	end
	if mainHandCharges ~= FBItemEnchants["mainhandcharges"] then
		FB_RaiseEvent("MainHandCharges",mainHandCharges)
	end
	if offHandCharges ~= FBItemEnchants["offhandcharges"] then
		FB_RaiseEvent("OffHandCharges",offHandCharges)
	end
	FBItemEnchants["mainhandcharges"] = mainHandCharges
	FBItemEnchants["offhandcharges"] = offHandCharges
	if mainHandExpiration then
		FBItemEnchants["mainhandtimeleft"] = mainHandExpiration - GetTime()
	else
		FBItemEnchants["mainhandtimeleft"] = 0
	end
	if offHandExpiration then
		FBItemEnchants["offhandtimeleft"] = offHandExpiration - GetTime()
	else
		FBItemEnchants["offhandtimeleft"] = 0
	end
end

function FB_DeathEvents()
-- check for unit being dead or a ghost
	local index, value, list
	list = FBEventToggleInfo["deathcheck"][FBEventToggles["deathcheck"] .."list"]
	if not list then return end
	for index, value in pairs(list) do
		if UnitExists(index) then
			if FBUnitStatus[index] ~= "dead" and UnitIsDeadOrGhost(index) then
				FB_RaiseEvent("UnitDied",index)
				FBUnitStatus[index] = "dead"
				if index == "player" then
					FBSavedProfile[FBProfileName].Buffs = {}
				end
			end
			if FBUnitStatus[index] == "dead" and not UnitIsDeadOrGhost(index) then
				FB_RaiseEvent("UnitRessed",index)
				FBUnitStatus[index] = "alive"
			end
		end
	end
end

function FB_CombatEvents()
-- check for unit "affecting combat"  New to 1300
	local index, value, list
	list = FBEventToggleInfo["affectcombat"][FBEventToggles["affectcombat"] .."list"]
	if not list then return end
	for index, value in pairs(list) do
		if UnitExists(index) then
			if FBUnitCombat[index] and not UnitAffectingCombat(index) then
				FB_RaiseEvent("EndCombat",index)
				FBUnitCombat[index] = nil
			end
			if not FBUnitCombat[index] and UnitAffectingCombat(index) then
				FB_RaiseEvent("StartCombat",index)
				FBUnitCombat[index] = true
			end
		end
	end
end

function FB_CheckPets(pet)
	if UnitExists(pet) then -- gained a pet
		if 	FBEventToggles["petcheck"] == "high" or FBEventToggleInfo["petcheck"]["lowlist"][pet] then
			FB_RaiseEvent("PetSummoned",pet)
		end
		if pet == "pet" then  -- legacy gainpet for player only - incudes type for warlocks
			FB_RaiseEvent("GainPet",UnitCreatureFamily("pet"))
			table.insert(FBPetTypes,UnitCreatureFamily("pet"))
		end
	else	
		if 	FBEventToggles["petcheck"] == "high" or FBEventToggleInfo["petcheck"]["lowlist"][pet] then
			FB_RaiseEvent("PetDismissed",pet)
		end
		if pet == "pet" then -- legacy losepet for player only
			FB_RaiseEvent("LosePet")
		end
	end
end

function FB_PetEvents()
-- Called every .5 seconds to check for a change in pet status
	local petname = UnitName("pet")
	if petname and not FBPetname then
		FB_RaiseEvent("GainPet","petname")
	end
	if not petname and FBPetname then
		FB_RaiseEvent("LosePet")
	end
	
	FBPetname = petname
end

local allpartyunits = {"player","pet"}
local index
for index = 1,4 do
	table.insert(allpartyunits,"party"..index)
	table.insert(allpartyunits,"partypet"..index)
end

local allraidunits = {}
for index = 1,40 do
	table.insert(allraidunits,"raid"..index)
	table.insert(allraidunits,"raidpet"..index)
end

function FB_TargetTargetEvents()
-- Called every .1 seconds to check for a target of your target and whether it's one of:
-- player, pet, party1-party4, partypet1-partypet4 or the name of the unit.
	-- Timer will only run while we have a target, but check for existence of targettarget
	if not UnitExists("targettarget") then
		if FBLastTargetTarget then
			FB_RaiseEvent("targetlosttarget")
			FBLastTargetTarget = nil
			FBLastTargetTargetName = nil
		end
		return 
	end
	
	local unit
	if UnitInParty("targettarget") then
		-- unit in party
		for i,v in ipairs(allpartyunits) do
			if UnitIsUnit("targettarget",v) then
				unit = v
				break
			end
		end
	end
	
	-- otherwise get unit type (hostile/neutral/friendly pc/npc)
	if not unit then
		local pctype
		if UnitIsPlayer("targettarget") then
			pctype = "pc"
		else
			pctype = "npc"
		end
		if UnitIsEnemy("targettarget","player") then
			unit = "hostile" .. pctype
		elseif UnitIsFriend("targettarget","player") then
			unit = "friendly" .. pctype
		else
			unit = "neutral" .. pctype
		end
	end
	
	-- if it's a friendly pc and the event toggle is set to high check for it being a raid member.
	if unit == "friendlypc" and FBEventToggles["targetcheck"] == "high" then
		for i,v in ipairs(allraidunits) do
			if UnitIsUnit("targettarget",v) then
				unit = v
				break
			end
		end
	end
	
	if FBLastTargetTarget ~= unit then
		if not FBLastTargetTarget then
			FB_RaiseEvent("targetgaintarget",unit)
		elseif FBLastTargetTarget ~= unit or FBLastTargetTargetName ~= UnitName("targettarget") then
			FB_RaiseEvent("targetchangedtarget",unit)
		end
	end
	
	FBLastTargetTarget = unit
	FBLastTargetTargetName = UnitName("targettarget")
end

function FB_UsableEvents()
-- usable events.
	local index, value, list
	list = FBEventToggleInfo["usablecheck"][FBEventToggles["usablecheck"].."list"]
	if not list then return end
	for index, value in pairs(list) do
		local button, frame = FB_GetWidgets(index)
		if HasAction(button:GetID()) then
			local isUsable, notEnoughMana = IsUsableAction(button:GetID());
			if FBState[index]["isUsable"] and not isUsable then
				FB_RaiseEvent("NotUsable", FB_GetButtonNum(button))
			end
			if not FBState[index]["isUsable"] and isUsable then
				FB_RaiseEvent("IsUsable", FB_GetButtonNum(button))
			end
			FBState[index]["isUsable"] = isUsable
		end
	end
end

function FB_ManaEvents()
-- mana events.
	local index, value, list
	list = FBEventToggleInfo["manacheck"][FBEventToggles["manacheck"].."list"]
	if not list then return end
	for index, value in pairs(list) do
		local button, frame = FB_GetWidgets(index)
		if HasAction(button:GetID()) then
			local isUsable, notEnoughMana = IsUsableAction(button:GetID());
			if FBState[index]["notEnoughMana"] and not notEnoughMana then
				FB_RaiseEvent("EnoughMana", FB_GetButtonNum(button))
			end
			if not FBState[index]["notEnoughMana"] and notEnoughMana then
				FB_RaiseEvent("NotEnoughMana", FB_GetButtonNum(button))
			end

			FBState[index]["notEnoughMana"]=notEnoughMana
		end
	end
end

function FB_RangeEvents()
-- Range events.
	local index, value, list
	list = FBEventToggleInfo["rangecheck"][FBEventToggles["rangecheck"].."list"]
	if not list then return end
	for index, value in pairs(list) do
		local button, frame = FB_GetWidgets(index)
		if HasAction(button:GetID()) then
			local inRange = IsActionInRange(button:GetID());
			if FBState[index]["inRange"] ~= 0 and inRange == 0 then
				FB_RaiseEvent("OutOfRange", index)
			end
			if FBState[index]["inRange"] == 0 and inRange ~= 0 then
				FB_RaiseEvent("NowInRange", index)
			end
			FBState[index]["inRange"] = inRange
		end
	end
end

function FB_CooldownEvents()
-- raise cooldown events	
	local index, value, list
	list = FBEventToggleInfo["cooldowncheck"][FBEventToggles["cooldowncheck"].."list"]
	if not list then return end
	for index, value in pairs(list) do
		local button, frame = FB_GetWidgets(index)
		if HasAction(button:GetID()) then
			local start, duration, enable = GetActionCooldown(button:GetID())
			if FBState[index]["start"] == nil then FBState["start"] = 0 end
			if start == 0 and FBState[index]["start"] ~= 0 then
				FB_RaiseEvent("CoolDownMet", index)
			end
			if start ~= 0 and FBState[index]["start"] == 0 then
				FB_RaiseEvent("CoolDownStart", index)
			end
			FBState[index]["start"] = start
		end
	end
end	

function FB_KeyEvents()
-- raise modifier key down/up events	
	if IsShiftKeyDown() and not FBKeyStates["shift"] then
		FB_RaiseEvent("ShiftKeyDown")
	end
	if not IsShiftKeyDown() and FBKeyStates["shift"] then
		FB_RaiseEvent("ShiftKeyUp")
	end
	FBKeyStates["shift"] = IsShiftKeyDown()
	if IsAltKeyDown() and not FBKeyStates["alt"] then
		FB_RaiseEvent("AltKeyDown")
	end
	if not IsAltKeyDown() and FBKeyStates["alt"] then
		FB_RaiseEvent("AltKeyUp")
	end
	FBKeyStates["alt"] = IsAltKeyDown()
	if IsControlKeyDown() and not FBKeyStates["control"] then
		FB_RaiseEvent("ControlKeyDown")
	end
	if not IsControlKeyDown() and FBKeyStates["control"] then
		FB_RaiseEvent("ControlKeyUp")
	end
	FBKeyStates["control"] = IsControlKeyDown()

end	

-- General Utility functions

function FB_RegisterEvent(event, name, callback, wowevent)
-- Register a handler for an event
-- all names will be upper case
-- WoW events and FB events are in the same table
	if wowevent then
		FlexBar:RegisterEvent(event)
	end
	if not FBExtHandlers[string.upper(event)] then
		FBExtHandlers[string.upper(event)] = {}
	end

	FBExtHandlers[string.upper(event)][string.lower(name)] = callback
end

function FB_UnregisterEvent(event, name)
-- Un load a handler
	if not FBExtHandlers[string.upper(event)] then return end
	FBExtHandlers[string.upper(event)][string.lower(name)] = nil
end

function FB_Report(msg)
-- little helper function to display feedback of actions to the user.
	if not FBToggles["verbose"] then return end
	util:Print("FB> " .. msg)
end

function FB_ReportToUser(msg)
-- little helper function to display feedback of actions to the user - reguardless of configuration.
	util:Print(msg)
end

function FB_LoadProfile()

	if(FB_DebugFlag == 1) then
		FB_ReportToUser("<< FB_LoadProfile Entry >>"); 
	end

	-- Load the current character's profile back into the buttons.
	if 	(UnitName("player") == nil) or
		(string.lower(UnitName("player")) == "unknown entity") or 
		UnitName("player") == UNKNOWNBEING or
		UnitName("player") == UNKNOWNOBJECT then
		FBTimers["loadprofile"] = Timer_Class:New(1, nil, 
			".... loading", nil, FB_LoadProfile)
		return
	end

	FBCharName = UnitName("player")
	FBProfileName = FBCharName .. " of " .. GetCVar("RealmName")

	local index
	if FBSavedProfile[FBProfileName] == nil then
		if FBSavedProfile[FBCharName] ~= nil then
			util:Print("Updating to per-server/character saved profiles")
			util:Print("Copying profile for " .. FBCharName .. " to " .. FBProfileName)
			FBSavedProfile[FBProfileName] = util:TableCopy(FBSavedProfile[FBCharName])
		else
			util:Print("No profile for " .. FBProfileName .." found, creating a new one")
			FBSavedProfile[FBProfileName] = {}
			for index = 1, FBNumButtons do
				FBSavedProfile[FBProfileName][index] = {}
				FBSavedProfile[FBProfileName][index].State = {}
				FBSavedProfile[FBProfileName][index].State["hidden"] = true
			end
			FBSavedProfile[FBProfileName].Events = {}
			FBSavedProfile[FBProfileName].Events.n = 0
		end
	end
	
	for index = 1, FBNumButtons do
		FB_LoadState(index)
	end
	
	if FBSavedProfile[FBProfileName].Buffs == nil then
		FBSavedProfile[FBProfileName].Buffs = {}
	end
	
	if FBSavedProfile[FBProfileName].EventToggles == nil then
		FBSavedProfile[FBProfileName].EventToggles = {}
		local index,value
		for index,value in pairs(FBEventToggleInfo) do
			FBSavedProfile[FBProfileName].EventToggles[index] = value["toggle"]
		end
	else
		local index,value
		for index,value in pairs(FBEventToggleInfo) do
			if FBSavedProfile[FBProfileName].EventToggles[index] == nil then
				FBSavedProfile[FBProfileName].EventToggles[index] = value["toggle"]
			end
		end
	end
	
	if FBSavedProfile[FBProfileName].FlexActions == nil then
		FBSavedProfile[FBProfileName].FlexActions = {}
	end
	
	
	FBEventToggles = FBSavedProfile[FBProfileName].EventToggles
	FBEventToggleInfo["boundcheck"]["highlist"] = FBGroupData
	FBProfileLoaded=true;
	FBEvents = util:TableCopy(FBSavedProfile[FBProfileName].Events)
	FB_CreateQuickDispatch()
	
	-- Add ReformAllGroups call to properly setup all groups and buttons.
	FB_ReformAllGroups()
	
	-- Using FlexBar's scale to see if the buttons need rescaling
	FlexBar:SetScale(.25)
	
	util:Echo("Flex Bar settings for " .. FBProfileName .. " loaded")
	
	-- Start Resync timer - just used to check for an overall UI scale change. If scale has gotten changed for some
	-- reason, it will reload the profile.
	FBTimers["resync"] = Timer_Class:New(5, true, nil, nil, FB_ResyncSettings)
	
	-- set a recurring timers to check for the variety of events that don't necessarily have real
	-- WoW events (some of these do, but was just easier to copy/paste the timer code.  A liittle
	-- sloppy, but the extra overhead for group checking is minimal, and for form checking not only
	-- minimal but well worth it to get fades that normally don't get reported in UNIT_LOSE_AURA)
	FBTimers["boundcheck"] = 	Timer_Class:New(.1,  true, nil, nil, FB_CheckGroups)
	FBTimers["cooldowncheck"] =	Timer_Class:New(1,   true, nil, nil, FB_CooldownEvents)
	FBTimers["manacheck"] = 	Timer_Class:New(1,   true, nil, nil, FB_ManaEvents)
	FBTimers["usablecheck"] = 	Timer_Class:New(.5,  true, nil, nil, FB_UsableEvents)
	FBTimers["rangecheck"] = 	Timer_Class:New(.25, true, nil, nil, FB_RangeEvents)
	FBTimers["targettarget"] = 	Timer_Class:New(.1,  true, nil, nil, FB_TargetTargetEvents)
	FBTimers["deathcheck"] = 	Timer_Class:New(.5,  true, nil, nil, FB_DeathEvents)
	FBTimers["keycheck"] = 		Timer_Class:New(.1,  true, nil, nil, FB_KeyEvents)
	FBTimers["affectcombat"] =	Timer_Class:New(.25, true, nil, nil, FB_CombatEvents)
	FBTimers["itembuffs"] 	=	Timer_Class:New(.33, true, nil, nil, FB_ItemEnchantEvents)
	-- FB_GetSpellInfo()
	FB_RestoreScripts()
	FB_CheckAutoItems()
	FB_Set_PerformanceOptions()
	FB_RaiseEvent("ProfileLoaded",FBProfileName)
	-- pre-load buffs
	FB_CheckAllBuffs("player")
	FB_GeneratePetIDList()
	FB_MimicPetButtons()
	FB_SetTheorycraftData(true);
	for index = 1, FBNumButtons do
		--set the button's theorycraft text and itemcount text
		FlexBarButton_UpdateItemCount(index);
		-- Get each spell's name to set decent keybinding names
		FB_SetKeyBindingName(index);
	end
end

function FB_RestoreScripts()
-- function to rebuild scripts > 1024 characters long
	local name, script, index, value
	for name, script in pairs(FBTextChunks) do
		FBScripts[name] = ""
		for index,value in ipairs(script) do
			FBScripts[name] = FBScripts[name] .. value
		end
	end
end

function FB_GetButtonList(list)
-- regularize arguments to functions that take button lists
-- returns a list of buttons to be affected as a list, with the
-- number of buttons in list.n
	local 	return_value = {}
	return_value.n = 0
	if list == nil then
		return return_value		
	elseif type(list) == "number" then
		return_value[1]=list
		return_value.n = 1
		return return_value
	elseif type(list) == "table" then
		local index
		list.n = 0
		for index in ipairs(list) do
			list.n = list.n+1
		end
		return_value=list
		return return_value
	else
		util:Print("Nil passed to FB_GetButtonList - report this")
	end
end

function FB_DisplayScripts()
	local scripts = {}
	local ind, value
	for ind, value in pairs(FBScripts) do
		table.insert(scripts, ind)
	end
	table.sort(scripts, function(v1,v2) return string.lower(v1) < string.lower(v2) end)

	local count, index, val
	local scriptnames = {}
	count=0
	for index,val in pairs(scripts) do
		count=count+1
		scriptnames[count]=val
	end
	
	if FBScriptCount > count - 16 then FBScriptCount = count - 14 end
	if FBScriptCount < 1 then FBScriptCount = 1 end
	
	for index = 0,15 do
		local button=getglobal("FBScriptsMenu"..index)
		if scriptnames[index+FBScriptCount] then
			button:SetText(scriptnames[index+FBScriptCount])
		else
			button:SetText("")
		end
	end
end

function FB_CastSpellByName(spellname)
-- function to replace CastSpellByName
	CastSpellByName(spellname)
	return
	
	--[[
	if FBPlayerSpells[spellname] then
		CastSpell(FBPlayerSpells[spellname],1)
	elseif FBPlayerMaxRank[spellname] then
		CastSpell(FBPlayerMaxRank[spellname],1)
	elseif FBPetSpells[spellname] then
		CastSpell(FBPetSpells[spellname],BOOKTYPE_PET)
	elseif FBPetMaxRank[spellname] then
		CastSpell(FBPetMaxRank[spellname],BOOKTYPE_PET)
	else
		util:Echo("ERROR: Attempt to cast non-existent spell","red")
	end ]]
end

function FBCastcmd:Dispatch(msg)
-- implement my castspellbyname as a slash command for macros
	CastSpellByName(msg)
end

function FBDoIncmd:Dispatch(msg)
-- implement my in as a slash command
	local firsti, lasti, seconds = string.find(msg,"^(%d+)%s+")
	if not firsti then return end
	FBcmd:Dispatch("runmacro macro='"..string.sub(msg,lasti+1).."' in=".. seconds)
end

function FBUsecmd:Dispatch(msg)
-- implement my usebyname as a slash command for macros
	FB_UseByName(msg)
end

function FBEchocmd:Dispatch(msg)
-- implement echo as a slash command for macros
	local _,_,capture = string.find(msg,"#(%w+)")
	local color = "white"
	if capture and Utility_Class.ColorList[string.lower(capture)] then
		msg = string.gsub(msg,"#"..capture.."%s*","")
		color = string.lower(capture)
	end
	local variable
	for variable in string.gfind(msg,"(%%%w+)") do
		if FBTextSubstitutions[string.lower(variable)] then
			msg = string.gsub(msg, "%"..variable, FBTextSubstitutions[string.lower(variable)]())
		end
	end
	for variable in string.gfind(msg,"$([%w%p]+)$") do
		RunScript("FBMacroValue="..variable)
		variable = string.gsub(variable,"([%(%)%.%%%+%-%*%?%[%]%^%$])","%%%1")
		msg = string.gsub(msg,"$"..variable.."$",tostring(FBMacroValue))
	end
	util:Echo(msg,color)
end

function FBPrintcmd:Dispatch(msg)
-- implement echo as a slash command for macros
	local _,_,capture = string.find(msg,"#(%w+)")
	local color = "white"
	if capture and Utility_Class.ColorList[string.lower(capture)] then
		msg = string.gsub(msg,"#"..capture.."%s*","")
		color = string.lower(capture)
	end
	local variable
	for variable in string.gfind(msg,"(%%%w+)") do
		if FBTextSubstitutions[string.lower(variable)] then
			msg = string.gsub(msg, "%"..variable, FBTextSubstitutions[string.lower(variable)]())
		end
	end
	for variable in string.gfind(msg,"$([%w%p]+)$") do
		RunScript("FBMacroValue="..variable)
		variable = string.gsub(variable,"([%(%)%.%%%+%-%*%?%[%]%^%$])","%%%1")
		msg = string.gsub(msg,"$"..variable.."$",tostring(FBMacroValue))
	end
	util:Print(msg,color)
end

function FB_GetSpellInfo()
-- Load tables up with spell position by name for a CastSpellByName that works in scripts
	FBPlayerSpells = {}
	FBPlayerMaxRank = {}
	FBPetSpells = {}
	FBPetMaxRank = {}
	local index = 1

	while true do
		local spellname, spellrank = GetSpellName(index,1)
		if not spellname then break end
		if not spellrank then spellrank = "" end
		if not IsSpellPassive(index,1) then
			FBPlayerSpells[spellname.."("..spellrank..")"] = index
			FBPlayerMaxRank[spellname] = index
		end
		index = index+1
	end

	if not HasPetSpells() then return end
	
	local index = 1

	while true do
		local spellname, spellrank = GetSpellName(index,BOOKTYPE_PET)
		if not spellname then break end
		if not spellrank then spellrank = "" end
		FBPetSpells[spellname.."("..spellrank..")"] = index
		FBPetMaxRank[spellname] = index
		index = index+1
	end
end

function FB_InTable(value1, table1)
-- Simple utility function to check for the existence of
-- value1 in table1
	local index, value
	for index,value in pairs(table1) do
		if type(value) == "string" then value=string.lower(value) end
		if value == value1 then
			return true;
		end
	end
	return false
end

function FB_Execute_Command(command)
-- Simple function to execute a string as though it were typed in the chat box
	local variable
	for variable in string.gfind(command,"(%%%w+)") do
		if FBTextSubstitutions[string.lower(variable)] then
			command = string.gsub(command, "%"..variable, FBTextSubstitutions[string.lower(variable)]())
		end
	end
	for variable in string.gfind(command,"%$([%w%p]+)%$") do
		RunScript("FBMacroValue="..variable)
		variable = string.gsub(variable,"([%(%)%.%%%+%-%*%?%[%]%^%$])","%%%1")
		command = string.gsub(command,"%$"..variable.."%$",tostring(FBMacroValue))
	end
	FlexBar_Command_EditBox:SetText(command)
	local editbox = FlexBar_Command_EditBox
	ChatEdit_SendText(editbox)
end

function FB_Execute_ConfigString(config)
-- Execute config contained in a string
	local capture
	FBTestConfig = {}
	config = "\n" .. config .. "\n"
	for capture in string.gfind(config,"([^%\n]+)") do
		if capture ~= "" and not string.find(capture,"%s*%-%-") then 
			table.insert(FBTestConfig, capture) 
		end
	end
	FBcmd:Dispatch("loadconfig config='FBTestConfig'")
end

function FB_Execute_MultilineMacro(macro, macroname)
-- Execute multi-line macro contained in a string
-- Clear FBEventArgs:  When doing a RunMacro off an event, this was causing /FlexBar Commands to not execute
	FBEventArgs = nil
-- Macro is waiting, and someone tried to re-run it.
	if FBMacroWait[macroname] and FBMacroWait[macroname]["time"] > GetTime() then return end
	local skip = 0
	local count = 1
	local capture
	config = "\n" .. macro .. "\n"
	for capture in string.gfind(config,"([^%\n]+)") do
		if (FBMacroWait[macroname] and (count > FBMacroWait[macroname]["line"])) or (not FBMacroWait[macroname])then
			if string.sub(capture,1,3) == "/if" then
				if not FB_CheckConditional(string.sub(capture,4)) then
					skip = 1
				end
			elseif string.sub(capture,1,5) == "/else" then
				if skip == 1 then skip = 0 else skip = 1 end
			elseif string.sub(capture,1,4) == "/end" then
				skip = 0
			elseif string.sub(capture,1,6) == "/break" then
				FBMacroWait[macroname] = nil
				return
			elseif skip == 0 then
				if capture ~= "" then
					local _,_,delay = string.find(string.lower(capture),"/fbwait (%d+)")
					if delay then
						FBMacroWait[macroname] = {}
						FBMacroWait[macroname]["time"] = GetTime() + (delay/10)
						FBMacroWait[macroname]["line"] = count
						FBTimers[macroname .. "wait timer"] = 
							Timer_Class:New((delay/10)+.1,nil,nil,nil,function() FB_Execute_MultilineMacro(macro, macroname) end)
						return
					end
					FB_Execute_Command(capture) 
				end
			end
		end
		count=count+1
	end
	FBMacroWait[macroname] = nil
end

function FB_CheckTextSub()
-- Scan text subs on health, mana, combopoints, scan-inventory or simply 1 time per second for timers
	if (FBButtonInfoShown == true) then return end
	local index
	for index = 1, FBNumButtons do
		FB_TextSub(index)
	end
end

function FB_GetBagLocation(name)
-- find a location with the item named
	local bagnum, slotindex
	if FBBagContents[name] then
		return FBBagContents[name]["bag"],FBBagContents[name]["slot"]
	else
		return nil
	end
end

function FB_UseByName(name)
-- Function to use an item by name 
	local bag, slot = FB_GetBagLocation(name)
	if bag then
		if bag < 5 then
			UseContainerItem(bag,slot)
		else
			UseInventoryItem(slot)
		end
	else
		util:Echo(name .. " not found on your person", "yellow")
	end
end

function FB_PlaceItemOnActionBar(name, id)
-- Function to place an item on the bar by name
	local bag, slot = FB_GetBagLocation(name)
	if bag then
		if bag < 5 then
			PickupContainerItem(bag,slot)
			PlaceAction(id)
		else
			PickupInventoryItem(FB_GetInvLocation(name))
			PlaceAction(id)
		end
	else
		util:Echo(name .. " not found on your person", "yellow")
	end
end

function FB_ScanInventory()
-- creates a table indexed by item name with total count and a location
	local index, value
	for index, value in pairs(FBBagContents) do
		if type(value) == "table" then
			value["count"] = 0
			value["bag"] = nil
			value["slot"] = nil
		end
	end
	local bag, slot
	-- search bags left to right
	for bag = 0,4 do
		if bag == 0 then
			FBBagContents["allbagsnumslots"] = GetContainerNumSlots(bag)
			FBBagContents["backpacknumslots"] = GetContainerNumSlots(bag)
			FBBagContents["allbagsnumslotsleft"] = GetContainerNumSlots(bag)
			FBBagContents["backpacknumslotsleft"] = GetContainerNumSlots(bag)
			FBBagContents["allbagsnumslotsused"] = 0
			FBBagContents["backpacknumslotsused"] = 0
		else
			local name = "bag"..bag
			FBBagContents[name.."numslots"] = GetContainerNumSlots(bag)
			FBBagContents[name.."numslotsleft"] = GetContainerNumSlots(bag)
			FBBagContents[name.."numslotsused"] = 0
			FBBagContents["allbagsnumslots"] = FBBagContents["allbagsnumslots"] + GetContainerNumSlots(bag)
			FBBagContents["allbagsnumslotsleft"] = FBBagContents["allbagsnumslotsleft"] + GetContainerNumSlots(bag)
		end
		for slot = 1,GetContainerNumSlots(bag) do
			local link = GetContainerItemLink(bag, slot)
			local name
			if link then
				_,_,name = string.find(link,"%[([^%[%]]+)%]")
			end
			if name then
				FBBagContents["allbagsnumslotsused"] = FBBagContents["allbagsnumslotsused"] + 1	
				FBBagContents["allbagsnumslotsleft"] = FBBagContents["allbagsnumslotsleft"] - 1
				if bag == 0 then
					FBBagContents["backpacknumslotsused"] = FBBagContents["backpacknumslotsused"] + 1	
					FBBagContents["backpacknumslotsleft"] = FBBagContents["backpacknumslotsleft"] - 1
				else
					FBBagContents["bag"..bag.."numslotsused"] = FBBagContents["bag"..bag.."numslotsused"] + 1	
					FBBagContents["bag"..bag.."numslotsleft"] = FBBagContents["bag"..bag.."numslotsleft"] - 1
				end
				if not FBBagContents[name] then
					FBBagContents[name] = {}
					FBBagContents[name]["bag"] = bag
					FBBagContents[name]["slot"] = slot
					local text,count = GetContainerItemInfo(bag,slot)
					if not count then count = 0 end
					FBBagContents[name]["count"] = count
				else
					local text,count = GetContainerItemInfo(bag,slot)
					FBBagContents[name]["bag"] = bag
					FBBagContents[name]["slot"] = slot
					if not count then count = 0 end
					FBBagContents[name]["count"] = FBBagContents[name]["count"] + count
				end
			end
		end
	end
	bag = 5
	for slot = 1,23 do
		local link = GetInventoryItemLink("player",slot)
		local name
		if link then
			_,_,name = string.find(link,"%[([^%[%]]+)%]")
		end
		if name then
			if not FBBagContents[name] then
				FBBagContents[name] = {}
			end
			FBBagContents[name]["bag"] = bag
			FBBagContents[name]["slot"] = slot
			FBBagContents[name]["count"] = 1
		end
	end
end

function FB_CheckAutoItems()
-- Check to see if an auto item is empty
	if not FBProfileLoaded then return end
	FB_ScanInventory()
	local index,value
	for index,value in pairs(FBSavedProfile[FBProfileName].FlexActions) do
		if value["action"] == "autoitem" and GetActionCount(index) == 0 and value["count"] > 0 then
		-- used last one
			if FBEventToggles["autoitems"] ~= "off" then FB_RaiseEvent("AutoItemOut",value["name"]) end
			value["count"] = 0
		end
		
		if 	value["action"] == "autoitem" and value["count"] == 0 and 
			FBBagContents[value["name"]] and
			FBBagContents[value["name"]]["count"] and
			FBBagContents[value["name"]]["count"] > 0 then
		-- was out, now have some
			FB_PlaceItemOnActionBar(value["name"],index)
			if FBEventToggles["autoitems"] ~= "off" then FB_RaiseEvent("AutoItemRestored",value["name"]) end
			value["count"] = FBBagContents[value["name"]]["count"]
			PutItemInBackpack()  -- just in case it was occupied
		end
		
	end
end

function FB_FlexActionButtonBindingCode(buttonnum, keystate)
-- The code in the xml was out of control, moved to here
	local echonum = FBState[buttonnum]["echo"]
	if ( keystate == "down" ) then
		if not FBState[buttonnum]["disabled"] then
			FlexBarButtonDown(buttonnum);
		end
		if 	FBEventToggles["buttonevents"] ~= "off" then
			FB_RaiseEvent("LeftButtonDown",buttonnum)
			if echonum then
				FB_RaiseEvent("LeftButtonDown",echonum)
			end
		end
	else
		if 	FBEventToggles["buttonevents"] ~= "off" then
			FB_RaiseEvent("LeftButtonUp",buttonnum)
			if echonum then
				FB_RaiseEvent("LeftButtonUp",echonum)
			end
		end
		if not FBState[buttonnum]["disabled"] then
			FlexBarButtonUp(buttonnum);
		end
	end
end

function FB_BindingKeyEvents(source, keystate)
-- Code from FlexBar event bindings
	if 	FBEventToggles["bindingkeyevents"] ~= "off" then
		if ( keystate == "down" ) then
			FB_RaiseEvent("BindingKeyDown", source);
		else
			FB_RaiseEvent("BindingKeyUp", source);
		end
	end
end

function FB_SetCommandTimer(action, args)
-- Put a command on a timer
	if not args["in"] then return nil end
	if args["ttoggle"] and not args["tname"] then
		util:Print("Error: must specify a name for toggle to work")
		return true
	end
	local name = args["tname"] or action .. GetTime()
	local delay = args["in"]/10
	if args["ttoggle"] and FBTimers[name] and FBTimers[name]:GetRunning() then
		FBTimers[name] = nil
		return true
	end
	-- have to allocate more memory for this - no way out that I can see
	local timerargs = util:TableCopy(args)
	timerargs["tname"] = nil
	timerargs["in"] = nil
	timerargs["ttoggle"] = nil
	FBTimers[name] = Timer_Class:New(delay,nil,nil,nil,function() FB_CommandTimerDispatch(action,timerargs) end)
	return true
end

function FB_CommandTimerDispatch(action, args)
	FBEventArgs = args
	local dispatch = FBcmd.CommandList[action]
	dispatch("")
	FBEventArgs = nil
end

function FB_GetEventMsg(action, args)
-- Creates a new table with the parameters needed for RaiseEvent and populates FBEvents.
	local 	event = {}
-- if there is no "ON" directive then return nil
	if args["on"] == nil then  
		return nil 
	else
		event["on"] = args["on"]
	end
-- copy the target over to the event
	if args["target"] and type(args["target"]) == "table" then
		event["target"] = util:TableCopy(args["target"])
	else
		if args["target"] then
			event["target"] = {args["target"]}
		else
			event["target"] = nil
		end
	end
-- Set up the message string to go to Command_Class:Dispatch
	local	msg = action
	local	index, value
	for index, value in pairs(args) do
		if index~= "on" and index~="target" then
			if type(value) ~= "table" then
				if type(value) == "string" then value="'"..value.."'" end
				msg = msg .. " " .. index .. "=" .. value
			else
				msg = msg .. " " .. index .. "=" .. "["
				local index2, value2
				for index2, value2 in ipairs(value) do
					if type(value2) == "string" then value2="'"..value2.."'" end
					msg = msg .. " " .. value2
				end
				msg = msg .. " ]"
			end
		end
	end
-- Copy out the target text for the list events command.
	event["command"]=msg
	local targettext = "Target="
	if args["target"] == nil then
		targettext = targettext .. "No target"
	elseif type(args["target"]) == "string" then
		targettext = targettext .. "'" .. args["target"] .. "'"
	elseif type(args["target"]) == "table" then
		local index2, value2
		targettext=targettext .. "[ "
		for index2, value2 in ipairs(args["target"]) do
			targettext = targettext .. " " .. value2
		end
		targettext = targettext .. " ]"
	else
		targettext = targettext .. args["target"]
	end
	event["targettext"]=targettext

	if FBEvents.n == nil then FBEvents.n = 0 end
	FBEvents.n = FBEvents.n + 1
	FBEvents[FBEvents.n] = util:TableCopy(event)
	FB_Report("On " .. FBEvents[FBEvents.n]["on"] .. " " .. FBEvents[FBEvents.n]["targettext"] .. " : " .. FBEvents[FBEvents.n]["command"])
	FBSavedProfile[FBProfileName].Events = util:TableCopy(FBEvents)
	FB_CreateQuickDispatch()
	return true
end

function FB_CreateQuickDispatch()
-- Creates a quick dispatch table from the linear event table
		
	local index, event
	FBQuickEventDispatch = {}
	for index, event in pairs(FBEventToggleInfo) do
		event["lowlist"] = {}
		if FBToggles["autoperf"] then
			FBEventToggles[index] = "off"
		end
	end
	for index, event in ipairs(FBEvents) do
		local args = FBcmd:GetParameters(event["command"])
		local quickevent = {}
		local eventname = string.lower(event["on"])
		local command
		_,_,command = string.find(event["command"],"(%w+)")
		if FBQuickEventDispatch[eventname] == nil then
			FBQuickEventDispatch[eventname] = {}
		end
		if FBToggles["autoperf"] and string.lower(eventname) ~= "profileloaded" and FBEventGroups[eventname] then
			FBEventToggles[FBEventGroups[eventname]] = "low"
		end
		if event["target"] then 
			quickevent["target"] = util:TableCopy(event["target"])
			local index, target
			for index, target in ipairs(quickevent["target"]) do
				if FBEventGroups[eventname] then
					FBEventToggleInfo[FBEventGroups[eventname]]["lowlist"][target] = true
				end
			end
		end
		quickevent["command"] = command
		quickevent["args"] = util:TableCopy(args)
		table.insert(FBQuickEventDispatch[eventname], quickevent)
	end
end

function FB_MimicPetButtons()
-- Have button duplicate appearance of petbutton
	if not FBProfileLoaded then return end
	for i in pairs(FBPetActions) do
		local button = FB_GetWidgets(i)
		local id = button:GetID()
		
		if 	FBSavedProfile[FBProfileName].FlexActions[id] and
			FBSavedProfile[FBProfileName].FlexActions[id]["action"] == "pet" then
		
			local 	petbuttonID = FBSavedProfile[FBProfileName].FlexActions[id]["id"]
			local 	name, subtext, texture, isToken, 
					isActive, autoCastAllowed, autoCastEnabled = GetPetActionInfo(petbuttonID)
			local 	buttonnum = FB_GetButtonNum(button)
			
			if not name or name == "" then name = "Empty Pet Button" end
			
			if isToken then
				FBSavedProfile[FBProfileName].FlexActions[id]["texture"] = getglobal( texture )
				FBSavedProfile[FBProfileName].FlexActions[id]["name"] = TEXT(getglobal( name ))
			else
				FBSavedProfile[FBProfileName].FlexActions[id]["texture"] = texture
				FBSavedProfile[FBProfileName].FlexActions[id]["name"] = name
			end
			
			if subtext then 
				FBSavedProfile[FBProfileName].FlexActions[id]["name"] = 
					FBSavedProfile[FBProfileName].FlexActions[id]["name"] .. "\n" .. subtext
			end
			if autoCastAllowed then 
				getglobal(button:GetName() .. "AutoCastable"):Show()
			else
				getglobal(button:GetName() .. "AutoCastable"):Hide()
			end
			
			if autoCastEnabled then 
				getglobal(button:GetName() .. "AutoCast"):Show()
			else
				 getglobal(button:GetName() .. "AutoCast"):Hide()
			end
			
			if isActive then
				button:SetChecked(1)
				FBState[i]["checked"] = true
			else
				button:SetChecked(0)
				FBState[i]["checked"] = nil
			end
			
			if GetPetActionsUsable() then
				SetDesaturation(getglobal(button:GetName() .. "Icon"),nil)
			else
				SetDesaturation(getglobal(button:GetName() .. "Icon"),1)
			end
		end
		FlexBarButton_Update(button)
		FlexBarButton_UpdateCooldown(button)
	end
end

function FB_GeneratePetIDList()
-- generate a table to speed pet button processing
	for i = 1, 120 do
		local button = FB_GetWidgets(i)
		local id = button:GetID()
		if FBSavedProfile[FBProfileName].FlexActions[id] and FBSavedProfile[FBProfileName].FlexActions[id]["action"] == "pet" then
			FBPetActions[i] = true
		else
			FBPetActions[i] = nil
		end
	end
end

function FB_ResetPetItems(button)
	if 	not	(FBSavedProfile[FBProfileName].FlexActions[id] and
			 FBSavedProfile[FBProfileName].FlexActions[id]["action"] == "pet") then
	
		local 	buttonnum = FB_GetButtonNum(button)

		getglobal(button:GetName() .. "AutoCastable"):Hide()
		getglobal(button:GetName() .. "AutoCast"):Hide()
		SetDesaturation(getglobal(button:GetName() .. "Icon"),nil)
		FBState[buttonnum]["checked"] = nil

		FlexBarButton_Update(button)
		FlexBarButton_UpdateCooldown(button)
		FlexBarButton_UpdateState(button)
		
		FBPetActions[buttonnum] = nil
	end
end

local workinglist = {}
workinglist.n = 0
function FB_GetBuffs(target)
-- return a list of the buffs currently on a target
	local index
	for index = 1, workinglist.n do
		table.remove(workinglist,1)
	end
	local count = 1
	FB_MoneyToggle();
	FlexBarTooltip:SetOwner(UIParent, "ANCHOR_NONE");
	while UnitBuff(target,count) do
		FlexBarTooltip:SetUnitBuff(target,count)
		local buffname = string.lower(FlexBarTooltipTextLeft1:GetText())
		table.insert(workinglist,buffname)
		count = count+1
	end
	FB_MoneyToggle();	
	workinglist.n = count
	return workinglist
end

local typelist = {}
function FB_GetDebuffs(target)
-- return a list of the DeBuffs currently on a target, and a list of the types and their count
	local index
	for index = 1, workinglist.n do
		table.remove(workinglist,1)
	end
	for index in pairs(typelist) do
		typelist[index] = nil
	end
	local count = 1
	FB_MoneyToggle();
	FlexBarTooltip:SetOwner(UIParent, "ANCHOR_NONE");
	while UnitDebuff(target,count) do
		FlexBarTooltip:SetUnitDebuff(target,count)
		local buffname = string.lower(FlexBarTooltipTextLeft1:GetText())
		local debufftype = FlexBarTooltipTextRight1:GetText()
		if debufftype then debufftype = string.lower(debufftype) end
		table.insert(workinglist,buffname)
		if debufftype and FlexBarTooltipTextRight1:IsVisible() then
			if typelist[debufftype] then
				typelist[debufftype] = typelist[debufftype] + 1
			else
				typelist[debufftype] = 1
			end
		end
		count = count+1
	end
	FB_MoneyToggle();	
	workinglist.n = count
	return workinglist,typelist
end

--	Keep track of the buffs/debuffs/debufftypes gained/lost for the player
--	Need to raise them after all the information has been updated so if= works correctly

local playerbuffschanged = {}
playerbuffschanged["gainbuff"] = {}
playerbuffschanged["losebuff"] = {}
playerbuffschanged["gaindebuff"] = {}
playerbuffschanged["losedebuff"] = {}
playerbuffschanged["gaindebufftype"] = {}
playerbuffschanged["losedebufftype"] = {}
local unitbuffschanged = {}
unitbuffschanged["gainbuff"] = {}
unitbuffschanged["losebuff"] = {}
unitbuffschanged["gaindebuff"] = {}
unitbuffschanged["losedebuff"] = {}
unitbuffschanged["gaindebufftype"] = {}
unitbuffschanged["losedebufftype"] = {}

function FB_CheckAllBuffs(unit)
-- Scan for new/dropped buffs, debuffs and debuff types on unit
	if not FBProfileLoaded then return end
	if (not UnitName(unit)) then return end

	if FBEventToggles["buffcheck"] == "off" then return end
	if FBEventToggles["buffcheck"] == "low" and not FBEventToggleInfo["buffcheck"]["lowlist"][unit] then return end
	
	if(FB_DebugFlag == 4) then
		FB_ReportToUser("<< FB_CheckAllBuffs: Checking Buffs for "..unit.." >>"); 
	end

	local newbuffs = FB_GetBuffs(unit)
	local buffschanged = false
	local index,value
	for index,value in pairs(playerbuffschanged) do
		while value[1] do
			table.remove(value,1)
		end
	end
	for index,value in pairs(unitbuffschanged) do
		while value[1] do
			table.remove(value,1)
		end
	end
	if not FBLastBuffs["buffs"][unit] then
		FBLastBuffs["buffs"][unit] = {}
	end
	if not FBLastBuffs["debuffs"][unit] then
		FBLastBuffs["debuffs"][unit] = {}
	end
	if not FBLastBuffs["debufftypes"][unit] then
		FBLastBuffs["debufftypes"][unit] = {}
	end
	local index, value
	for index, value in ipairs(newbuffs) do
		if not FBLastBuffs["buffs"][unit][string.lower(value)] then
			-- gained a buff
			FBLastBuffs["buffs"][unit][string.lower(value)] = true
			FBBuffs["buffs"]["'"..value.."'"] = true
			if unit == "player" then
				table.insert(playerbuffschanged["gainbuff"],value)
			else
				buffschanged = true
				table.insert(unitbuffschanged["gainbuff"],value)
			end
		end
	end
	for index,value in pairs(FBLastBuffs["buffs"][unit]) do
		if not FB_InTable(index,newbuffs) then
			-- lost a buff
			FBLastBuffs["buffs"][unit][string.lower(index)] = nil
			if unit == "player" then
				table.insert(playerbuffschanged["losebuff"],index)
			else
				buffschanged = true
				table.insert(unitbuffschanged["losebuff"],index)
			end
		end
	end

	local debuffschanged = false
	local newdebuffs, newafflictions = FB_GetDebuffs(unit)
	if not FBLastBuffs["debuffs"][unit] then
		FBLastBuffs["debuffs"][unit] = {}
	end
	local index, value
	for index, value in ipairs(newdebuffs) do
		if not FBLastBuffs["debuffs"][unit][value] then
			-- gained a debuff
			FBLastBuffs["debuffs"][unit][string.lower(value)] = true
			FBBuffs["debuffs"]["'"..value.."'"] = true
			if unit == "player" then
				table.insert(playerbuffschanged["gaindebuff"],value)
			else
				debuffschanged = true
				table.insert(unitbuffschanged["gaindebuff"],value)
			end
		end
	end
	for index,value in pairs(FBLastBuffs["debuffs"][unit]) do
		if not FB_InTable(index,newdebuffs) then
			-- lost a buff
			FBLastBuffs["debuffs"][unit][string.lower(index)] = nil
			if unit == "player" then
				table.insert(playerbuffschanged["losedebuff"],index)
			else
				debuffschanged = true
				table.insert(unitbuffschanged["losedebuff"],index)
			end
		end
	end

	local afflictionschanged = false
	for index, value in pairs(newafflictions) do
		if 	not FBLastBuffs["debufftypes"][unit][string.lower(index)] or
			value > FBLastBuffs["debufftypes"][unit][string.lower(index)] then
			-- gained a new type of debuff
			FBLastBuffs["debufftypes"][unit][string.lower(index)] = value
			FBBuffs["debufftypes"]["'"..index.."'"] = true
			if unit == "player" then
				table.insert(playerbuffschanged["gaindebufftype"],index)
			else
				afflictionschanged = true
				table.insert(unitbuffschanged["gaindebufftype"],index)
			end
		end
	end
	for index,value in pairs(FBLastBuffs["debufftypes"][unit]) do
		if 	not newafflictions[string.lower(index)] or 
			value > newafflictions[string.lower(index)] then
			-- lost an debuff type
			FBLastBuffs["debufftypes"][unit][string.lower(index)] = newafflictions[string.lower(index)]
			if unit == "player" then
				table.insert(playerbuffschanged["losedebufftype"],index)
			else
				afflictionschanged = true
				table.insert(unitbuffschanged["losedebufftype"],index)
			end
		end
	end

	for index,value in pairs(playerbuffschanged) do
		local i,v
		for i,v in ipairs(value) do
			FB_RaiseEvent(index,v)
		end
	end
	
	if buffschanged == true then
		FB_RaiseEvent("UnitBuff", unit)
	end
	if debuffschanged == true then 
		FB_RaiseEvent("UnitDebuff",unit)
	end
	if afflictionschanged == true then 
		FB_RaiseEvent("UnitDebuffType",unit)
	end
	
	for index,value in pairs(unitbuffschanged) do
		local i,v
		for i,v in ipairs(value) do
			FB_RaiseEvent(unit .. index,v)
		end
	end
end
-- Apply saved states (From FB.State) to the buttons. 
-- Removed group checks - if the user wants an entire
-- group to change, they need to explicitly say so.

function FB_ApplyHidden(buttonnum)
	local button, frame = FB_GetWidgets(buttonnum)
-- Apply hidden state
	if FBState[buttonnum]["hidden"] then
		frame:Hide()
	else
		frame:Show()
	end
end

function FB_ApplyAlpha(buttonnum)
	local button, frame = FB_GetWidgets(buttonnum)
-- Apply alpha
	if FBState[buttonnum]["alpha"] then
		button:SetAlpha(FBState[buttonnum]["alpha"])
	end
end

function FB_ApplyScale(buttonnum)
	local button, frame = FB_GetWidgets(buttonnum)
-- Apply scale
	if FBState[buttonnum]["scale"] then
		button:SetScale(FBState[buttonnum]["scale"])
	else
		button:SetScale(1)
	end
end

function FB_ApplyLocked(buttonnum)
	local button, frame = FB_GetWidgets(buttonnum)
-- Determine whether the button should be locked or not and apply
-- Only the anchor of a group may be locked/unlocked.
	if 	FBState[buttonnum]["group"] and 
		buttonnum ~= FBState[buttonnum]["group"] then 
		return 
	end;
	
	if FBState[buttonnum]["locked"] then
		FB_LockButton(buttonnum)
	else
		FB_UnlockButton(buttonnum)
	end
end

function FB_ApplyRemap(buttonnum)
	local button, frame = FB_GetWidgets(buttonnum)
-- Apply remap 
	if FBState[buttonnum]["remap"] then
		button:SetID(FBState[buttonnum]["remap"])
		FlexBarButton_Update(button)
		FlexBarButton_UpdateState(button)
	else
		button:SetID(FBState[buttonnum]["oldid"])
		FlexBarButton_Update(button)
		FlexBarButton_UpdateState(button)
	end
	
end

function FB_ApplyGrid(buttonnum)
	local button, frame = FB_GetWidgets(buttonnum)
-- Apply grid
	if FBState[buttonnum]["hidegrid"] then
		button.showgrid=0
		frame:DisableDrawLayer()
	else
		button.showgrid=1
		button:Show()
		frame:EnableDrawLayer()
	end
	FlexBarButton_Update(button)
	FlexBarButton_UpdateState(button)
end

function FB_ApplyTextPosition(buttonnum)
	local button, frame = FB_GetWidgets(buttonnum)
	local buttonname = button:GetName()
	local text = getglobal(buttonname.."HotKey")
	local text2 = getglobal(buttonname.."Text2")
	local text3 = getglobal(buttonname.."Text3")
	
	local textpos = FBState[buttonnum]["justifytext"]
	local text2pos = FBState[buttonnum]["justifytext2"]
	local text3pos = FBState[buttonnum]["justifytext3"]
	
	if textpos == nil then textpos = "TOPRIGHT" end
	text:SetJustifyH("CENTER")
	local pos = string.upper(textpos)
	if string.find(pos,"LEFT") then
		text:SetJustifyH("LEFT")
	end
	if string.find(pos,"RIGHT") then
		text:SetJustifyH("RIGHT")
	end
	text:ClearAllPoints()
	text:SetPoint(pos,buttonname,pos,0,0)

	if text2pos == nil then text2pos = "BOTTOMLEFT" end 
	text2:SetJustifyH("CENTER")
	local pos = string.upper(text2pos)
	if string.find(pos,"LEFT") then
		text2:SetJustifyH("LEFT")
	end
	if string.find(pos,"RIGHT") then
		text2:SetJustifyH("RIGHT")
	end
	text2:ClearAllPoints()
	text2:SetPoint(pos,buttonname,pos,0,0)
	
	if text3pos == nil then text3pos = "CENTER" end 
	text3:SetJustifyH("CENTER")
	local pos = string.upper(text3pos)
	if string.find(pos,"LEFT") then
		text3:SetJustifyH("LEFT")
	end
	if string.find(pos,"RIGHT") then
		text3:SetJustifyH("RIGHT")
	end
	text3:ClearAllPoints()
	text3:SetPoint(pos,buttonname,pos,0,0)
	FB_SaveState(buttonnum)
end

-- Toggle Preserve for GameTooltip_ClearMoney functionality
function FB_MoneyToggle()
	if( Save_Original_GameTooltip_ClearMoney ) then
		GameTooltip_ClearMoney = Save_Original_GameTooltip_ClearMoney;
		Save_Original_GameTooltip_ClearMoney = nil;
		FlexBarTooltip:SetOwner(UIParent, "ANCHOR_NONE");
	else
		Save_Original_GameTooltip_ClearMoney = GameTooltip_ClearMoney;
		GameTooltip_ClearMoney = FB_GameTooltip_ClearMoney;
		FlexBarTooltip:SetOwner(UIParent, "ANCHOR_NONE");
	end
end

function FB_GameTooltip_ClearMoney()
	-- Intentionally empty; don't clear money while we use hidden tooltips
end

function FB_StoreGroupOffsetForMove(group)
	-- Stores group button offset values for non-anchor buttons prior to a group anchor move taking
	-- place. This is required in order to ensure all group button locations are properly updated
	-- at the time the group anchor is moved. For programmatical moves it seems that the WoW Update
	-- time for button locations not attached to UIParent in memory varies.
	local index
	local members = FB_GetGroupMembers(group)
	
	if members.n < 1 then return end

	if(FB_DebugFlag == 2) then
		FB_ReportToUser("<< FB_StoreGroupOffsetForMove: Group #"..group.." Store Offsets >>"); 
	end

	local anchor = FBState[members[1]]["group"]
	local aX = FBState[anchor]["xcoord"]
	local aY = FBState[anchor]["ycoord"]
		
	for index = 1, members.n do
		if (members[index] ~= anchor) then
			local oX = FBState[members[index]]["xcoord"] - aX
			local oY = FBState[members[index]]["ycoord"] - aY

			FBGroupMoveOffset[members[index]]["xcoord"] = oX
			FBGroupMoveOffset[members[index]]["ycoord"] = oY

			if(FB_DebugFlag == 2) then
				FB_ReportToUser("<< StoreOffset B# "..members[index].." (oX="..oX..", oY="..oY.."), (aX="..aX..", aY="..aY..") >>");
				--FB_PrintButtonSnapshot(members[index])
			end
		end
	end
end

function FB_RestoreGroupOffsetAfterMove(group)
	-- Resets stored values for non-anchor buttons after a group move command has been executed.
	-- This way we do not have to wait some indeterminate amount of time to get coordinates for the group.
	local index
	local members = FB_GetGroupMembers(group)
	
	if members.n < 1 then return end

	if(FB_DebugFlag == 2) then
		FB_ReportToUser("<< FB_RestoreGroupOffsetAfterMove: Group #"..group.." Restore Offsets >>"); 
	end

	local anchor = FBState[members[1]]["group"]
	local aX = FBState[anchor]["xcoord"]
	local aY = FBState[anchor]["ycoord"]
		
	for index = 1, members.n do
		if (members[index] ~= anchor) then
			local oX = FBGroupMoveOffset[members[index]]["xcoord"]
			local oY = FBGroupMoveOffset[members[index]]["ycoord"]
			
			-- For a mysterious reason, a nil error appeared here, so we return if oX or oY is nil
			if(oX == nil or oY == nil) then return; end;			
			
			FBState[members[index]]["xcoord"] = aX + oX
			FBState[members[index]]["ycoord"] = aY + oY
			FBSavedProfile[FBProfileName][members[index]].State["xcoord"] = aX + oX
			FBSavedProfile[FBProfileName][members[index]].State["ycoord"] = aY + oY

			FB_UpdateSmartPet(members[index])
			
			if(FB_DebugFlag == 2) then
				FB_ReportToUser("<< RestOffset B# "..members[index].." (oX="..oX..", oY="..oY.."), (aX="..aX..", aY="..aY..") >>");
				--FB_PrintButtonSnapshot(members[index])
			end
		end
	end
end

function FB_SetDebugLevel(val)
	util:Print("<< FB_SetDebugLevel: Debug Level changed to: "..val.." >>", "red"); 
	FB_DebugFlag = val
end

function FB_ShowButtonInfo()
	if(FB_DebugFlag == 1) then
		FB_ReportToUser("<< FB_ShowButtonInfo: Entry >>"); 
	end

	local index
	
	for index = 1,FBNumButtons do
		FB_ShowSingleButtonInfo(index)
	end

	FBButtonInfoShown = true
end

function FB_ShowSingleButtonInfo(btn)
	if(FB_DebugFlag == 1) then
		FB_ReportToUser("<< FB_ShowButtonInfo: Entry >>"); 
	end

	local grp, map
	local text1, text2, text3
	
	FBButtonTextSave[btn]["text1"] = FBState[btn]["hotkeytext"]
	FBButtonTextSave[btn]["text2"] = FBState[btn]["text2"]
	FBButtonTextSave[btn]["text3"] = FBState[btn]["text3"]
		
	text1 = getglobal("FlexBarButton"..btn.."HotKey")
	text2 = getglobal("FlexBarButton"..btn.."Text2")
	text3 = getglobal("FlexBarButton"..btn.."Text3")
		
	text1:SetText("b"..btn)
		
	if(FBState[btn]["group"]) then
		grp = "g"..FBState[btn]["group"]
	else
		grp = "none"
	end
		
	text2:SetText(grp)

	if(FBState[btn]["remap"]) then
		map = "r"..FBState[btn]["remap"]
	else
		map = "none"
	end

	text3:SetText(map)

end
function FB_RestoreButtonText()
	if(FB_DebugFlag == 1) then
		FB_ReportToUser("<< FB_RestoreButtonText Entry >>"); 
	end

	local index, button
	local text1, text2, text3
	
	for index = 1,FBNumButtons do
		text1 = getglobal("FlexBarButton"..index.."HotKey")
		text2 = getglobal("FlexBarButton"..index.."Text2")
		text3 = getglobal("FlexBarButton"..index.."Text3")

		text1:SetText(FBButtonTextSave[index]["text1"])
		text2:SetText(FBButtonTextSave[index]["text2"])
		text3:SetText(FBButtonTextSave[index]["text3"])

		FB_TextSub(index)
		FB_ApplyTextPosition(index)
		button = FB_GetWidgets(index)
		FlexBarButton_UpdateHotkeys(button)
	end

	FBButtonInfoShown = false
end

function FB_UpdateSmartPet(btn)
	if IsAddOnLoaded("SmartPet") and 
		FBSavedProfile[FBProfileName].FlexActions[btn] and
		FBSavedProfile[FBProfileName].FlexActions[btn].action == "pet" then
		local petid = FBSavedProfile[FBProfileName].FlexActions[btn].id
		local smartie = getglobal("SmartPetActionButton"..petid)
		local flexie = getglobal("FlexBarButton"..btn)
		if not smartie then
			FB_ReportToUser("SmartPet action button "..petid.." not found")
		else
			if smartie:GetParent() ~= flexie then
				smartie:SetParent(flexie)
				smartie:ClearAllPoints()
				smartie:SetPoint("BOTTOM", flexie, "TOP", 0, 0)
			end
		end
	end
end