--[[
Name: AutoBar
Revision: $Rev: 24739 $
Author: Toadkiller of Proudmoore
Credits: Saien the original author.  Sayclub (Korean), PDI175 (Chinese traditional and simplified), Teodred (German), Cinedelle (French), shiftos (Spanish)
Website: http://www.wowace.com/
Description: Dynamic 24 button bar automatically adds potions, water, food and other items you specify into a button for use. Does not use action slots so you can save those for spells and abilities.
]]
--
-- Copyright 2004, 2005, 2006 original author.
-- New Stuff Copyright 2006+ Toadkiller of Proudmoore.

-- Maintained by Azethoth / Toadkiller of Proudmoore.  Original author Saien of Hyjal
-- http://www.wowace.com/wiki/AutoBar
-- http://www.curse-gaming.com/en/wow/addons-4430-1-autobar-toadkiller.html

-- Coming Attractions:
-- Set operations / calculated categories.
-- Multible bars or individually draggable buttons?
-- Exchange profiles
-- Hide Microbuttons option
-- Indication of when an object being edited is obscured by a higher layer.
-- Named Custom Categories, Named button ranges.
-- Inventory & Instance checks
-- Buff detection: Don't show buff items if buffed already
-- Crafting slots
-- Spell Slots
-- Full Mount support, including stance switching

-- Next Up:
-- AceDB
-- Dragging items to category select inserts first matched category instead of a custom item.

-- Named Slots
-- Hide slot option
-- Food Blend for pets.

-- 2.00.00.29 beta
-- Consumable.Warlock.Health Stone -> Consumable.Warlock.Healthstone
-- Jeweler Stone Statues added
-- First cut at new buffs.  Default categories are buffs general, melee, caster and other.
-- Targeted buffs have lower default priority
-- More specific categories are available as well by buff type.
-- Requires a reset on the Profile Tab or manual adding of buff categories
-- Disable arrange on use for first popup button
-- Removed bogus instance dependency for flying mounts.  Awaiting patch 2.1 "canfly" or something property for final flying mount support.
-- Reorganize buff foods a bit.  Added warp burgers.
-- Add Chest and Shield Buffs

-- 2.00.00.28 beta
-- Mini Pets: Gurky [Pink Murloc Egg] itemid = 22114, Murky [Blue Murloc Egg] itemid = 20371
-- Major Combat Potions
-- Fixed right click Conjure Food for mages
-- PT3.  This is a reworked item database, if you are missing an item see above for how to report it.
-- Code to add priority to certain items.  eg. conjured food.  Not used everywhere yet.
-- New conjured food & water added

-- 2.00.00.27 beta
-- Dont lock size for cyCircled buttons
-- Dont show annoying ugly borders around buttons
-- Slightly better button click feedback

-- 2.00.00.26 beta
-- Kill the keybind tab & switch back to Blizzard KeyBind UI based keys.
-- Keys tab keys are not preserved, old ones still bound from Blizzard UI will be reused.
-- Barring bugs that finalizes the key bindings feature.
-- Allow gapping of -1
-- CanCastSpell -> AutoBarProfile:CanCastSpell
-- Zorbin's Ultra-Shrinker

-- See Changelist.lua for older versions


local _G = getfenv(0);
local L = AceLibrary("AceLocale-2.2"):new("AutoBar");
local BS = AceLibrary:GetInstance("Babble-Spell-2.2");

BINDING_HEADER_AUTOBAR_SEP = L["AUTOBAR"];
BINDING_NAME_AUTOBAR_CONFIG = L["CONFIG_WINDOW"];
BINDING_NAME_AUTOBAR_BUTTON1 = L["BUTTON"] .. " 1";
BINDING_NAME_AUTOBAR_BUTTON2 = L["BUTTON"] .. " 2";
BINDING_NAME_AUTOBAR_BUTTON3 = L["BUTTON"] .. " 3";
BINDING_NAME_AUTOBAR_BUTTON4 = L["BUTTON"] .. " 4";
BINDING_NAME_AUTOBAR_BUTTON5 = L["BUTTON"] .. " 5";
BINDING_NAME_AUTOBAR_BUTTON6 = L["BUTTON"] .. " 6";
BINDING_NAME_AUTOBAR_BUTTON7 = L["BUTTON"] .. " 7";
BINDING_NAME_AUTOBAR_BUTTON8 = L["BUTTON"] .. " 8";
BINDING_NAME_AUTOBAR_BUTTON9 = L["BUTTON"] .. " 9";
BINDING_NAME_AUTOBAR_BUTTON10 = L["BUTTON"] .. " 10";
BINDING_NAME_AUTOBAR_BUTTON11 = L["BUTTON"] .. " 11";
BINDING_NAME_AUTOBAR_BUTTON12 = L["BUTTON"] .. " 12";
BINDING_NAME_AUTOBAR_BUTTON13 = L["BUTTON"] .. " 13";
BINDING_NAME_AUTOBAR_BUTTON14 = L["BUTTON"] .. " 14";
BINDING_NAME_AUTOBAR_BUTTON15 = L["BUTTON"] .. " 15";
BINDING_NAME_AUTOBAR_BUTTON16 = L["BUTTON"] .. " 16";
BINDING_NAME_AUTOBAR_BUTTON17 = L["BUTTON"] .. " 17";
BINDING_NAME_AUTOBAR_BUTTON18 = L["BUTTON"] .. " 18";
BINDING_NAME_AUTOBAR_BUTTON19 = L["BUTTON"] .. " 19";
BINDING_NAME_AUTOBAR_BUTTON20 = L["BUTTON"] .. " 20";
BINDING_NAME_AUTOBAR_BUTTON21 = L["BUTTON"] .. " 21";
BINDING_NAME_AUTOBAR_BUTTON22 = L["BUTTON"] .. " 22";
BINDING_NAME_AUTOBAR_BUTTON23 = L["BUTTON"] .. " 23";
BINDING_NAME_AUTOBAR_BUTTON24 = L["BUTTON"] .. " 24";


function AutoBarConfig_Toggle(msg)
	if (not InCombatLockdown()) then
		local loaded, message = LoadAddOn("AutoBarConfig");
		if (loaded) then
			PlaySound("igMainMenuOpen");
			if (not AutoBarConfig.initialized) then
				AutoBarConfig:TabButtonInitialize();
				AutoBarConfig.initialized = true;
			end

			if (AutoBarConfigFrame:IsVisible()) then
				AutoBarConfigFrame:Hide();
			else
				AutoBarConfigFrame:Show();
			end
		else
			PlaySound("TellMessage");
			DEFAULT_CHAT_FRAME:AddMessage(L["LOAD_ERROR"]..message);
		end
	end
end

local options = {
	type = 'execute',
	desc = "Toggle the config panel",
	func = AutoBarConfig_Toggle,
}


-- If the Debug library is available then use it
if AceLibrary:HasInstance("AceDebug-2.0") then
	AutoBar = AceLibrary("AceAddon-2.0"):new("AceEvent-2.0", "AceConsole-2.0", "AceDB-2.0", "AceHook-2.1", "AceDebug-2.0");
else
	AutoBar = AceLibrary("AceAddon-2.0"):new("AceEvent-2.0", "AceConsole-2.0", "AceDB-2.0", "AceHook-2.1");
end
AutoBar:RegisterChatCommand({L["SLASHCMD_SHORT"], L["SLASHCMD_LONG"]}, options)
AutoBar:RegisterDB("AutoBarDB", "AutoBarDBPC")
AutoBar:RegisterDefaults("char", {
--		profile = {};
--		profile.useCharacter = true;
--		profile.useShared = false;
--		profile.useClass = false;
--		profile.useBasic = false;
--		profile.edit = 1;
--		profile.shared = "_SHARED1";
--		profile.layout = 1;
--		profile.layoutProfile = AutoBar.currentPlayer;
	}
)
-- Table recycling stuff
local new, del, erase
do
	local created = 0
	local deleted = 0
	local recycled = 0
	local erased = 0
	local cache = setmetatable({}, {__mode='k'})
	local noaccess = {
		__newindex = function() error('write not allowed') end;
		__index = function() error('read not allowed') end;
	}
	function erase(t, depth)
		depth = depth or 0
		for k,v in pairs(t) do
			t[k] = nil
			if depth > 0 and type(v) == 'table' and not cache[v] then
				del(v, depth - 1)
			end
		end
		erased = erased + 1
	end
	function new(...)
		local t = next(cache)
		if t then
			cache[t] = nil
			setmetatable(t, nil)
			for i = 1, select('#', ...) do
				t[i] = select(i, ...)
			end
			recycled = recycled + 1
			return t
		else
			created = created + 1
			return { ... }
		end
	end
	function del(t, depth)
		if type(t) == 'table' then
			setmetatable(t, nil);
			erase(t, depth)
			setmetatable(t, noaccess);
			cache[t] = true
			deleted = deleted + 1
		end
		return nil
	end
	function AutoBar:RecycleStats()
		local n = 0
		for t in pairs(cache) do n = n + 1 end
		self:Print('Table stats: %d created, %d recycled, %d erased, %d deleted, %d in cache',
			created, recycled, erased, deleted, n);
	end
end

AUTOBAR_MAXBUTTONS = 24;
AUTOBAR_MAXPOPUPBUTTONS = 12;
AUTOBAR_MAXSLOTCATEGORIES = 16;

AutoBar_Debug = nil;

AutoBar_SearchedForItems = {};
local AutoBar_ButtonItemList = {};
local AutoBar_ButtonItemList_Reversed = {};
AutoBar_Buttons_CurrentItems = {};


function AutoBar:OnInitialize()
	AutoBar.currentPlayer = UnitName("player") .. " - " .. GetCVar("realmName");

	AutoBar.inWorld = false;
	AutoBar.inCombat = false;	-- For item use restrictions
	AutoBar.inBG = false;		-- For battleground only items

	-- Single parent for key binding overides
	self.keyframe = CreateFrame("Frame", nil, UIParent);

	AutoBarItemList:OnInitialize();
	AutoBarProfile.Initialize();
	AutoBar:LayoutInitialize();
end


function AutoBar:OnEnable()
	-- Called when the addon is enabled
	self:RegisterEvent("PLAYER_ENTERING_WORLD")
	self:RegisterEvent("PLAYER_LEAVING_WORLD")
	self:RegisterEvent("BAG_UPDATE")
--	self:RegisterEvent("ACTIONBAR_UPDATE_USABLE")
	self:RegisterOverrideBindings()
	self:RegisterEvent("UPDATE_BINDINGS")

	-- For item use restrictions
	self:RegisterEvent("ZONE_CHANGED_NEW_AREA")
	self:RegisterEvent("PLAYER_REGEN_ENABLED")
	self:RegisterEvent("PLAYER_REGEN_DISABLED")
	self:RegisterEvent("PLAYER_ALIVE")
	self:RegisterEvent("PLAYER_UNGHOST")
	self:RegisterEvent("BAG_UPDATE_COOLDOWN")
	self:RegisterEvent("UPDATE_BATTLEFIELD_STATUS")
end


function AutoBar:OnDisable()
	-- Called when the addon is disabled
end


-- Copied from Bartender3
-- Create Override Bindings from the Blizzard bindings.  These do not clash so all is happy.
-- Will not update if set during combat
function AutoBar:RegisterOverrideBindings()
	ClearOverrideBindings(self.keyframe)
	for buttonsIndex = 1, AUTOBAR_MAXBUTTONS, 1 do
		local button = AutoBar.ssButtons[buttonsIndex];
		local key1, key2 = GetBindingKey("AUTOBAR_BUTTON"..buttonsIndex)
		if key1
			then SetOverrideBindingClick(self.keyframe, false, key1, "AutoBarSAB" .. buttonsIndex)
			button.hotKey:SetText(AutoBarButton:GetHotKeyDisplayText(key1));
		end
		if key2 then
			SetOverrideBindingClick(self.keyframe, false, key2, "AutoBarSAB" .. buttonsIndex)
			button.hotKey:SetText(AutoBarButton:GetHotKeyDisplayText(key2));
		end
	end
end


local delayScanBags = nil;
local delayButtonsUpdate = nil;
function AutoBar:DelayScanBags()
	local currentTime = GetTime();
	local function DelayedScanBags()
--DEFAULT_CHAT_FRAME:AddMessage("   AutoBar:DelayScanBags end delay at  " .. tostring(currentTime));
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:DelayScanBags total" .. tostring(GetTime() - delayScanBags));
		delayScanBags = nil;
		AutoBar_ScanBags();
	end

	if (delayScanBags) then
--DEFAULT_CHAT_FRAME:AddMessage("   AutoBar:DelayScanBags delay at      " .. currentTime);
		self:CancelScheduledEvent("AutoBarScanBags");
		if (InCombatLockdown()) then
			return;
		end

		self:ScheduleEvent("AutoBarScanBags", DelayedScanBags, 0.1);
	else
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:DelayScanBags start delay at" .. currentTime);
		delayScanBags = currentTime;
		self:ScheduleEvent("AutoBarScanBags", DelayedScanBags, 0.1);
		if (delayButtonsUpdate) then
			self:CancelScheduledEvent("AutoBarButtonsUpdate");
		end
	end
end


function AutoBar:DelayButtonsUpdate()
	local currentTime = GetTime();
	local function DelayedButtonsUpdate()
--DEFAULT_CHAT_FRAME:AddMessage("   AutoBar:DelayButtonsUpdate end delay at  " .. tostring(currentTime));
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:DelayButtonsUpdate total" .. tostring(GetTime() - delayButtonsUpdate));
		delayButtonsUpdate = nil;
		if (AutoBar.display.alignButtons and math.fmod(AutoBar.display.alignButtons, 2)) then
			AutoBar:ButtonsUpdate();
		else
			AutoBar:LayoutUpdate()
		end
--		AutoBar:ButtonsUpdate();
	end

	if (delayScanBags) then
--DEFAULT_CHAT_FRAME:AddMessage("   AutoBar:DelayButtonsUpdate delay at      " .. currentTime);
		-- Cancel because DelayScanBags supersedes ButtonsUpdate()
		if (self:IsEventScheduled("AutoBarButtonsUpdate")) then
			self:CancelScheduledEvent("AutoBarButtonsUpdate");
		end
	elseif (not delayButtonsUpdate) then
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:DelayButtonsUpdate start delay at" .. currentTime);
		delayButtonsUpdate = currentTime;
		self:ScheduleEvent("AutoBarButtonsUpdate", DelayedButtonsUpdate, 0.1);
	else
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:DelayButtonsUpdate continue delay at" .. currentTime);
		self:CancelScheduledEvent("AutoBarButtonsUpdate");
		if (InCombatLockdown()) then
			return;
		end
		self:ScheduleEvent("AutoBarButtonsUpdate", DelayedButtonsUpdate, 0.1);
	end
end


function AutoBar:PLAYER_ENTERING_WORLD()
	AutoBar.inCombat = false;
	local scanned = false;
	if (not AutoBar.initialized) then
		AutoBar.ConfigChanged();
		profileLoaded = true;
		scanned = true;
		AutoBar.initialized = true;
	end

	if (not AutoBar.inWorld) then
		AutoBar.inWorld = true;
		if (not scanned) then
			AutoBar:DelayScanBags();
		else
			AutoBar:LayoutUpdate();
		end
	end
end


function AutoBar:PLAYER_LEAVING_WORLD()
	AutoBar.inWorld = false;
end


local pendingScan = false;
function AutoBar:BAG_UPDATE()
	if (AutoBar.inWorld and arg1 < 5) then
		if (InCombatLockdown()) then
			AutoBar:ButtonsEvent(AutoBarButton.UpdateCount);
			pendingScan = true;
		else
			AutoBar:DelayScanBags(arg1);
		end
	end
end


--function AutoBar:ACTIONBAR_UPDATE_USABLE()
--	if (AutoBar.inWorld and arg1 and arg1 < 5) then
--		if (InCombatLockdown()) then
--			AutoBar:ButtonsEvent(AutoBarButton.UpdateUsable);
--			pendingScan = true;
--		else
--			AutoBar:DelayScanBags(arg1);
--		end
--	end
--end
--
--
function AutoBar:UPDATE_BINDINGS()
	if (not InCombatLockdown()) then
		self:RegisterOverrideBindings();
		AutoBar:DelayButtonsUpdate();
	end
end


function AutoBar:ZONE_CHANGED_NEW_AREA()
	if (not InCombatLockdown()) then
		AutoBar:DelayButtonsUpdate();
	end
end


function AutoBar:PLAYER_REGEN_ENABLED()
	AutoBar.inCombat = false;
	if (pendingScan) then
		AutoBar:DelayScanBags();
		pendingScan = false;
	else
		AutoBar:DelayButtonsUpdate();
	end
end


function AutoBar:PLAYER_REGEN_DISABLED()
	AutoBar.inCombat = true;
	if (self:IsEventScheduled("AutoBarScanBags")) then
		self:CancelScheduledEvent("AutoBarScanBags");
		delayScanBags = nil;
		AutoBar_ScanBags();
	else
		if (self:IsEventScheduled("AutoBarButtonsUpdate")) then
			self:CancelScheduledEvent("AutoBarButtonsUpdate");
			delayButtonsUpdate = nil;
		end
		AutoBar:ButtonsUpdate();
	end
	if (AutoBarConfigFrame) then
		AutoBarConfigFrame:Hide();
	end
end


function AutoBar:UNIT_MANA()
	if (arg1 == "player") then
		if (not InCombatLockdown()) then
			AutoBar:DelayButtonsUpdate();
		end
	end
end


function AutoBar:UNIT_HEALTH()
	if (arg1 == "player") then
		if (not InCombatLockdown()) then
			AutoBar:DelayButtonsUpdate();
		end
	end
end


function AutoBar:PLAYER_ALIVE()
	if (not InCombatLockdown()) then
		AutoBar:DelayButtonsUpdate();
	end
end


function AutoBar:PLAYER_UNGHOST()
	if (not InCombatLockdown()) then
		AutoBar:DelayButtonsUpdate();
	end
end


function AutoBar:BAG_UPDATE_COOLDOWN()
	if (InCombatLockdown()) then
		AutoBar:ButtonsEvent(AutoBarButton.UpdateCooldown);
	else
		AutoBar:DelayButtonsUpdate();
	end
end


function AutoBar:UPDATE_BATTLEFIELD_STATUS()
	if (AutoBar.inWorld) then
		for i = 1, MAX_BATTLEFIELD_QUEUES do
			local status, mapName, instanceID = GetBattlefieldStatus(i);
			if (instanceID ~= 0) then
				AutoBar.inBG = true;
				return;
			else
				AutoBar.inBG = false;
			end
		end
	end
end


-- When dragging, contains { frameName, index }, otherwise nil
AutoBar.dragging = nil;
local draggingData = {};

function AutoBar.GetDraggingIndex(frameName)
	if (AutoBar.dragging and AutoBar.dragging.frameName == frameName) then
		return AutoBar.dragging.index;
	end
	return nil;
end


function AutoBar.SetDraggingIndex(frameName, index)
	draggingData.frameName = frameName;
	draggingData.index = index;
	AutoBar.dragging = draggingData;
end


function AutoBar.LinkDecode(link)
	if (link) then
		local _, _, id, _, _, _, name = string.find(link, "item:(%d+):(%d+):(%d+):(%d+).+%[(.+)%]");
		if (id and name) then
			return name, tonumber(id);
		end
	end
end


local function AutoBar_BuildItemList()
	local function AddItemInfo(identifier, buttonsIndex)
		if (AutoBar_Category_Info[identifier] and AutoBar_Category_Info[identifier].items) then
			local index, j;
			for index, j in pairs(AutoBar_Category_Info[identifier].items) do
				if (AutoBar_SearchedForItems[j]) then
					table.insert(AutoBar_SearchedForItems[j], buttonsIndex);
				else
					AutoBar_SearchedForItems[j] = new( buttonsIndex, identifier, index );
				end
				table.insert(AutoBar_ButtonItemList[buttonsIndex], j);
			end
		else
			if (AutoBar_SearchedForItems[identifier]) then
				table.insert(AutoBar_SearchedForItems[identifier], buttonsIndex);
			else
				AutoBar_SearchedForItems[identifier] = new( buttonsIndex, identifier, 0 );
			end
			table.insert(AutoBar_ButtonItemList[buttonsIndex], identifier);
		end
	end
	erase(AutoBar_SearchedForItems,10);
	erase(AutoBar_ButtonItemList_Reversed,10);
	local index;
	for index = 1, AUTOBAR_MAXBUTTONS, 1 do
		if (AutoBar.buttons[index]) then
			local i, j;
			AutoBar_ButtonItemList[index] = new();
			AutoBar_ButtonItemList_Reversed[index] = new();
			if (type(AutoBar.buttons[index]) == "table") then
				for i, j in pairs(AutoBar.buttons[index]) do
					AddItemInfo (j, index);
				end
			else
				AddItemInfo (AutoBar.buttons[index], index);
			end
			for i, j in pairs(AutoBar_ButtonItemList[index]) do
				AutoBar_ButtonItemList_Reversed[index][j] = i;
			end
		end
	end
end


function AutoBar_Button_GetDisplayItem(buttonsIndex)
	local index, bag, slot, rank, itemId, category, categoryIndex, acceptable, cooldowntime, itemMinLevel;
	local cooldownIndex, start, duration, enable, fallback;
	if (AutoBar_Buttons_CurrentItems[buttonsIndex]) then
		index = table.maxn(AutoBar_Buttons_CurrentItems[buttonsIndex]);
	else
		index = 0;
	end
--DEFAULT_CHAT_FRAME:AddMessage("DisplayItem " .. buttonsIndex .. " index " .. tostring(index) .. " " .. tostring(index));
	while (index > 0 and not acceptable) do
		bag = AutoBar_Buttons_CurrentItems[buttonsIndex][index].items[1][1];
		slot = AutoBar_Buttons_CurrentItems[buttonsIndex][index].items[1][2];
		rank = AutoBar_Buttons_CurrentItems[buttonsIndex][index].rank;
		itemId = AutoBar_ButtonItemList[buttonsIndex][rank];
		if (type(itemId) == "number") then
			_,_,_,_,itemMinLevel = GetItemInfo(itemId);
		else
			itemMinLevel = 0;
		end
		if (AutoBar_SearchedForItems[itemId]) then
			category = AutoBar_SearchedForItems[itemId][2];
			categoryIndex = AutoBar_SearchedForItems[itemId][3];
		else
			category = nil;
			categoryIndex = nil;
		end

		if (itemMinLevel > UnitLevel("player")) then
			index = index - 1;
		elseif (AutoBar_Category_Info[category] and AutoBar_Category_Info[category].location and AutoBar_Category_Info[category].location ~= GetRealZoneText()) then
			index = index - 1;
		elseif (AutoBar_Category_Info[category] and AutoBar_Category_Info[category].battleground and not AutoBar.inBG) then
			index = index - 1;
		elseif (AutoBar_Category_Info[category]) then
			if (not fallback) then
				fallback = index;
			end
			if (AutoBar_Category_Info[category].noncombat and AutoBar.inCombat) then
				index = index - 1;
			elseif (AutoBar_Category_Info[category].limit) then
				local losthp = UnitHealthMax("player") - UnitHealth("player");
				local lostmana = UnitManaMax("player") - UnitMana("player");
				if (UnitPowerType("player") ~= 0 ) then
					--if (UnitClass("player") == "Druid") then
						-- Can't check mana in druid forms
					--	lostmana = 0;
					--else
						-- Class doesn't have mana, don't limit
						lostmana = 10000;
					--end
				end
				if (AutoBar_Category_Info[category].limit.downhp and AutoBar_Category_Info[category].limit.downhp[categoryIndex] > losthp) then
					index = index - 1
				elseif (AutoBar_Category_Info[category].limit.downmana and AutoBar_Category_Info[category].limit.downmana[categoryIndex] > lostmana) then
					index = index - 1
				else
					acceptable = true;
				end
			else
				acceptable = true;
			end
		else
			acceptable = true;
		end
	end
	if (not acceptable) then
		if (fallback) then
			index = fallback;
		elseif (cooldownIndex) then
			index = cooldownIndex;
		elseif (AutoBar_Buttons_CurrentItems[buttonsIndex]) then
			index = table.maxn(AutoBar_Buttons_CurrentItems[buttonsIndex]);
		else
			index = nil;
		end
	end
	--
	if (index) then
		bag = AutoBar_Buttons_CurrentItems[buttonsIndex][index].items[1][1];
		slot = AutoBar_Buttons_CurrentItems[buttonsIndex][index].items[1][2];
		rank = AutoBar_Buttons_CurrentItems[buttonsIndex][index].rank;
	else
		bag = nil; slot = nil; rank = nil;
	end
	if (AutoBar_ButtonItemList[buttonsIndex]) then
		itemId = AutoBar_ButtonItemList[buttonsIndex][rank];
		if (AutoBar_SearchedForItems[itemId]) then
			category = AutoBar_SearchedForItems[itemId][2];
			categoryIndex = AutoBar_SearchedForItems[itemId][3];
		else
			category = nil;
			categoryIndex = nil;
		end
	else
		itemId = nil; category = nil; categoryIndex = nil;
	end
	return bag, slot, rank, itemId, category, categoryIndex, index, acceptable, cooldowntime;
end


-- Assign content to the buttons, set count & keybinding texts, visibility, etc.
function AutoBar:ButtonsEvent(eventHandler)
	for buttonsIndex = 1, AUTOBAR_MAXBUTTONS, 1 do
		local button = AutoBar.ssButtons[buttonsIndex];
		if (button:GetAttribute("buttonsIndex")) then
			eventHandler(self, button);
		end
	end
end


-- Assign display buttons to active slots and return the number of displayed buttons
function AutoBar:AssignButtons(maxButtons)
	local displayedButtons = 1;

	if (AutoBar.ssButtons) then
		local buttonsIndex, buttonInfo, rankIndex, items;
		for buttonsIndex = 1, AUTOBAR_MAXBUTTONS do
			local button = AutoBar.ssButtons[buttonsIndex];
			if (maxButtons and displayedButtons > maxButtons) then
				button:SetAttribute("buttonsIndex", nil);
			elseif (AutoBar.display.showEmptyButtons or AutoBar_Buttons_CurrentItems[buttonsIndex]) then
				button:SetAttribute("buttonsIndex", buttonsIndex);
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:AssignButtons CurrentItems -> "..buttonsIndex);
				displayedButtons = displayedButtons + 1;
			elseif (AutoBar.buttons[buttonsIndex] and AutoBar.display.showCategoryIcon) then
				button:SetAttribute("buttonsIndex", buttonsIndex);
				displayedButtons = displayedButtons + 1;
			elseif (AutoBar.buttons[buttonsIndex] and AutoBar.buttons[buttonsIndex].castSpell) then
				local usable, noMana = IsUsableSpell(AutoBar.buttons[buttonsIndex].castSpell);
				if (usable or noMana) then
					button:SetAttribute("buttonsIndex", buttonsIndex);
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:AssignButtons " .. tostring(AutoBar.buttons[buttonsIndex].castSpell));
					displayedButtons = displayedButtons + 1;
				else
					button:SetAttribute("buttonsIndex", nil);
				end
			else
				button:SetAttribute("buttonsIndex", nil);
			end
		end
	end
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:AssignButtons maxButtons " .. maxButtons .. " displayedButtons " .. (displayedButtons - 1));

	return displayedButtons - 1;
end


function AutoBar_ScanBags(specificbag)
	local function ClearOutBag(bag)
		local buttonsIndex, index, i, bagSlot, newitemlist, newranks;
		for buttonsIndex = 1, AUTOBAR_MAXBUTTONS, 1 do
			if (AutoBar_Buttons_CurrentItems[buttonsIndex]) then
				newranks = new();
				for index in pairs(AutoBar_Buttons_CurrentItems[buttonsIndex]) do
					newitemlist = new();
					for i, bagSlot in pairs(AutoBar_Buttons_CurrentItems[buttonsIndex][index].items) do
						if (bag ~= bagSlot[1]) then
							table.insert(newitemlist,bagSlot);
						end
					end
					if (table.maxn(newitemlist) > 0) then
						AutoBar_Buttons_CurrentItems[buttonsIndex][index].items = newitemlist;
						table.insert(newranks,AutoBar_Buttons_CurrentItems[buttonsIndex][index]);
					end
				end
				if (table.maxn(newranks) == 0) then
					erase(AutoBar_Buttons_CurrentItems[buttonsIndex],10)
					AutoBar_Buttons_CurrentItems[buttonsIndex] = nil;
				else
					AutoBar_Buttons_CurrentItems[buttonsIndex] = newranks;
				end
			end
		end
	end
	local function AddItem(buttonsIndex, rank, bag, slot)
		if (AutoBar_Buttons_CurrentItems[buttonsIndex]) then
			local index, rec, findRank;
			for index, rec in pairs(AutoBar_Buttons_CurrentItems[buttonsIndex]) do
				if (rec.rank == rank) then
					findRank = index;
				end
			end
			if (findRank) then
				table.insert(AutoBar_Buttons_CurrentItems[buttonsIndex][findRank].items, new( bag, slot ) );
			else
				local t = new();
				t.rank = rank;
				t.items = new( new( bag, slot ) )
				table.insert(AutoBar_Buttons_CurrentItems[buttonsIndex], t);
			end
		else
			local t = new();
			t.rank = rank;
			t.items = new( new( bag, slot ) )
			AutoBar_Buttons_CurrentItems[buttonsIndex] = new(t);
		end
	end
	local function SortByRank(a,b)
		if (a and b and a.rank and b.rank) then
			return a.rank < b.rank;
		else
			return true;
		end
	end

local startTime = GetTime();
	local bag, slot, name, id, i;
	local minbag,maxbag = 0, 4;
	if (specificbag) then
		minbag = specificbag;
		maxbag = specificbag;
		ClearOutBag(specificbag);
	else
		erase(AutoBar_Buttons_CurrentItems,10);
	end
	-- AutoBar_Buttons_CurrentItems = {
	--	buttonsIndex = {
	--		index = {
	--			"rank" = ranknum,
	--			"items" = { {bag,slot}, {bag,slot}, {bag, slot} }
	--		},
	--	},
	--};
	for bag = minbag, maxbag, 1 do
		for slot = 1, GetContainerNumSlots(bag), 1 do
			name, id = AutoBar.LinkDecode(GetContainerItemLink(bag,slot));
--DEFAULT_CHAT_FRAME:AddMessage("name, id " .. tostring(name) .. "  " .. tostring(id));
			if (id and AutoBar_SearchedForItems[id]) then
				local button = AutoBar_SearchedForItems[id][1];
				local rank = AutoBar_ButtonItemList_Reversed[button][id];
				AddItem(button, rank, bag, slot)
				if (AutoBar_SearchedForItems[id][4]) then
					for i = 4, table.maxn(AutoBar_SearchedForItems[id]), 1 do
						button = AutoBar_SearchedForItems[id][i];
						rank = AutoBar_ButtonItemList_Reversed[button][id];
--DEFAULT_CHAT_FRAME:AddMessage("id: " .. tostring(name) .. "  " .. tostring(id) .. " -> button " .. tostring(button));
						AddItem(button, rank, bag, slot)
					end
				end
			end
		end
	end

	-- Scan equiped items
	bag = nil;
	for slot = 0, 19 do
		name, id = AutoBar.LinkDecode(GetInventoryItemLink("player", slot));
		if (id and AutoBar_SearchedForItems[id]) then
			local button = AutoBar_SearchedForItems[id][1];
			local rank = AutoBar_ButtonItemList_Reversed[button][id];
			AddItem(button, rank, bag, slot)
			if (AutoBar_SearchedForItems[id][4]) then
				for i = 4, table.maxn(AutoBar_SearchedForItems[id]), 1 do
					button = AutoBar_SearchedForItems[id][i];
					rank = AutoBar_ButtonItemList_Reversed[button][id];
					AddItem(button, rank, bag, slot)
				end
			end
		end
	end

	local buttonsIndex;
	for buttonsIndex = 1, AUTOBAR_MAXBUTTONS, 1 do
	 	if (AutoBar_Buttons_CurrentItems[buttonsIndex]) then
			table.sort(AutoBar_Buttons_CurrentItems[buttonsIndex], SortByRank);
	 	end
	end
	AutoBar:AssignButtons(AutoBar.display.rows * AutoBar.display.columns);
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar_ScanBags " .. tostring(GetTime() - startTime));

	-- If any alignment is center, do a LayoutUpdate
	if (AutoBar.display.alignButtons and math.fmod(AutoBar.display.alignButtons, 2)) then
		AutoBar:ButtonsUpdate();
	else
		AutoBar:LayoutUpdate()
	end
end


--	local numReagents = GetTradeSkillNumReagents(id);
--	local totalReagents = 0;
--	for i=1, numReagents, 1 do
--		local reagentName, reagentTexture, reagentCount, playerReagentCount = GetTradeSkillReagentInfo(id, i);
--		totalReagents = totalReagents + reagentCount;
--	end;

--local numReagents = GetTradeSkillNumReagents(id);
--for i=1, numReagents, 1 do
--	local reagentName, reagentTexture, reagentCount, playerReagentCount = GetTradeSkillReagentInfo(id, i);
--	SetItemButtonTexture(reagent, reagentTexture);
--	name:SetText(reagentName);
--	-- Grayout items
--	if ( playerReagentCount < reagentCount ) then
--		SetItemButtonTextureVertexColor(reagent, 0.5, 0.5, 0.5);
--		name:SetTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b);
--		creatable = nil;
--	end;
--end;

-- Return the display texture of the object
function AutoBar_GetTexture(id)
	if (not id) then
		return "";
	end

	-- Last item has priority so use its icon
	if (type(id) == "table" and id[1]) then
		id = id[table.maxn(id)];
	end

	if (id and AutoBar_Category_Info[id]) then
		if (AutoBar_Category_Info[id].texture) then
			return "Interface\\Icons\\"..AutoBar_Category_Info[id].texture;
		else
			id = AutoBar_Category_Info[id].items[table.maxn(AutoBar_Category_Info[id].items)];
		end
	end
	if (type(id)=="number" and id > 0) then
		local _,_,_,_,_,_,_,_,_,texture = GetItemInfo(tonumber(id));

		if (texture) then return texture; end
	end
	return "Interface\\Icons\\INV_Misc_Gift_01";
end


function AutoBar.ConfigChanged()
	AutoBar_BuildItemList();
	AutoBar_ScanBags();
	AutoBar:LayoutUpdate();
end


AutoBar.dockingFrames = {
	["NONE"] = {
		text = L["AUTOBAR_CONFIG_DOCKTONONE"],
		offset = { x = 0, y = 0, point = "CENTER", relative = "TOPLEFT" },
	},
	["BT3Bar1"] = {
		text = L["AUTOBAR_CONFIG_BT3BAR"]..1,
		offset = { x = 0, y = 0, point = "CENTER", relative = "TOPLEFT" },
	},
	["BT3Bar2"] = {
		text = L["AUTOBAR_CONFIG_BT3BAR"]..2,
		offset = { x = 0, y = 0, point = "CENTER", relative = "TOPLEFT" },
	},
	["BT3Bar3"] = {
		text = L["AUTOBAR_CONFIG_BT3BAR"]..3,
		offset = { x = 0, y = 0, point = "CENTER", relative = "TOPLEFT" },
	},
	["BT3Bar4"] = {
		text = L["AUTOBAR_CONFIG_BT3BAR"]..4,
		offset = { x = 0, y = 0, point = "CENTER", relative = "BOTTOMLEFT" },
	},
	["BT3Bar6"] = {
		text = L["AUTOBAR_CONFIG_BT3BAR"]..6,
		offset = { x = 0, y = 0, point = "CENTER", relative = "TOPLEFT" },
	},
	["BT3Bar10"] = {
		text = L["AUTOBAR_CONFIG_BT3BAR"]..10,
		offset = { x = 0, y = 0, point = "CENTER", relative = "TOPLEFT" },
	},
	["MainMenuBarArtFrame"] = {
		text = L["AUTOBAR_CONFIG_DOCKTOMAIN"],
		offset = { x = 0, y = 0, point = "CENTER", relative = "TOPRIGHT" },
	},
	["ChatFrame1"] = {
		text = L["AUTOBAR_CONFIG_DOCKTOCHATFRAME"],
		offset = { x = 0, y = 25, point = "CENTER", relative = "TOPLEFT" },
	},
	["ChatFrameMenuButton"] = {
		text = L["AUTOBAR_CONFIG_DOCKTOCHATFRAMEMENU"],
		offset = { x = 0, y = 25, point = "CENTER", relative = "TOPLEFT" },
	},
	["MainMenuBar"] = {
		text = L["AUTOBAR_CONFIG_DOCKTOACTIONBAR"],
		offset = { x = 7, y = 40, point = "CENTER", relative = "TOPLEFT" },
	},
	["CharacterMicroButton"] = {
		text = L["AUTOBAR_CONFIG_DOCKTOMENUBUTTONS"],
		offset = { x = 0, y = 0, point = "CENTER", relative = "BOTTOMLEFT" },
	},
};


function AutoBar:Test()
	DEFAULT_CHAT_FRAME:AddMessage(    "header state " .. tostring(_G["AutoBarSSHeaderFrame"]:GetAttribute("state")));
	for i = 1, AUTOBAR_MAXBUTTONS, 1 do
		button = AutoBar.ssButtons[i];
		s = AutoBar.ssPopupHeaders[i];
		DEFAULT_CHAT_FRAME:AddMessage("      button ".. i .. tostring(button:GetAttribute("state")).." s " .. tostring(s:GetAttribute("state")));

		local popupButtons = AutoBar.ssPopupButtons[i];
		local popupButton;
		local msg = ""
		for popupButtonIndex = 1, AUTOBAR_MAXPOPUPBUTTONS, 1 do
			popupButton = popupButtons[popupButtonIndex];--AutoBar.ssPopupButtons
			if (popupButton:GetAttribute("showstates") ~= "!*") then
				msg = msg .. tostring(popupButton:GetAttribute("state")).." ";
			end
		end
		DEFAULT_CHAT_FRAME:AddMessage(msg);
	end
end


local AUTOBAR_SHIFTSTATE = AUTOBAR_MAXBUTTONS; -- state offset when shift is held down
local AUTOBAR_SHIFT_ON = AUTOBAR_MAXBUTTONS * 2 + 1; -- ground state when shift is held down
local AUTOBAR_MAXBUTTONS_SHIFT = AUTOBAR_MAXBUTTONS * 2; -- ground state when shift is held down
local AUTOBAR_MIN_RANGE = "1-"..AUTOBAR_MAXBUTTONS;
local AUTOBAR_MAX_RANGE = (AUTOBAR_MAXBUTTONS+1).."-"..(AUTOBAR_MAXBUTTONS * 2);
local AUTOBAR_MAP_SHIFT_UP
local AUTOBAR_MAP_SHIFT_DOWN
local AUTOBAR_MAP_SHIFT_UP_ALL;
local AUTOBAR_MAP_SHIFT_DOWN_ALL;

function AutoBar:StateChanged(newState)
	DEFAULT_CHAT_FRAME:AddMessage(self:GetName().. ": " .. tostring(self:GetAttribute("state")));
end

-- Create the buttons and popups
function AutoBar:LayoutInitialize()
	local buttonWidth, buttonHeight, gapping = AutoBar:GetButtonSize();

	AUTOBAR_MAP_SHIFT_UP = ""
	AUTOBAR_MAP_SHIFT_DOWN = ""
	for i = 1, AUTOBAR_MAXBUTTONS, 1 do
		AUTOBAR_MAP_SHIFT_UP = AUTOBAR_MAP_SHIFT_UP..";"..i..":"..(i+AUTOBAR_SHIFTSTATE)
		AUTOBAR_MAP_SHIFT_DOWN = AUTOBAR_MAP_SHIFT_DOWN..";"..(i+AUTOBAR_SHIFTSTATE)..":"..i
	end
	AUTOBAR_MAP_SHIFT_UP_ALL = AUTOBAR_SHIFT_ON..":0"..AUTOBAR_MAP_SHIFT_UP
	AUTOBAR_MAP_SHIFT_DOWN_ALL = "0:"..AUTOBAR_SHIFT_ON..AUTOBAR_MAP_SHIFT_DOWN

--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:LayoutInitialize gapping " .. tostring(gapping) .. " / " .. tostring(buttonWidth) .. " / " .. tostring(buttonHeight), 1, 0.5, 0);

	-- The main driver.  It's draghandle child allows repositioning
	local p = _G["AutoBarSSHeaderFrame"];
	-- State 0 shows the slot buttons but no popups
	p:SetAttribute("statemap-anchor", "0-"..AUTOBAR_MAXBUTTONS..":$input");
	p:SetAttribute("delaystatemap-anchor", "0-"..AUTOBAR_MAXBUTTONS..":0");
	p:SetAttribute("delaytimemap-anchor", "0-"..AUTOBAR_MAXBUTTONS..":0.2");
	p:SetAttribute("delayhovermap-anchor", "0-"..AUTOBAR_MAXBUTTONS..":true");
	p:SetAttribute("state-popup", "0");
	p:SetAttribute("statemap-popup", "*:0");

--p.StateChanged = AutoBar.StateChanged;
	AutoBar.ssHeader = p;


local driver = _G["AutoBarSSDriverFrame"]; --CreateFrame("Frame", "StealthBarDriverFrame", UIParent, "SecureStateDriverTemplate")
--driver:SetPoint("CENTER",0,0) driver:SetWidth(2) driver:SetHeight(2)
-- in stealth, set to state 1, out of stealth go to state 1
--driver:SetAttribute("statemap-stealth-0", "0")
--driver:SetAttribute("statemap-stealth-1", "1")

--driver:SetAttribute("statemap-ctrl-0", "0")
--driver:SetAttribute("statemap-ctrl-1", AUTOBAR_SHIFT_ON)
--p = driver;
--p:SetAttribute("statemap-anchor", "0-"..AUTOBAR_SHIFT_ON..":$input");
--p:SetAttribute("delaystatemap-anchor", "0-"..AUTOBAR_MAXBUTTONS..":0");
--p:SetAttribute("delaytimemap-anchor", "0-"..AUTOBAR_MAXBUTTONS..":0.2");
--p:SetAttribute("delayhovermap-anchor", "0-"..AUTOBAR_MAXBUTTONS..":true");
--p:SetAttribute("ctrl-delaystatemap-anchor", AUTOBAR_MAX_RANGE..":"..AUTOBAR_SHIFT_ON);
--p:SetAttribute("ctrl-delaytimemap-anchor", AUTOBAR_MAX_RANGE..":0.2");
--p:SetAttribute("ctrl-delayhovermap-anchor", AUTOBAR_MAX_RANGE..":true");
--p:SetAttribute("state-popup", "0");
--p:SetAttribute("statemap-popup", "*:0");
--p:SetAttribute("ctrl-statemap-popup", "*:"..AUTOBAR_SHIFT_ON);

	-- Hook up AutoBarFrame components so we can do docking
	AutoBarFrame:SetAttribute("addchild", AutoBarSSHeaderFrame);
	AutoBarFrame:SetAttribute("addchild", AutoBarAnchorFrameHandle);
	AutoBarAnchorFrameHandle:SetAttribute("ofspoint", "*:CENTER");
	AutoBarAnchorFrameHandle:SetAttribute("ofsrelpoint", "*:CENTER");
	AutoBarAnchorFrameHandle:SetAttribute("ofsx", 0);
	AutoBarAnchorFrameHandle:SetAttribute("ofsy", 0);
	AutoBarSSHeaderFrame:SetAttribute("ofspoint", "*:CENTER");
	AutoBarSSHeaderFrame:SetAttribute("ofsrelpoint", "*:CENTER");
	AutoBarSSHeaderFrame:SetAttribute("ofsx", 0);
	AutoBarSSHeaderFrame:SetAttribute("ofsy", 0);

	-- Create the slot buttons
	AutoBar.ssButtons = {};
	for i = 1, AUTOBAR_MAXBUTTONS do
		local button = CreateFrame("CheckButton", "AutoBarSAB" .. i, p, "AutoBarSAButtonTemplate, SecureAnchorEnterTemplate");
		AutoBar.ssButtons[i] = button;

		button:SetAttribute("newstate","0:"..tostring(i)..";"..tostring(i)..":0")
		-- Set 0 second delay before returning to state 0 so keybindings dont open the popup
		button:SetAttribute("delaystate", "0:0")
		button:SetAttribute("delaytime", "0:0.1")
		button:SetAttribute("*childraise*", true);
		AutoBarButton:InitializeVisual(button);

		-- Support selfcast
		button:SetAttribute("checkselfcast", true);
--button:RegisterForDrag("LeftButton", "RightButton")
		p:SetAttribute("addchild", button);
	end

	-- Create the popup buttons and slave headers to control them
	AutoBar.ssPopupHeaders = {};
	AutoBar.ssPopupButtons = {};
	for i = 1, AUTOBAR_MAXBUTTONS do
		local popupHeader = CreateFrame("Frame", "AutoBarSSPopupHeaderFrame"..i, p, "SecureStateHeaderTemplate");
		popupHeader:SetWidth(2);
		popupHeader:SetHeight(2);
		AutoBar.ssPopupHeaders[i] = popupHeader;
		popupHeader:SetAttribute("statemap-anchor", "0-"..AUTOBAR_MAXBUTTONS..":$input");
		popupHeader:SetAttribute("delaystatemap-anchor", "0-"..AUTOBAR_MAXBUTTONS..":0");
		popupHeader:SetAttribute("delaytimemap-anchor", "0-"..AUTOBAR_MAXBUTTONS..":0.2");
		popupHeader:SetAttribute("delayhovermap-anchor", "0-"..AUTOBAR_MAXBUTTONS..":true");

		popupHeader:SetAttribute("state-parent", 0);
		popupHeader:SetAttribute("statemap-parent", "*:"..tostring(i));
popupHeader:SetAttribute("ctrl-statemap-parent", "*:"..tostring(i + AUTOBAR_SHIFTSTATE));
--		popupHeader:SetAttribute("statemap-parent", "1-"..tostring(i)..":"..tostring(i)..";0:0");
--popupHeader:SetAttribute("ctrl-statemap-parent", tostring(i + AUTOBAR_SHIFTSTATE)..":"..tostring(i + AUTOBAR_SHIFTSTATE));

		popupHeader:SetAttribute("exportstate", "popup");

--popupHeader.StateChanged = AutoBar.StateChanged;
		p:SetAttribute("addchild", popupHeader);

		popupHeader:SetAttribute("ofspoint", "*:CENTER");
		popupHeader:SetAttribute("ofsrelpoint", "*:CENTER");
		popupHeader:SetAttribute("ofsx", 0);
		popupHeader:SetAttribute("ofsy", 0);
		popupHeader:SetAttribute("state", 0);
		popupHeader:SetAttribute("showstates", tostring(i));
popupHeader:SetAttribute("showstates", tostring(i)..","..tostring(i + AUTOBAR_SHIFTSTATE));
		AutoBar.ssButtons[i]:SetAttribute("anchorchild", popupHeader);

		AutoBar.ssPopupButtons[i] = {};
		local popupButtons = AutoBar.ssPopupButtons[i];
		for popupButtonIndex = 1, AUTOBAR_MAXPOPUPBUTTONS do
			local popupButton = CreateFrame("CheckButton", "AutoBarSAB" .. i .. "P" .. popupButtonIndex, AutoBar.ssButtons[i], "AutoBarSAPopupButtonTemplate");
			popupButtons[popupButtonIndex] = popupButton;

			popupButton:SetAttribute("newstate", 0);
			popupButton:RegisterForClicks("LeftButtonUp", "RightButtonUp");
			AutoBarButton:InitializeVisual(popupButton);

			-- Support selfcast
			button:SetAttribute("checkselfcast", true);

			popupHeader:SetAttribute("addchild", popupButton);
		end
	end

	AutoBar.buttonLocations = {};
	for i = 1, AUTOBAR_MAXBUTTONS do
		AutoBar.buttonLocations[i] = {};
	end

--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:LayoutInitialize ssButtons " .. tostring(AutoBar.ssButtons) .. " / " .. tostring(AutoBar.ssButtons[1]));
end


-- Arrange the buttons using the various settings and alignment options
function AutoBar:LayoutUpdate()
	if (InCombatLockdown()) then
		return;
	end
	local rows = AutoBar.display.rows;
	local columns = AutoBar.display.columns;
	local buttonWidth, buttonHeight, gapping = AutoBar:GetButtonSize();
	local centerShiftX = 0;
	local centerShiftY = 0;
	local point = "BOTTOMLEFT";
	local x = buttonWidth + gapping;
	local y = buttonHeight + gapping;

	local displayedButtons = AutoBar:AssignButtons(rows * columns);
	local displayedColumns = math.min(displayedButtons, columns);
	local displayedRows = math.floor((displayedButtons - 1) / columns) + 1;
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:LayoutUpdate buttons " .. displayedButtons .. " columns " .. tostring(displayedColumns) .. " Rows " .. tostring(displayedRows));

	if (AutoBar.display.alignButtons == 1) then
		point = "BOTTOMLEFT";
	elseif (AutoBar.display.alignButtons == 2) then
		centerShiftX = -0.5 * displayedColumns * (buttonWidth + gapping) + gapping / 2;
		point = "BOTTOMLEFT";
	elseif (AutoBar.display.alignButtons == 3) then
		x = x * -1;
		point = "BOTTOMRIGHT";
	elseif (AutoBar.display.alignButtons == 4) then
		x = x * -1;
		point = "BOTTOMRIGHT";
		centerShiftY = -0.5 * displayedRows * (buttonHeight + gapping) + gapping / 2;
	elseif (AutoBar.display.alignButtons == 5) then
		point = "BOTTOMLEFT";
		centerShiftX = -0.5 * displayedColumns * (buttonWidth + gapping) + gapping / 2;
		centerShiftY = -0.5 * displayedRows * (buttonHeight + gapping) + gapping / 2;
	elseif (AutoBar.display.alignButtons == 6) then
		point = "BOTTOMLEFT";
		centerShiftY = -0.5 * displayedRows * (buttonHeight + gapping) + gapping / 2;
	elseif (AutoBar.display.alignButtons == 7) then
		x = x * -1;
		y = y * -1;
		point = "TOPRIGHT";
	elseif (AutoBar.display.alignButtons == 8) then
		y = y * -1;
		point = "TOPLEFT";
		centerShiftX = -0.5 * displayedColumns * (buttonWidth + gapping) + gapping / 2;
	elseif (AutoBar.display.alignButtons == 9) then
		y = y * -1;
		point = "TOPLEFT";
	end

	if (AutoBar.display.popupOnShift) then
AutoBar.ssHeader:SetAttribute("statemap-ctrl-0", AUTOBAR_MAP_SHIFT_UP)
AutoBar.ssHeader:SetAttribute("statemap-ctrl-1", AUTOBAR_MAP_SHIFT_DOWN)
	else
		AutoBar.ssHeader:SetAttribute("statemap-ctrl-0", nil)
		AutoBar.ssHeader:SetAttribute("statemap-ctrl-1", nil)
	end

	local i, button, effectiveButton, popupHeader;
	local displayButton = 1;
	for i = 1, AUTOBAR_MAXBUTTONS, 1 do
		button = AutoBar.ssButtons[i];

		AutoBarButton:SetVisual("AutoBarSAB"..i);
		AutoBar.ssPopupHeaders[i]:SetFrameStrata("DIALOG");

		-- Set the relative positions of the buttons
		AutoBar.buttonLocations[i].x = (math.fmod(i - 1, columns) * x + centerShiftX);
		AutoBar.buttonLocations[i].y = math.floor((i - 1) / columns) * y + centerShiftY;
		button:SetAttribute("ofspoint", "*:"..point);
		button:SetAttribute("ofsrelpoint", "*:".."CENTER");

		popupHeader = AutoBar.ssPopupHeaders[i];
		if (AutoBar.display.popupOnShift) then
			button:SetAttribute("newstate","0:"..tostring(i)..";"..tostring(i)..":0")
			button:SetAttribute("ctrl-newstate", AUTOBAR_SHIFTSTATE..":"..tostring(i + AUTOBAR_SHIFTSTATE)..";"..tostring(i + AUTOBAR_SHIFTSTATE)..":"..AUTOBAR_SHIFTSTATE)
--			button:SetAttribute("delaystate", "0:0")
			button:SetAttribute("ctrl-delaystate", "0:"..AUTOBAR_SHIFTSTATE)
			button:SetAttribute("childstate", "^" .. tostring(i));
			button:SetAttribute("ctrl-childstate", "^" .. tostring(i + AUTOBAR_SHIFTSTATE));

			popupHeader:SetAttribute("statemap-anchor", "0-"..AUTOBAR_SHIFT_ON..":$input");
			popupHeader:SetAttribute("delaystatemap-anchor", "0-"..AUTOBAR_MAXBUTTONS..":0");
			popupHeader:SetAttribute("ctrl-delaystatemap-anchor", AUTOBAR_MAX_RANGE..":AUTOBAR_SHIFT_ON");
			popupHeader:SetAttribute("delaytimemap-anchor", "0-"..AUTOBAR_SHIFT_ON..":0.2");
			popupHeader:SetAttribute("delayhovermap-anchor", "0-"..AUTOBAR_SHIFT_ON..":true");

			popupHeader:SetAttribute("state-parent", 0);
--			popupHeader:SetAttribute("statemap-parent", "*:"..tostring(i));
			popupHeader:SetAttribute("ctrl-statemap-parent", "*:"..tostring(i + AUTOBAR_SHIFTSTATE));
--
--			popupHeader:SetAttribute("statemap-ctrl-1", tostring(i)..":"..(tostring(i + AUTOBAR_SHIFTSTATE)));
--			popupHeader:SetAttribute("statemap-ctrl-0", (tostring(i + AUTOBAR_SHIFTSTATE))..":"..tostring(i));
		else
			button:SetAttribute("childstate", "^" .. tostring(i));
			popupHeader:SetAttribute("statemap-ctrl-1", nil);
			popupHeader:SetAttribute("statemap-ctrl-0", nil);
		end

		local popupButtons = AutoBar.ssPopupButtons[i];
		for popupButtonIndex = 1, AUTOBAR_MAXPOPUPBUTTONS do
			local popupButton = popupButtons[popupButtonIndex];

			popupButton:SetAttribute("ofspoint", "*:"..point);
			popupButton:SetAttribute("ofsrelpoint", "*:".."CENTER");
		end
	end

	AutoBar:ButtonsUpdate();
	AutoBar:UpdateAnchor(displayedButtons);
	AutoBar.ssHeader:SetAttribute("state", "0");
end


-- Handle anchor docking / positioning
function AutoBar:UpdateAnchor(displayedButtons)
	local rows = AutoBar.display.rows;
	local columns = AutoBar.display.columns;
	local buttonWidth, buttonHeight, gapping = AutoBar:GetButtonSize();
	local displayedColumns = math.min(displayedButtons, columns);
	local displayedRows = math.floor((displayedButtons - 1) / columns) + 1;
	local autoBarAnchorFrameHandle = _G["AutoBarAnchorFrameHandle"];

	autoBarAnchorFrameHandle:SetChecked(AutoBar.display.frameLocked);

	local dockShiftX = AutoBar.display.dockShiftX;
	local dockShiftY = AutoBar.display.dockShiftY;

	if (AutoBar.display.docking == "CharacterMicroButton") then
		AutoBarSSHeaderFrame:SetFrameStrata("HIGH");
		autoBarAnchorFrameHandle:SetFrameStrata("DIALOG");
	elseif (AutoBar.display.frameStrata) then
		AutoBarSSHeaderFrame:SetFrameStrata("HIGH");
		autoBarAnchorFrameHandle:SetFrameStrata("DIALOG");
	else
		AutoBarSSHeaderFrame:SetFrameStrata("LOW");
		autoBarAnchorFrameHandle:SetFrameStrata("MEDIUM");
	end

	if (AutoBar.display.docking and _G[AutoBar.display.docking]) then
		local offset = AutoBar.dockingFrames[AutoBar.display.docking].offset;
		AutoBarFrame:SetAttribute("headofspoint", "*:"..offset.point);
		AutoBarFrame:SetAttribute("headofsrelpoint", "*:"..offset.relative);
		AutoBarFrame:SetAttribute("headofsx", dockShiftX + offset.x);
		AutoBarFrame:SetAttribute("headofsy", dockShiftY + offset.y);
		AutoBarFrame:SetParent(_G[AutoBar.display.docking]);
	elseif (AutoBar.display.position) then
		AutoBarFrame:SetAttribute("headofspoint", "*:CENTER");
		AutoBarFrame:SetAttribute("headofsrelpoint", "*:screen");
		AutoBarFrame:SetAttribute("headofsx", AutoBar.display.position.x);
		AutoBarFrame:SetAttribute("headofsy", AutoBar.display.position.y);
		AutoBarFrame:SetParent("UIParent");
	else
		AutoBarFrame:SetAttribute("headofspoint", "*:CENTER");
		AutoBarFrame:SetAttribute("headofsrelpoint", "*:screen");
		AutoBarFrame:SetAttribute("headofsx", 300);
		AutoBarFrame:SetAttribute("headofsy", 300);
		AutoBarFrame:SetParent("UIParent");
	end
	AutoBarFrame:Show();
	AutoBarFrame:SetAttribute("state", 0);

	if (AutoBar.display.hideDragHandle) then
		autoBarAnchorFrameHandle:SetAttribute("showstates", "!*");
		autoBarAnchorFrameHandle:Hide();
	else
		autoBarAnchorFrameHandle:SetAttribute("showstates", "*");
		autoBarAnchorFrameHandle:Show();
	end
end


-- Assign content to the buttons, set count & keybinding texts, visibility, etc.
function AutoBar:ButtonsUpdate()
	if (not AutoBar.inWorld or InCombatLockdown()) then
		return
	end
--local startTime = GetTime();
	local buttonsIndex, i, button, rank, icon, countText, index, enabled, items, bag, slot, count, itemId, category, popupItemId;
	local displayButton = 1;		-- On Screen button, ignores empty slots
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:ButtonsUpdate start");
	for buttonsIndex = 1, AUTOBAR_MAXBUTTONS, 1 do
		local button = AutoBar.ssButtons[buttonsIndex];
		local showButton = button:GetAttribute("buttonsIndex");

		local popupButtons = AutoBar.ssPopupButtons[buttonsIndex];
		local popupButton;
		for popupButtonIndex = 1, AUTOBAR_MAXPOPUPBUTTONS, 1 do
			popupButton = popupButtons[popupButtonIndex];
			popupButton:SetAttribute("showstates", "!*");
		end

--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:ButtonsUpdate buttonsIndex " .. buttonsIndex);
		if (showButton) then
			bag, slot,_,_,_,_,index,enabled = AutoBar_Button_GetDisplayItem(buttonsIndex)
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:ButtonsUpdate buttonsIndex " .. buttonsIndex .. " bag, slot " .. tostring(bag) .. " " .. tostring(slot));
			if (bag or slot) then
				button:SetAttribute("showstates", "*");
				button.x = AutoBar.buttonLocations[displayButton].x;
				button.y = AutoBar.buttonLocations[displayButton].y;
				button:SetAttribute("ofsx", button.x);
				button:SetAttribute("ofsy", button.y);

				rank = AutoBar_Buttons_CurrentItems[buttonsIndex][index].rank;
				itemId = AutoBar_ButtonItemList[buttonsIndex][rank];
				count = GetItemCount(tonumber(itemId));

				category = AutoBar_SearchedForItems[itemId][2];
				AutoBarButton:SetAttributes(button, "AutoBarSAB"..buttonsIndex, bag, slot, count, itemId, category);

				-- The popup mostly displays the best to worst choice in order, mostly
				local popupButtonIndex, currentItems, buttonIndex, effectivePopup;
				local maxPopups = table.maxn(AutoBar_Buttons_CurrentItems[buttonsIndex]);
				local targetPopup = maxPopups;

				for popupButtonIndex = 1, maxPopups do
					if (popupButtonIndex > AUTOBAR_MAXPOPUPBUTTONS) then
						break;
					end
					currentItems = AutoBar_Buttons_CurrentItems[buttonsIndex][popupButtonIndex];
					rank = AutoBar_Buttons_CurrentItems[buttonsIndex][popupButtonIndex].rank;
					popupItemId = AutoBar_ButtonItemList[buttonsIndex][rank];
					count = GetItemCount(tonumber(popupItemId));

					if (itemId == popupItemId) then
						effectivePopup = 1;
					else
						effectivePopup = targetPopup;
						targetPopup = targetPopup - 1;
					end
--local name, _ = AutoBar.LinkDecode(GetContainerItemLink(currentItems.items[1][1], currentItems.items[1][2]));
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:ButtonsUpdate popupButtonIndex " .. tostring(popupButtonIndex) .. " effectivePopup " .. tostring(effectivePopup) .. " " .. tostring(name));
					popupButton = popupButtons[effectivePopup];
					category = AutoBar_SearchedForItems[popupItemId][2];
					AutoBarButton:SetAttributes(popupButton, "AutoBarSAB"..buttonsIndex.."P"..effectivePopup, currentItems.items[1][1], currentItems.items[1][2], count, popupItemId, category);
					popupButton:SetAttribute("buttonsIndex", buttonsIndex);
					popupButton:SetAttribute("popupButtonIndex", popupButtonIndex);

					if (AutoBar.display.popupOnShift) then
						if (effectivePopup == 1) then
--popupButton:SetAttribute("showstates", tostring(buttonsIndex));
							popupButton:SetAttribute("showstates", tostring(buttonsIndex)..","..(tostring(buttonsIndex + AUTOBAR_SHIFTSTATE)));
						else
							popupButton:SetAttribute("showstates", (tostring(buttonsIndex + AUTOBAR_SHIFTSTATE)));
						end
					else
						popupButton:SetAttribute("showstates", tostring(buttonsIndex));
					end

					AutoBarButton:SetVisual("AutoBarSAB"..buttonsIndex.."P"..effectivePopup);
				end

				-- Adjust popup positions based on their buttons position
				local buttonWidth, buttonHeight, gapping = AutoBar:GetButtonSize();
				for popupButtonIndex = 1, AUTOBAR_MAXPOPUPBUTTONS do
					local popupButton = popupButtons[popupButtonIndex];
					local buttonDistance = popupButtonIndex - 1;	-- Zero based so first button is on top of its parent

					-- Set the popup direction
					if (AutoBar.display.popupToBottom) then
						popupButton:SetAttribute("ofsx", "*:"..button.x);
						popupButton:SetAttribute("ofsy", "*:"..(button.y - buttonDistance * (buttonHeight + gapping)));
					elseif (AutoBar.display.popupToLeft) then
						popupButton:SetAttribute("ofsx", "*:"..button.x - buttonDistance * (buttonWidth + gapping));
						popupButton:SetAttribute("ofsy", "*:"..(button.y));
					elseif (AutoBar.display.popupToRight) then
						popupButton:SetAttribute("ofsx", "*:"..button.x + buttonDistance * (buttonWidth + gapping));
						popupButton:SetAttribute("ofsy", "*:"..(button.y));
					else
						popupButton:SetAttribute("ofsx", "*:"..button.x);
						popupButton:SetAttribute("ofsy", "*:"..(button.y + buttonDistance * (buttonHeight + gapping)));
					end
				end

				displayButton = displayButton + 1;
			else
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:ButtonsUpdate buttonsIndex " .. tostring(buttonsIndex).." spell "..tostring(AutoBar.buttons[buttonsIndex].castSpell));
				if (AutoBar.buttons[buttonsIndex] and (AutoBar.display.showCategoryIcon or AutoBar.buttons[buttonsIndex].castSpell)) then
					button:SetAttribute("showstates", "*");
					button.x = AutoBar.buttonLocations[displayButton].x;
					button.y = AutoBar.buttonLocations[displayButton].y;
					button:SetAttribute("ofsx", button.x);
					button:SetAttribute("ofsy", button.y);
				    -- Button is empty so show Category Icon
					local buttonInfo = AutoBar.buttons[buttonsIndex];
					if (buttonInfo and buttonInfo[1]) then
						category = buttonInfo[table.maxn(buttonInfo)];
						AutoBarButton:SetAttributes(button, "AutoBarSAB"..buttonsIndex, nil, nil, 0, nil, category);
						displayButton = displayButton + 1;
					else
						if (AutoBar.display.showEmptyButtons) then
							button:SetAttribute("showstates", "!*");
							displayButton = displayButton + 1;
						end
					end
				else
					button:SetAttribute("showstates", "!*");
				end
			end
		else
			button:SetAttribute("showstates", "!*");
		end
	end
	self:ButtonsEvent(AutoBarButton.UpdateUsable);
	AutoBar.ssHeader:SetAttribute("state", "0");
	SecureStateHeader_Refresh(AutoBar.ssHeader);
--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:ButtonsUpdate " .. tostring(GetTime() - startTime));
end


--
-- Drag Handle
--

-- Lock & Unlock the frame on left click, and toggle config dialog with right click
function AutoBar:ClickHandle(button)
	local function RelockActionBars()
		self.display.frameLocked = true;
		if (AutoBar.display.lockActionBars) then
			LOCK_ACTIONBAR = "1";
		end
		_G["AutoBarAnchorFrameHandle"]:SetChecked(true);
	end

	if (button == "RightButton") then
		AutoBarConfig_Toggle();
		this:SetChecked(AutoBar.display.frameLocked);
	elseif (button == "LeftButton") then
		AutoBar.display.frameLocked = not AutoBar.display.frameLocked;
		if (AutoBar.display.frameLocked) then
			if (AutoBar.display.lockActionBars) then
				LOCK_ACTIONBAR = "1";
			end
		else
			if (AutoBar.display.lockActionBars) then
				LOCK_ACTIONBAR = "0";
			end
			self:ScheduleEvent("AutoBarTemporaryUnlock", RelockActionBars, 30);
		end
		this:SetChecked(AutoBar.display.frameLocked);
	end
end


-- Start dragging if not locked
function AutoBar:DragStart()
	if (not AutoBar.display.frameLocked) then
		_G["AutoBarFrame"]:StartMoving();
	end
end


-- End dragging
function AutoBar:DragStop()
	_G["AutoBarFrame"]:StopMovingOrSizing();
	AutoBar.display.position = {};
	AutoBar.display.position.x,
	AutoBar.display.position.y = _G["AutoBarAnchorFrameHandle"]:GetCenter();
	AutoBar.display.docking = nil;
end

--DEFAULT_CHAT_FRAME:AddMessage("AutoBar:DragStop" .. frame:GetName() .. "x/y " .. tostring(AutoBar.display.position.x).. "/" ..tostring(AutoBar.display.position.y), 1, 0.5, 0);
