--[[ ItemRack 1.99 12/4/06 Gello ]]--

--[[ SavedVariables ]]--

ItemRack_Users = {} -- per-user bar settings (position, orientation, scale, locked status, bar contents)

ItemRack_Settings = {			-- These settings are for all users:
	TooltipFollow = "OFF",		-- whether tooltip shows at pointer or default position
	CooldownNumbers = "OFF",	-- whether cooldowns show a number overlay
	Soulbound = "OFF",			-- whether menu limits items to soulbound only
	Bindings = "OFF",			-- whether key bindings is displayed
	MenuShift = "OFF",			-- whether Shift key needs held to open the menu
	Notify = "OFF",				-- whether to notify when cooldowns finished on used items
	ShowEmpty = "ON",			-- whether to show empty slot in the menu
	FlipMenu = "OFF",			-- whether to display menu on the opposite side
	RightClick = "OFF",			-- whether right click sends to second slot
	TinyTooltip = "OFF",		-- whether to only display name, durability and cooldown in tooltip
	ShowTooltips = "ON",		-- whether to display tooltip at all
	RotateMenu = "OFF",			-- whether menu is rotated (temporary setting)
	ShowIcon = "ON",			-- whether to show the minimap button
	DisableToggle = "ON",		-- whether left-clicking disables the minimap button
	FlipBar = "OFF",			-- whether control appears on bottom or right bar grows other direction
	EnableEvents = "OFF",		-- whether automated event scrips run
	CompactList = "OFF",		-- whether saved sets list is compacted or not
	NotifyThirty = "ON",		-- whether notify happens at 30 seconds
	ShowAllEvents = "OFF",		-- whether to show all classes' events
	AllowHidden = "OFF",		-- whether to hide menu items with ALT+click
	LargeFont = "OFF",			-- whether event script font is large or small
	SquareMinimap = "OFF",		-- whether minimap button should go around square minimap
	BigCooldown = "OFF",		-- whether cooldown numbers are huge (like Cooldown Count)
	SetLabels = "ON",			-- whether labels show on set icons
	AutoToggle = "OFF",			-- whether sets automatically toggle when chosen
}

-- all event scripts are stored globally in this saved variable.  Defaults are in localization.lua
ItemRack_Events = {}

ItemRack_Version = 1.99

--[[ Local Variables ]]--

-- some mount textures share non-mount buff textures, if you run across one put it here
local problem_mounts = {
	["Interface\\Icons\\Ability_Mount_PinkTiger"] = 1,
	["Interface\\Icons\\Ability_Mount_WhiteTiger"] = 1,
	["Interface\\Icons\\Spell_Nature_Swiftness"] = 1,
	["Interface\\Icons\\INV_Misc_Foot_Kodo"] = 1,
	["Interface\\Icons\\Ability_Mount_JungleTiger"] =1,
}

local current_events_version = 1.99 -- use to control when to upgrade events

-- defaults for ItemRack_Users
local ItemRackOpt_Defaults = {
	MainOrient = "HORIZONTAL",  -- direction of main bar, "HORIZONTAL" or "VERTICAL", menu orient always opposite
	MainScale = 1,				-- scale 0-1 of main bar and menu bar
	XPos = 400,					-- left position of main bar
	YPos = 350,					-- top position of main bar
	Locked = "OFF",				-- lock status of main bar (for all intents menu always locked)
	Visible = "ON"				-- whether the bar should be drawn on the screen
}

local user = "default"  -- "Gello of Hyjal" or "Gello of Deathwing ", defined at PLAYER_LOGIN

ItemRack = {}

ItemRack.FrameToScale = nil -- holds frame being scaled for ScaleUpdate

ItemRack.BaggedItems = {} -- table containing items in bags to show in menu
ItemRack.NumberOfItems = 0 -- number of items in the menu
ItemRack.MaxItems = 30 -- maximum number of items that can display in the menu
ItemRack.InvOpen = nil -- which inventory slot has its menu open

ItemRack.TooltipOwner = nil -- (this) when tooltip created
ItemRack.TooltipType = nil -- "BAG" or "INVENTORY"
ItemRack.TooltipBag = nil -- bag number
ItemRack.TooltipSlot = nil -- bag or inventory slot number

ItemRack.CurrentTime = GetTime()

ItemRack.MainDock = "" -- "TOPLEFT" "BOTTOMRIGHT" etc
ItemRack.MenuDock = ""

ItemRack.Queue = {} -- [slot]="ItemName" if an item in queue, ie [13]="Arcanite Dragonling", [slot]=nil for no item in queue
ItemRack.NotifyList = {} -- ["item name"] = { bag=0-4, slot=1-x, inv=0-19, hadcooldown=true/nil }
ItemRack.AmmoCounts = {} -- ["Heavy Shot"]=200, ["Thorium Arrow"]=982, etc
ItemRack.TrinketsPaired = false -- true if the two trinkets are next to each other

ItemRack.KeyBindingsSettled = false

ItemRack.MenuDockedTo = nil -- "SET" for set window, "CHARACTERSHEET" for PaperDollFrame, nil for rack

ItemRack.SelectedEvent = 0 -- index in list of event selected

ItemRack.CanWearOneHandOffHand = nil -- whether player can wear one-hand in offhand (warrior, rogue, hunter)

ItemRack.Buffs = {} -- table indexed by buff names, whether a buff is on or not
ItemRack.BankedItems = {} -- items in the bank indexed by itemID
ItemRack.BankSlots = { -1,5,6,7,8,9,10 }

local eventList = {} -- note the departure from using the table for local values -- mod will be rewritten to a consistent style
local eventListSize = 1

local scratchTable = { {}, {} } -- for secondary sorts
local scratchTableSize = { 1, 1 }

--[[ reference tables ]]--

ItemRack.OptInfo = {
	["ItemRack_Control_Rotate"] = { text=ItemRackText.CONTROL_ROTATE_TEXT, tooltip=ItemRackText.CONTROL_ROTATE_TOOLTIP },
	["ItemRack_Control_Lock"] = { text=ItemRackText.CONTROL_LOCK_TEXT, tooltip=ItemRackText.CONTROL_LOCK_TOOLTIP },
	["ItemRack_Control_Options"] = { text=ItemRackText.CONTROL_OPTIONS_TEXT, tooltip=ItemRackText.CONTROL_OPTIONS_TOOLTIP },
	["ItemRack_Opt_TooltipFollow"] = { text=ItemRackText.OPT_TOOLTIPFOLLOW_TEXT, tooltip=ItemRackText.OPT_TOOLTIPFOLLOW_TOOLTIP, type="Check", info="TooltipFollow" },
	["ItemRack_Opt_CooldownNumbers"] = { text=ItemRackText.OPT_COOLDOWNNUMBERS_TEXT, tooltip=ItemRackText.OPT_COOLDOWNNUMBERS_TOOLTIP, type="Check", info="CooldownNumbers" },
	["ItemRack_Opt_Soulbound"] = { text=ItemRackText.OPT_SOULBOUND_TEXT, tooltip=ItemRackText.OPT_SOULBOUND_TOOLTIP, type="Check", info="Soulbound" },
	["ItemRack_Opt_Bindings"] = { text=ItemRackText.OPT_BINDINGS_TEXT, tooltip=ItemRackText.OPT_BINDINGS_TOOLTIP, type="Check", info="Bindings" },
	["ItemRack_Opt_MenuShift"] = { text=ItemRackText.OPT_MENUSHIFT_TEXT, tooltip=ItemRackText.OPT_MENUSHIFT_TOOLTIP, type="Check", info="MenuShift" },
	["ItemRack_Opt_Close"] = { text=ItemRackText.OPT_CLOSE_TEXT, tooltip=ItemRackText.OPT_CLOSE_TOOLTIP },
	["ItemRack_InvFrame_Resize"] = { text=ItemRackText.INVFRAME_RESIZE_TEXT, tooltip=ItemRackText.INVFRAME_RESIZE_TOOLTIP },
	["ItemRack_Opt_ShowEmpty"] = { text=ItemRackText.OPT_SHOWEMPTY_TEXT, tooltip=ItemRackText.OPT_SHOWEMPTY_TOOLTIP, type="Check", info="ShowEmpty" },
	["ItemRack_Opt_FlipMenu"] = { text=ItemRackText.OPT_FLIPMENU_TEXT, tooltip=ItemRackText.OPT_FLIPMENU_TOOLTIP, type="Check", info="FlipMenu" },
	["ItemRack_Opt_RightClick"] = { text=ItemRackText.OPT_RIGHTCLICK_TEXT, tooltip=ItemRackText.OPT_RIGHTCLICK_TOOLTIP, type="Check", info="RightClick" },
	["ItemRack_Opt_TinyTooltip"] = { text=ItemRackText.OPT_TINYTOOLTIP_TEXT, tooltip=ItemRackText.OPT_TINYTOOLTIP_TOOLTIP, type="Check", info="TinyTooltip" },
	["ItemRack_Opt_ShowTooltips"] = { text=ItemRackText.OPT_SHOWTOOLTIPS_TEXT, tooltip=ItemRackText.OPT_SHOWTOOLTIPS_TOOLTIP, type="Check", info="ShowTooltips" },
	["ItemRack_Opt_Notify"] = { text=ItemRackText.OPT_NOTIFY_TEXT, tooltip=ItemRackText.OPT_NOTIFY_TOOLTIP, type="Check", info="Notify" },
	["ItemRack_Opt_RotateMenu"] = { text=ItemRackText.OPT_ROTATEMENU_TEXT, tooltip=ItemRackText.OPT_ROTATEMENU_TOOLTIP, type="Check", info="RotateMenu" },
	["ItemRack_Sets_Close"] = { text=ItemRackText.SETS_CLOSE_TEXT, tooltip=ItemRackText.SETS_CLOSE_TOOLTIP },
	["ItemRack_Sets_NameLabel"] = { text=ItemRackText.SETS_NAMELABEL_TEXT, type="Label" },
	["ItemRack_Sets_HideSet"] = { text=ItemRackText.SETS_HIDESET_TEXT, tooltip=ItemRackText.SETS_HIDESET_TOOLTIP, type="Check" },
	["ItemRack_Sets_Tab1"] = { text=ItemRackText.SETS_TAB1_TEXT, tooltip=ItemRackText.SETS_TAB1_TOOLTIP, type="Tab" },
	["ItemRack_Sets_Tab2"] = { text=ItemRackText.SETS_TAB2_TEXT, tooltip=ItemRackText.SETS_TAB2_TOOLTIP, type="Tab" },
	["ItemRack_Sets_Tab3"] = { text=ItemRackText.SETS_TAB3_TEXT, tooltip=ItemRackText.SETS_TAB3_TOOLTIP, type="Tab" },
	["ItemRack_Opt_ShowIcon"] = { text=ItemRackText.OPT_SHOWICON_TEXT, tooltip=ItemRackText.OPT_SHOWICON_TOOLTIP, type="Check", info="ShowIcon" },
	["ItemRack_Opt_DisableToggle"] = { text=ItemRackText.OPT_DISABLETOGGLE_TEXT, tooltip=ItemRackText.OPT_DISABLETOGGLE_TOOLTIP, type="Check", info="DisableToggle" },
	["ItemRack_Sets_Lock"] = { text=ItemRackText.CONTROL_LOCK_TEXT, tooltip=ItemRackText.CONTROL_LOCK_TOOLTIP },
	["ItemRack_Sets_BindButton"] = { text=ItemRackText.SETS_BINDBUTTON_TEXT, tooltip=ItemRackText.SETS_BINDBUTTON_TOOLTIP, type="Label" },
	["ItemRack_Sets_SaveButton"] = { text=ItemRackText.SETS_SAVEBUTTON_TEXT, tooltip=ItemRackText.SETS_SAVEBUTTON_TOOLTIP, type="Label" },
	["ItemRack_Sets_RemoveButton"] = { text=ItemRackText.SETS_REMOVEBUTTON_TEXT, tooltip=ItemRackText.SETS_REMOVEBUTTON_TOOLTIP, type="Label" },
	["ItemRack_Opt_FlipBar"] = { text=ItemRackText.OPT_FLIPBAR_TEXT, tooltip=ItemRackText.OPT_FLIPBAR_TOOLTIP, type="Check", info="FlipBar" },
	["ItemRack_Opt_EnableEvents"] = { text=ItemRackText.OPT_ENABLEEVENTS_TEXT, tooltip=ItemRackText.OPT_ENABLEEVENTS_TOOLTIP, type="Check", info="EnableEvents" },
	["ItemRack_Opt_CompactList"] = { text=ItemRackText.OPT_COMPACTLIST_TEXT, tooltip=ItemRackText.OPT_COMPACTLIST_TOOLTIP, type="Check", info="CompactList" },
	["ItemRack_SavedSets_Close"] = { text=ItemRackText.OPT_SAVEDSETSCLOSE_TEXT, tooltip=ItemRackText.OPT_SAVEDSETSCLOSE_TOOLTIP },
	["ItemRack_Events_DeleteButton"] = { text=ItemRackText.EVENTSDELETE_TEXT, tooltip=ItemRackText.EVENTSDELETE_TOOLTIP },
	["ItemRack_Events_EditButton"] = { text=ItemRackText.EVENTSEDIT_TEXT, tooltip=ItemRackText.EVENTSEDIT_TOOLTIP },
	["ItemRack_Events_NewButton"] = { text=ItemRackText.EVENTSNEW_TEXT, tooltip=ItemRackText.EVENTSNEW_TOOLTIP },
	["ItemRack_EditEvent_Save"] = { text=ItemRackText.EVENTSSAVE_TEXT, tooltip=ItemRackText.EVENTSSAVE_TOOLTIP },
	["ItemRack_EditEvent_Test"] = { text=ItemRackText.EVENTSTEST_TEXT, tooltip=ItemRackText.EVENTSTEST_TOOLTIP },
	["ItemRack_EditEvent_Cancel"] = { text=ItemRackText.EVENTSCANCEL_TEXT, tooltip=ItemRackText.EVENTSCANCEL_TOOLTIP },
	["ItemRack_EventName"] = { text=ItemRackText.EVENTNAME_TEXT, tooltip=ItemRackText.EVENTNAME_TOOLTIP },
	["ItemRack_EventTrigger"] = { text=ItemRackText.EVENTTRIGGER_TEXT, tooltip=ItemRackText.EVENTTRIGGER_TOOLTIP },
	["ItemRack_EventDelay"] = { text=ItemRackText.EVENTDELAY_TEXT, tooltip=ItemRackText.EVENTDELAY_TOOLTIP },
	["ItemRack_Opt_NotifyThirty"] = { text=ItemRackText.OPT_NOTIFYTHIRTY_TEXT, tooltip=ItemRackText.OPT_NOTIFYTHIRTY_TOOLTIP, type="Check", info="NotifyThirty" },
	["ItemRack_Opt_ShowAllEvents"] = { text=ItemRackText.OPT_SHOWALLEVENTS_TEXT, tooltip=ItemRackText.OPT_SHOWALLEVENTS_TOOLTIP, type="Check", info="ShowAllEvents" },
	["ItemRack_ResetButton"] = { text=ItemRackText.RESETBUTTON_TEXT, tooltip=ItemRackText.RESETBUTTON_TOOLTIP },
	["ItemRack_Opt_AllowHidden"] = { text=ItemRackText.OPT_ALLOWHIDDEN_TEXT, tooltip=ItemRackText.OPT_ALLOWHIDDEN_TOOLTIP, type="Check", info="AllowHidden" },
	["ItemRack_Opt_LargeFont"] = { text=ItemRackText.OPT_LARGEFONT_TEXT, tooltip=ItemRackText.OPT_LARGEFONT_TOOLTIP, type="Check", info="LargeFont" },
	["ItemRack_Opt_SquareMinimap"] = { text=ItemRackText.OPT_SQUAREMINIMAP_TEXT, tooltip=ItemRackText.OPT_SQUAREMINIMAP_TOOLTIP, info="SquareMinimap" },
	["ItemRack_Opt_BigCooldown"] = { text=ItemRackText.OPT_BIGCOOLDOWN_TEXT, tooltip=ItemRackText.OPT_BIGCOOLDOWN_TOOLTIP, info="BigCooldown" },
	["ItemRack_ShowHelmText"] = { text="Helm", type="Label" },
	["ItemRack_ShowCloakText" ] = { text="Cloak", type="Label" },
	["ItemRack_Opt_SetLabels"] = { text=ItemRackText.OPT_SETLABELS_TEXT, tooltip=ItemRackText.OPT_SETLABELS_TOOLTIP, info="SetLabels" },
	["ItemRack_Opt_AutoToggle"] = { text=ItemRackText.OPT_AUTOTOGGLE_TEXT, tooltip=ItemRackText.OPT_AUTOTOGGLE_TOOLTIP, info="AutoToggle" },
	["ItemRack_BindSlotsButton"] = { text=ItemRackText.BINDSLOTS_TEXT, tooltip=ItemRackText.BINDSLOTS_TOOLTIP }
}

-- numerically indexed list of options for scrollable options window
ItemRack.OptScroll = {
	{ idx="ItemRack_Opt_ShowTooltips" },
	{ idx="ItemRack_Opt_TooltipFollow", dependency="ItemRack_Opt_ShowTooltips" },
	{ idx="ItemRack_Opt_TinyTooltip", dependency="ItemRack_Opt_ShowTooltips" },
	{ idx="ItemRack_Opt_ShowIcon" },
	{ idx="ItemRack_Opt_DisableToggle", dependency="ItemRack_Opt_ShowIcon" },
	{ idx="ItemRack_Opt_SquareMinimap", dependency="ItemRack_Opt_ShowIcon" },
	{ idx="ItemRack_Opt_Bindings" },
	{ idx="ItemRack_Opt_SetLabels" },
	{ idx="ItemRack_Opt_CooldownNumbers" },
	{ idx="ItemRack_Opt_BigCooldown", dependency="ItemRack_Opt_CooldownNumbers" },
	{ idx="ItemRack_Opt_Notify" },
	{ idx="ItemRack_Opt_NotifyThirty", dependency="ItemRack_Opt_Notify" },
	{ idx="ItemRack_Opt_MenuShift" },
	{ idx="ItemRack_Opt_AutoToggle" },
	{ idx="ItemRack_Opt_ShowEmpty" },
	{ idx="ItemRack_Opt_AllowHidden" },
	{ idx="ItemRack_Opt_Soulbound" },
	{ idx="ItemRack_Opt_RightClick" },
	{ idx="ItemRack_Opt_FlipMenu" },
	{ idx="ItemRack_Opt_RotateMenu" },
	{ idx="ItemRack_Opt_FlipBar" }
}

-- paperdoll_slot=frame name of the slot on the paperdoll frame (for alt+click purposes)
-- ["SlotName"]=1 for each allowable slot
-- swappable=1 for slots that can be swapped in combat
-- ignore_soulbound = ignore soulbound flag for this slot
ItemRack.Indexes = {
	[0] = { name=AMMOSLOT, paperdoll_slot="CharacterAmmoSlot", keybind="Use Ammo Item", ignore_soulbound=1, swappable=1, INVTYPE_AMMO=1 },
	[1] = { name=INVTYPE_HEAD, paperdoll_slot="CharacterHeadSlot", keybind="Use Head Item", INVTYPE_HEAD=1 },
	[2] = { name=INVTYPE_NECK, paperdoll_slot="CharacterNeckSlot", keybind="Use Neck Item", INVTYPE_NECK=1 },
	[3] = { name=INVTYPE_SHOULDER, paperdoll_slot="CharacterShoulderSlot", keybind="Use Shoulder Item", INVTYPE_SHOULDER=1 },
	[4] = { name=INVTYPE_BODY, paperdoll_slot="CharacterShirtSlot", keybind="Use Shirt Item", ignore_soulbound=1, INVTYPE_BODY=1 },
	[5] = { name=INVTYPE_CHEST, paperdoll_slot="CharacterChestSlot", keybind="Use Chest Item", INVTYPE_CHEST=1, INVTYPE_ROBE=1 },
	[6] = { name=INVTYPE_WAIST, paperdoll_slot="CharacterWaistSlot", keybind="Use Waist Item", INVTYPE_WAIST=1 },
	[7] = { name=INVTYPE_LEGS, paperdoll_slot="CharacterLegsSlot", keybind="Use Legs Item", INVTYPE_LEGS=1 },
	[8] = { name=INVTYPE_FEET, paperdoll_slot="CharacterFeetSlot", keybind="Use Feet Item", INVTYPE_FEET=1 },
	[9] = { name=INVTYPE_WRIST, paperdoll_slot="CharacterWristSlot", keybind="Use Wrist Item", INVTYPE_WRIST=1 },
	[10]= { name=INVTYPE_HAND, paperdoll_slot="CharacterHandsSlot", keybind="Use Hands Item", INVTYPE_HAND=1 },
	[11]= { name=INVTYPE_FINGER, paperdoll_slot="CharacterFinger0Slot", keybind="Use Top Finger Item", INVTYPE_FINGER=1 },
	[12]= { name=INVTYPE_FINGER, paperdoll_slot="CharacterFinger1Slot", keybind="Use Bottom Finger Item", INVTYPE_FINGER=1 },
	[13]= { name=INVTYPE_TRINKET, paperdoll_slot="CharacterTrinket0Slot", ignore_soulbound=1, keybind="Use Top Trinket Item", INVTYPE_TRINKET=1 },
	[14]= { name=INVTYPE_TRINKET, paperdoll_slot="CharacterTrinket1Slot", ignore_soulbound=1, keybind="Use Bottom Trinket Item", INVTYPE_TRINKET=1 },
	[15]= { name=INVTYPE_CLOAK, paperdoll_slot="CharacterBackSlot", keybind="Use Back Item", INVTYPE_CLOAK=1 },
	[16]= { name=INVTYPE_WEAPONMAINHAND, paperdoll_slot="CharacterMainHandSlot", keybind="Use Main-Hand Item", swappable=1, INVTYPE_WEAPONMAINHAND=1, INVTYPE_2HWEAPON=1, INVTYPE_WEAPON=1 },
	[17]= { name=INVTYPE_WEAPONOFFHAND, paperdoll_slot="CharacterSecondaryHandSlot", keybind="Use Off-Hand Item", swappable=1, INVTYPE_WEAPONOFFHAND=1, INVTYPE_SHIELD=1, INVTYPE_HOLDABLE=1, INVTYPE_WEAPON=1 },
	[18]= { name=INVTYPE_RANGED, paperdoll_slot="CharacterRangedSlot", keybind="Use Range Item", swappable=1, INVTYPE_RANGED=1, INVTYPE_RANGEDRIGHT=1, INVTYPE_THROWN=1, INVTYPE_RELIC=1 },
	[19]= { name=INVTYPE_TABARD, paperdoll_slot="CharacterTabardSlot", keybind="Use Tabard Item", ignore_soulbound=1, INVTYPE_TABARD=1 }
}

-- "add" or "remove" a frame from UISpecialFrames
local function make_escable(frame,add)
	local found
	for i in pairs(UISpecialFrames) do
		if UISpecialFrames[i]==frame then
			found = i
		end
	end
	if not found and add=="add" then
		table.insert(UISpecialFrames,frame)
	elseif found and add=="remove" then
		table.remove(UISpecialFrames,found)
	end
end

local _,_,durability_pattern = string.find(DURABILITY_TEMPLATE,"(.+) .+/.+")
durability_pattern = durability_pattern or ""

-- dock-dependant offset and directions: MainDock..MenuDock
-- x/yoff   = offset MenuFrame is positioned to InvFrame
-- x/ydir   = direction items are added to menu
-- x/ystart = starting offset when building a menu, relativePoint MenuDock
-- mx/y     = offset MenuFrame is positioned to contents of InvFrame
local dock_stats = { ["TOPRIGHTTOPLEFT"] =		 { xoff=-4, yoff=0,  xdir=1,  ydir=-1, xstart=8,   ystart=-8,  mx=3,  my=8 },
					 ["BOTTOMRIGHTBOTTOMLEFT"] = { xoff=-4, yoff=0,  xdir=1,  ydir=1,  xstart=8,   ystart=44,  mx=3,  my=-8 },
					 ["TOPLEFTTOPRIGHT"] =		 { xoff=4,  yoff=0,  xdir=-1, ydir=-1, xstart=-44, ystart=-8,  mx=-2, my=8 },
					 ["BOTTOMLEFTBOTTOMRIGHT"] = { xoff=4,  yoff=0,  xdir=-1, ydir=1,  xstart=-44, ystart=44,  mx=-2, my=-8 },
					 ["TOPRIGHTBOTTOMRIGHT"] =   { xoff=0,  yoff=-4, xdir=-1, ydir=1,  xstart=-44,  ystart=44, mx=8,  my=3 },
					 ["BOTTOMRIGHTTOPRIGHT"] =   { xoff=0,  yoff=4,	 xdir=-1, ydir=-1, xstart=-44,  ystart=-8, mx=8,  my=-3 },
					 ["TOPLEFTBOTTOMLEFT"] =	 { xoff=0,  yoff=-4, xdir=1,  ydir=1,  xstart=8,   ystart=44,  mx=-8, my=2 },
					 ["BOTTOMLEFTTOPLEFT"] =	 { xoff=0,  yoff=4,  xdir=1,  ydir=-1, xstart=8,   ystart=-8,  mx=-8, my=-2 } }

-- returns info depending on current docking. ie: dock_info("xoff")
local function dock_info(which)

	local anchor = ItemRack.MainDock..ItemRack.MenuDock

	if dock_stats[anchor] and which and dock_stats[anchor][which] then
		return dock_stats[anchor][which]
	else
		return 0
	end
end

-- returns info depending on where the window is currently
-- since frame scales and positions can be nil at the most inconvenient times, it approximates
-- its corner based on settings and makes no assumptions that even UIParent exists
-- no argument : return corner window is in
-- "LEFTRIGHT" : return "LEFT" or "RIGHT"
-- "TOPBOTTOM" : return "TOP" or "BOTTOM"
local function corner_info(which)

	local length,cx,cy,xpoint,ypoint
	local vertside,horzside = "TOP","LEFT"
	local info

	if table.getn(ItemRack_Users[user].Bar)>0 then
		length = table.getn(ItemRack_Users[user].Bar)*40 + 32
		if ItemRack_Users[user].MainOrient=="HORIZONTAL" then
			cx = length
			cy = 55
		else
			cx = 55
			cy = length
		end
		xpoint = cx/2+(ItemRack_Users[user].XPos*ItemRack_Users[user].MainScale)
		ypoint = (ItemRack_Users[user].YPos*ItemRack_Users[user].MainScale)-cy/2

		if xpoint<(UIParent and UIParent:GetWidth()/2 or 512) then
			horzside = "LEFT"
		else
			horzside = "RIGHT"
		end

		if ypoint<(UIParent and UIParent:GetHeight()/2 or 386) then
			vertside = "BOTTOM"
		else
			vertside = "TOP"
		end
	end

	if which=="LEFTRIGHT" then
		info = horzside
	elseif which=="TOPBOTTOM" then
		info = vertside
	else
		info = vertside..horzside
	end

	return info

end

local inv_dock = {
	[0]  = { orient="HORIZONTAL", maindock="BOTTOMLEFT", menudock="TOPLEFT" },
	[1]  = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" },
	[2]  = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" },
	[3]  = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" },
	[4]  = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" },
	[5]  = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" },
	[6]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
	[7]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
	[8]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
	[9]  = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" },
	[10]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
	[11]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
	[12]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
	[13]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
	[14]  = { orient="VERTICAL", maindock="TOPRIGHT", menudock="TOPLEFT" },
	[15] = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" },
	[16] = { orient="HORIZONTAL", maindock="BOTTOMLEFT", menudock="TOPLEFT" },
	[17] = { orient="HORIZONTAL", maindock="BOTTOMLEFT", menudock="TOPLEFT" },
	[18] = { orient="HORIZONTAL", maindock="BOTTOMLEFT", menudock="TOPLEFT" },
	[19] = { orient="VERTICAL", maindock="TOPLEFT", menudock="TOPRIGHT" }
}

-- places the menu against the invslot
-- setframe = true when docking to set frame
function ItemRack_DockMenu(invslot,relativeTo)

	local attachTo = ((not relativeTo) and "ItemRackInv"..invslot) or (relativeTo=="TITAN" and "TitanPanelItemRackButton") or (relativeTo=="SET" and "ItemRack_Sets_Inv"..invslot) or (relativeTo=="MINIMAP" and "ItemRack_IconFrame") or ItemRack.Indexes[invslot].paperdoll_slot
	local item = getglobal(attachTo)
	local noflip = ItemRack_Settings.FlipMenu=="OFF"
	local corner=corner_info()
	local mainorient = ItemRack_Users[user].MainOrient
	local ynudge -- amount if any to nudge the y offset

	if relativeTo then
		ItemRack.MenuDockedTo = relativeTo
	end

	if relativeTo=="MINIMAP" then
		if (ItemRack_IconFrame:GetTop() or 0)<(UIParent:GetHeight() or 0)/2 then
			ItemRack.MainDock = "TOPLEFT"
			ItemRack.MenuDock = "BOTTOMLEFT"
			ynudge = -12
		else
			ItemRack.MainDock = "BOTTOMRIGHT"
			ItemRack.MenuDock = "TOPRIGHT"
			ynudge = 12
		end
	elseif relativeTo=="TITAN" then
		local xpos, ypos = GetCursorPosition()
		if ypos<400 then
			ItemRack.MainDock = "TOPLEFT"
			ItemRack.MenuDock = "BOTTOMLEFT"
			ynudge = 0
		else
			ItemRack.MainDock = "BOTTOMLEFT"
			ItemRack.MenuDock = "TOPLEFT"
			ynudge = 0
		end
	elseif mainorient=="HORIZONTAL" then
		if corner=="BOTTOMLEFT" then
			ItemRack.MainDock = noflip and "TOPLEFT" or "BOTTOMLEFT"
			ItemRack.MenuDock = noflip and "BOTTOMLEFT" or "TOPLEFT"
		elseif corner=="BOTTOMRIGHT" then
			ItemRack.MainDock = noflip and "TOPRIGHT" or "BOTTOMRIGHT"
			ItemRack.MenuDock = noflip and "BOTTOMRIGHT" or "TOPRIGHT"
		elseif corner=="TOPLEFT" then
			ItemRack.MainDock = noflip and "BOTTOMLEFT" or "TOPLEFT"
			ItemRack.MenuDock = noflip and "TOPLEFT" or "BOTTOMLEFT"
		else -- "TOPRIGHT"
			ItemRack.MainDock = noflip and "BOTTOMRIGHT" or "TOPRIGHT"
			ItemRack.MenuDock = noflip and "TOPRIGHT" or "BOTTOMRIGHT"
		end
	else
		if corner=="BOTTOMLEFT" then
			ItemRack.MainDock = noflip and "BOTTOMRIGHT" or "BOTTOMLEFT"
			ItemRack.MenuDock = noflip and "BOTTOMLEFT" or "BOTTOMRIGHT"
		elseif corner=="BOTTOMRIGHT" then
			ItemRack.MainDock = noflip and "BOTTOMLEFT" or "BOTTOMRIGHT"
			ItemRack.MenuDock = noflip and "BOTTOMRIGHT" or "BOTTOMLEFT"
		elseif corner=="TOPLEFT" then
			ItemRack.MainDock = noflip and "TOPRIGHT" or "TOPLEFT"
			ItemRack.MenuDock = noflip and "TOPLEFT" or "TOPRIGHT"
		else -- "TOPRIGHT"
			ItemRack.MainDock = noflip and "TOPLEFT" or "TOPRIGHT"
			ItemRack.MenuDock = noflip and "TOPRIGHT" or "TOPLEFT"
		end
	end

	if relativeTo=="SET" then
		ItemRack_MenuFrame:SetScale(ItemRack_SetsFrame:GetScale())
		ItemRack.MainDock = inv_dock[invslot].maindock
		ItemRack.MenuDock = inv_dock[invslot].menudock
	elseif relativeTo=="CHARACTERSHEET" then
		ItemRack_MenuFrame:SetScale(getglobal(ItemRack.Indexes[1].paperdoll_slot):GetScale())
		ItemRack.MainDock = inv_dock[invslot].maindock
		ItemRack.MenuDock = inv_dock[invslot].menudock
		if ItemRack.MainDock == "TOPLEFT" then
			-- horizontal menus always go to right on character sheet
			ItemRack.MainDock = "TOPRIGHT"
			ItemRack.MenuDock = "TOPLEFT"
		end
	else
		ItemRack_MenuFrame:SetScale(ItemRack_Users[user].MainScale)
	end

	ItemRack_MenuFrame:ClearAllPoints()
	ItemRack_MenuFrame:SetPoint(ItemRack.MenuDock,attachTo,ItemRack.MainDock,dock_info("mx"),dock_info("my")+ (ynudge and ynudge or 0))
end

-- v1="Left1" or "Right1" up to "Left30" or "Right30"
local function is_red(v1)

	local its_red,r,g,b = false

	r,g,b = getglobal("Rack_TooltipScanText"..v1):GetTextColor()
	if r>.9 and g<.2 and b<.2 then
		its_red = true
	end

	return its_red
end

-- returns true if the player can wear this item (no red text on its tooltip)
-- separated from get_item_info because tooltip scanning should be done only at utmost need
local function player_can_wear(bag,slot,invslot)

	local found,lines,txt = false

	for i=2,Rack_TooltipScan:NumLines() do
		-- ClearLines doesn't remove colors, manually remove them
		getglobal("Rack_TooltipScanTextLeft"..i):SetTextColor(0,0,0)
		getglobal("Rack_TooltipScanTextRight"..i):SetTextColor(0,0,0)
	end
	Rack_TooltipScan:SetBagItem(bag,slot)

	for i=2,Rack_TooltipScan:NumLines() do
		txt = getglobal("Rack_TooltipScanTextLeft"..i):GetText()
		-- if either left or right text is red and this isn't a Durability x/x line, this item can't be worn
		if (is_red("Left"..i) or is_red("Right"..i)) and not string.find(txt,durability_pattern) and not string.find(txt,"^Requires") then
			found = true
		end
	end

	local _,_,_,itemType = Rack.GetItemInfo(bag,slot)
	if itemType=="INVTYPE_WEAPON" and invslot==17 and not ItemRack.CanWearOneHandOffHand then
		found = true
	end

	return not found
end

-- the old central info gatherer, now a wrapper to Rack.GetItemInfo
local function get_item_info(bag,slot)

	local texture,name,equipslot,soulbound,count

	if bag==20 then
		-- if querying set slot, return current set texture and name
		name = Rack.CurrentSet()
		texture = "Interface\\AddOns\\ItemRack\\ItemRack-Icon"
		if name and Rack_User[user].Sets[name] and not string.find(name,"^ItemRack") and not string.find(name,"^Rack-") then
			texture = Rack_User[user].Sets[name].icon
		else
			name = nil
		end
		return texture,name
	end

	texture,_,name,equipslot = Rack.GetItemInfo(bag,slot)
	if slot then
		_,count = GetContainerItemInfo(bag,slot)
	end
	if ItemRack_Settings.Soulbound=="ON" and name then
		local text
		if slot then
			Rack_TooltipScan:SetBagItem(bag,slot)
		else
			Rack_TooltipScan:SetInventoryItem("player",bag)
		end
		for i=2,5 do
			text = getglobal("Rack_TooltipScanTextLeft"..i):GetText() or ""
			if text==ITEM_SOULBOUND or text==ITEM_BIND_QUEST or text==ITEM_CONJURED then
				soulbound = true
			end
		end
	end

	return texture,name,equipslot,soulbound,count
end

local function cursor_empty()
	return not (CursorHasItem() or CursorHasMoney() or CursorHasSpell())
end

-- updates cooldown spinners in the menu
local function update_menu_cooldowns()

	local start, duration, enable

	if ItemRack.InvOpen then
		for i=1,ItemRack.NumberOfItems do
			if ItemRack.BaggedItems[i].bag then
				start, duration, enable = GetContainerItemCooldown(ItemRack.BaggedItems[i].bag,ItemRack.BaggedItems[i].slot)
				CooldownFrame_SetTimer(getglobal("ItemRackMenu"..i.."Cooldown"), start, duration, enable)
			else
				getglobal("ItemRackMenu"..i.."Time"):SetText("")
			end
		end
	end
end

-- updates cooldown spinners in the main bar
local function update_inv_cooldowns()

	local i, start, duration, enable

	if table.getn(ItemRack_Users[user].Bar)>0 then
		for i=1,table.getn(ItemRack_Users[user].Bar) do
			start, duration, enable = GetInventoryItemCooldown("player",ItemRack_Users[user].Bar[i])
			CooldownFrame_SetTimer(getglobal("ItemRackInv"..ItemRack_Users[user].Bar[i].."Cooldown"), start, duration, enable)
		end
	end

	update_menu_cooldowns()
end

-- call this when window has changed and cooldowns need redrawn
local function cooldowns_need_updating()
	Rack.StartTimer("CooldownUpdate",.25)
	ItemRack.CooldownsNeedUpdating = true
end

local function populate_baggeditems(idx,bag,slot,name,texture)

	if not ItemRack.BaggedItems[idx] then
		ItemRack.BaggedItems[idx] = {}
	end
	ItemRack.BaggedItems[idx].bag = bag
	ItemRack.BaggedItems[idx].slot = slot
	ItemRack.BaggedItems[idx].name = name
	ItemRack.BaggedItems[idx].texture = texture
end

-- to minimize garbage creation, tables are manipulated by copying values instead of tables
local function copy_baggeditems(source,dest)

	if not ItemRack.BaggedItems[dest] then
		ItemRack.BaggedItems[dest] = {}
	end
	ItemRack.BaggedItems[dest].bag = ItemRack.BaggedItems[source].bag
	ItemRack.BaggedItems[dest].slot = ItemRack.BaggedItems[source].slot
	ItemRack.BaggedItems[dest].name = ItemRack.BaggedItems[source].name
	ItemRack.BaggedItems[dest].texture = ItemRack.BaggedItems[source].texture
end

-- sorts menu up to stop_point, which is idx+1 usually (sort uses stop_point as a temp spot for swapping)
local function sort_menu(stop_point)

	local done,i=false

	if stop_point>2 then
		while not done do
			done = true
			for i=1,stop_point-2 do
				if ItemRack.BaggedItems[i].name > ItemRack.BaggedItems[i+1].name then
					copy_baggeditems(i,stop_point)
					copy_baggeditems(i+1,i)
					copy_baggeditems(stop_point,i+1)

					done = false
				end
			end
		end
	end

end

function ItemRack_CurrentSet()
	return Rack.CurrentSet()
end

-- builds a menu outward from invslot (0-19)
-- setframe = true if this is to dock to the set frame
function ItemRack_BuildMenu(invslot,relativeTo)

	local idx,i,j,k,item,texture,name,equipslot,soulbound,found = 1
	local mainorient = ItemRack_Users[user].MainOrient
	local bagStart,bagEnd = 0,4

	if relativeTo=="SET" or relativeTo=="CHARACTERSHEET" then
		-- if displaying to a set, then 
		mainorient = "VERTICAL"
		if invslot==0 or invslot==16 or invslot==17 or invslot==18 then
			mainorient = "HORIZONTAL"
		end
	elseif relativeTo=="MINIMAP" then
		mainorient = "HORIZONTAL"
	elseif relativeTo=="TITAN" then
		mainorient = "HORIZONTAL"
	end
	ItemRack_DockMenu(invslot,relativeTo)

	for i=1,table.getn(ItemRack_Users[user].Bar) do
		if invslot~=ItemRack_Users[user].Bar[i] then
			getglobal("ItemRackInv"..ItemRack_Users[user].Bar[i]):UnlockHighlight()
		else
			getglobal("ItemRackInv"..ItemRack_Users[user].Bar[i]):LockHighlight()
		end
	end

	if invslot==0 then
		-- if this is an ammo slot, clear totals
		for i in pairs(ItemRack.AmmoCounts) do
			ItemRack.AmmoCounts[i] = 0
		end
	end

	if invslot<20 then

		if ItemRack.BankIsOpen then
			bagStart,bagEnd = -1,10
		end

		-- go through bags and gather items into .BaggedItems
		for i=bagStart,bagEnd do
			for j=1,GetContainerNumSlots(i) do
				texture,name,equipslot,soulbound,count = get_item_info(i,j)
				soulbound = soulbound or ItemRack.Indexes[invslot].ignore_soulbound -- pretend item soulbound if flagged to ignore_soulbound
				if (equipslot and ItemRack.Indexes[invslot][equipslot]) and (soulbound or ItemRack_Settings.Soulbound=="OFF") then
					if ItemRack_Settings.AllowHidden=="ON" and ItemRack_Users[user].Ignore[name] and not IsAltKeyDown() then
						-- skip items that are on ignore list
					elseif player_can_wear(i,j,invslot) then
						if invslot==0 and count then
							-- if this is an ammo slot menu
							ItemRack.AmmoCounts[name] = (ItemRack.AmmoCounts[name] or 0) + count
							found = false
							for k=1,(idx-1) do
								if ItemRack.BaggedItems[k].name==name then
									found=true
								end
							end
							if not found then
								populate_baggeditems(idx,i,j,name,texture)
								idx = idx + 1
							end					
						else
							populate_baggeditems(idx,i,j,name,texture)
							idx = idx + 1
						end
					end
				end
			end
		end
		sort_menu(idx)

		if ItemRack_Settings.ShowEmpty=="ON" and GetInventoryItemLink("player",invslot) and not (ItemRack_Settings.RightClick=="ON" and (invslot==13 or invslot==14)) then
			-- add an empty slot to the menu
			_,_,i = string.find(ItemRack.Indexes[invslot].keybind,"Use (.+) Item")
			_,j = GetInventorySlotInfo(string.gsub(ItemRack.Indexes[invslot].paperdoll_slot,"Character",""))
			populate_baggeditems(idx,nil,nil,"(empty)",j)
			idx = idx + 1
		end

	else
		-- this is a menu for sets
		-- go through sets and gather them into .BaggedItems
		for i in pairs(Rack_User[user].Sets) do
			if not string.find(i,"^ItemRack") and not string.find(i,"^Rack-") and (not Rack_User[user].Sets[i].hide or IsAltKeyDown()) then
				populate_baggeditems(idx,nil,nil,i,Rack_User[user].Sets[i].icon)
				idx = idx + 1
			end
		end
		sort_menu(idx)
	end

	ItemRack.NumberOfItems = math.min(idx-1,ItemRack.MaxItems)

	if ItemRack.NumberOfItems<1 then
		-- user has no bagged items for this type
		ItemRack_MenuFrame:Hide()
	else
		-- display items outward from docking point
		local col,row,xpos,ypos = 0,0,dock_info("xstart"),dock_info("ystart")
		local max_cols = 1

		if ItemRack.NumberOfItems>24 then
			max_cols = 5
		elseif ItemRack.NumberOfItems>18 then
			max_cols = 4
		elseif ItemRack.NumberOfItems>12 then
			max_cols = 3
		elseif ItemRack.NumberOfItems>4 then
			max_cols = 2
		end

		for i=1,ItemRack.NumberOfItems do

			local item = getglobal("ItemRackMenu"..i.."Icon")
			item:SetTexture(ItemRack.BaggedItems[i].texture)
			-- grey menu item if it's on the ignore list (ALT key is down if it made it to BaggedItems)
			if ItemRack_Settings.AllowHidden=="ON" and (ItemRack_Users[user].Ignore[ItemRack.BaggedItems[i].name] or (Rack_User[user].Sets[ItemRack.BaggedItems[i].name] and Rack_User[user].Sets[ItemRack.BaggedItems[i].name].hide)) then
				SetDesaturation(item,1)
			else
				SetDesaturation(item,nil)
			end

			local item = getglobal("ItemRackMenu"..i)
			item:SetPoint("TOPLEFT","ItemRack_MenuFrame",ItemRack.MenuDock,xpos,ypos)

			if (mainorient=="HORIZONTAL" and ItemRack_Settings.RotateMenu=="OFF") or (mainorient=="VERTICAL" and ItemRack_Settings.RotateMenu=="ON") then
				xpos = xpos + dock_info("xdir")*40
				col = col + 1
				if col==max_cols then
					xpos = dock_info("xstart")
					col = 0
					ypos = ypos + dock_info("ydir")*40
					row = row + 1
				end
				item:Show()
			else
				ypos = ypos + dock_info("ydir")*40
				col = col + 1
				if col==max_cols then
					ypos = dock_info("ystart")
					col = 0
					xpos = xpos + dock_info("xdir")*40
					row = row + 1
				end
				item:Show()
			end
		end
		for i=(ItemRack.NumberOfItems+1),ItemRack.MaxItems do
			getglobal("ItemRackMenu"..i):Hide()
		end
		if col==0 then
			row = row-1
		end

		if (mainorient=="HORIZONTAL" and ItemRack_Settings.RotateMenu=="OFF") or (mainorient=="VERTICAL" and ItemRack_Settings.RotateMenu=="ON") then
			ItemRack_MenuFrame:SetWidth(12+(max_cols*40))
			ItemRack_MenuFrame:SetHeight(12+((row+1)*40))
		else
			ItemRack_MenuFrame:SetWidth(12+((row+1)*40))
			ItemRack_MenuFrame:SetHeight(12+(max_cols*40))
		end

		-- apply slot-dependant overlays, ammo count, set name and key bindings
		if invslot==0 then -- if this is an ammo slot, show counts
			for i=1,ItemRack.NumberOfItems do
				if ItemRack.AmmoCounts[ItemRack.BaggedItems[i].name] then
					getglobal("ItemRackMenu"..i.."Count"):SetText(ItemRack.AmmoCounts[ItemRack.BaggedItems[i].name])
				end
			end
		elseif invslot==20 then -- if this is a set slot, show names and bindings
			for i=1,ItemRack.NumberOfItems do
				name = ItemRack.BaggedItems[i].name
				if ItemRack.BankIsOpen and Rack.SetHasBanked(name) then
					getglobal("ItemRackMenu"..i.."Border"):Show()
					getglobal("ItemRackMenu"..i.."Icon"):SetVertexColor(.5,.5,.5)
				else
					getglobal("ItemRackMenu"..i.."Border"):Hide()
					getglobal("ItemRackMenu"..i.."Icon"):SetVertexColor(1,1,1)
				end
					
				item = getglobal("ItemRackMenu"..i.."Name")
				if ItemRack_Settings.SetLabels=="ON" then
					item:SetText(name)
					item:Show()
				else
					item:Hide()
				end
				item = getglobal("ItemRackMenu"..i.."HotKey")
				if Rack_User[user].Sets[name].key and ItemRack_Settings.Bindings=="ON" then
					_,_,j,k = string.find(Rack_User[user].Sets[name].key or "","(.).+(-.)")
					item:SetText((j or "")..(k or ""))
					item:Show()
				else
					item:Hide()
				end
			end
		else -- normal slot (1-19) has no overlays
			for i=1,ItemRack.NumberOfItems do
				getglobal("ItemRackMenu"..i.."Name"):SetText("")
				getglobal("ItemRackMenu"..i.."Count"):SetText("")
				getglobal("ItemRackMenu"..i.."HotKey"):SetText("")

				if ItemRack.BankedItems[ItemRack.BaggedItems[i].name] then
					getglobal("ItemRackMenu"..i.."Border"):Show()
					getglobal("ItemRackMenu"..i.."Icon"):SetVertexColor(.5,.5,.5)
				else
					getglobal("ItemRackMenu"..i.."Border"):Hide()
					getglobal("ItemRackMenu"..i.."Icon"):SetVertexColor(1,1,1)
				end
			end
		end

		ItemRack.InvOpen = invslot
		ItemRack_MenuFrame:Show()
		update_menu_cooldowns()
		Rack.StartTimer("CooldownUpdate",0) -- immediate cooldown update
		Rack.StartTimer("MenuFrame")

	end
end

-- for use with main/menu frames with UIParent parent when relocated by the mod, to register for layout-cache.txt
local function really_setpoint(frame,point,relativeTo,relativePoint,xoff,yoff)
	frame:SetPoint(point,relativeTo,relativePoint,xoff,yoff)
	ItemRack_Users[user].XPos = xoff
	ItemRack_Users[user].YPos = yoff
end

-- updates the image inside the minimap button depending on current set and disable toggle option ("Minimap set menu")
local function draw_minimap_icon()
	local setname = Rack.CurrentSet()

	if setname and Rack_User[user].Sets[setname] and ItemRack_Settings.DisableToggle=="ON" then
		ItemRack_IconFrame_Icon:SetTexture(Rack_User[user].Sets[setname].icon)
	else
		ItemRack_IconFrame_Icon:SetTexture("Interface\\AddOns\\ItemRack\\ItemRack-Icon")
	end
end

--function ItemRack_UpdateItemAttributes()
--	local bar = ItemRack_Users[user].Bar
--	local button
--	if #(bar)>0 then
--		for i=1,#(bar) do
--			if bar[i]<20 then
--				button = getglobal("ItemRackInv"..bar[i])
--				_,_,name = Rack.GetItemInfo(bar[i])
--				button:SetAttribute("item",name)
--			end
--		end
--	end
--end

-- draws the inventory bar
local function draw_inv()

	local oldx = ItemRack_InvFrame:GetLeft() or ItemRack_Users[user].XPos
	local oldy = ItemRack_InvFrame:GetTop() or ItemRack_Users[user].YPos
	local oldcx = ItemRack_InvFrame:GetWidth() or 0
	local oldcy = ItemRack_InvFrame:GetHeight() or 0
	local bar = ItemRack_Users[user].Bar

	if not oldx or not oldy then
		return -- frame isn't fully defined yet, leave now
	end

	-- for a left-to-right horizontal configuration
	local cx,cy,i,item,texture,xspacer,yspacer = 56,56

	if ItemRack_Users[user].MainOrient=="HORIZONTAL" then
		-- horizontal from left to right
		xdir,ydir,corner,cornerTo,cornerStart,xdirStart,ydirStart,xadd,yadd = 4,0,"TOPRIGHT","TOPLEFT","TOPLEFT",10,-10,40,0

		if ItemRack_Settings.FlipBar=="ON" then
			-- horizontal from right to left
			xdir,ydir,corner,cornerTo,cornerStart,xdirStart,ydirStart,xadd,yadd = -4,0,"TOPLEFT","TOPRIGHT","TOPRIGHT",-10,-10,-40,0
		end

	else
		-- vertical from top to bottom
		xdir,ydir,corner,cornerTo,cornerStart,xdirStart,ydirStart,xadd,yadd = 0,-4,"BOTTOMLEFT","TOPLEFT","TOPLEFT",10,-10,0,40
		
		if ItemRack_Settings.FlipBar=="ON" then
			-- vertical from bottom to top
			xdir,ydir,corner,cornerTo,cornerStart,xdirStart,ydirStart,xadd,yadd = 0,4,"TOPLEFT","BOTTOMLEFT","BOTTOMLEFT",10,10,0,-40
		end
	end

	for i=0,20 do
		getglobal("ItemRackInv"..i):Hide()
	end
	ItemRack.TrinketsPaired = false -- changes to true if two trinkets are beside each other

	if table.getn(bar)>0 then
		item = getglobal("ItemRackInv"..bar[1])
		item:ClearAllPoints()
		item:SetPoint(cornerStart,"ItemRack_InvFrame",cornerStart,xdirStart,ydirStart)
		getglobal("ItemRackInv"..bar[1].."Icon"):SetTexture(get_item_info(bar[1]))
		item:Show()
		if ItemRack_Settings.RightClick=="ON" and (bar[1]==13 and bar[2]==14) then
			ItemRack.TrinketsPaired = true
		end
		for i=2,table.getn(bar) do
			xspacer,yspacer = 0,0
			if ItemRack_Settings.RightClick=="ON" and ((bar[i]==13 and bar[i+1] and bar[i+1]==14) or 
				(bar[i-1]==14 and bar[i-2] and bar[i-2]==13)) then
				ItemRack.TrinketsPaired = true
			end
			if ItemRack_Users[user].Spaces[bar[i-1]] then
				xspacer = xdir*2
				yspacer = ydir*2
			end
			item = getglobal("ItemRackInv"..bar[i])
			item:ClearAllPoints()
			item:SetPoint(cornerTo,"ItemRackInv"..bar[i-1],corner,xdir+xspacer,ydir+yspacer)
			getglobal("ItemRackInv"..bar[i].."Icon"):SetTexture(get_item_info(bar[i]))
			item:Show()
			cx = cx + math.abs(xadd) + math.abs(xspacer)
			cy = cy + math.abs(yadd) + math.abs(yspacer) -- was minus yspacer
		end
		ItemRack_InvFrame:SetWidth(cx)
		ItemRack_InvFrame:SetHeight(cy)

		if ItemRack_Settings.FlipBar=="ON" and oldcx>32 and oldcy>32 then
			-- if bar size changed (after being drawn before), and we're flipped, we need to shift it over
			ItemRack_InvFrame:ClearAllPoints()
			if ItemRack_Users[user].MainOrient=="HORIZONTAL" then
				oldx = oldx + (oldcx-cx)
			else
				oldy = oldy + (cy-oldcy)
			end
			really_setpoint(ItemRack_InvFrame,"TOPLEFT","UIParent","BOTTOMLEFT",oldx,oldy)
		end

		if ItemRack_Users[user].Visible~="OFF" then
			ItemRack_InvFrame:Show()
		end
		cooldowns_need_updating()
		Rack.StartTimer("CooldownUpdate",0)

		if ItemRack_Settings.SetLabels=="ON" then
			local currentset = Rack.CurrentSet()
			if currentset and Rack_User[user].Sets[currentset] then
				ItemRackInv20Name:SetText(currentset)
			else
				ItemRackInv20Name:SetText(ItemRackText.EMPTYSET)
			end
		else
			ItemRackInv20Name:SetText("")
		end
	else
		ItemRack_Users[user].Visible="OFF"
		ItemRack_InvFrame:Hide()
	end
	draw_minimap_icon()

	if ItemRack_UpdatePlugins then
		-- update plugin if it exists
		local setname = Rack.CurrentSet()
		if setname and ItemRack_Users[user].Sets[setname] then
			ItemRack_UpdatePlugins(setname,ItemRack_Users[user].Sets[setname].icon)
		else
			ItemRack_UpdatePlugins(nil,"Interface\\AddOns\\ItemRack\\ItemRack-Icon")
		end
	end
--	ItemRack_UpdateItemAttributes()
end

local function unlocked()
	return ItemRack_Users[user].Locked~="ON"
end

-- sets window lock "ON" or "OFF"
local function set_lock(arg1)

	ItemRack_Users[user].Locked = arg1

	if arg1=="ON" then
		ItemRack_InvFrame:SetBackdropColor(0,0,0,0)
		ItemRack_InvFrame:SetBackdropBorderColor(0,0,0,0)
		ItemRack_InvFrame_Resize:Hide()
		ItemRack_Control_Rotate:SetAlpha(.4)
		ItemRack_Control_Rotate:Disable()
	else
		ItemRack_InvFrame:SetBackdropColor(1,1,1,1)
		ItemRack_InvFrame:SetBackdropBorderColor(1,1,1,1)
		ItemRack_InvFrame_Resize:Show()
		ItemRack_Control_Rotate:SetAlpha(1)
		ItemRack_Control_Rotate:Enable()
		ItemRack_ControlFrame:Show()
		Rack.StartTimer("ControlFrame")
	end
end

-- called at startup, UPDATE_BINDINGS and option change to show/hide key bindings
local function update_keybindings()

	local i,modifier,key,text

	-- update bindings for inventory slots
	for i=0,19 do
		if ItemRack.Indexes[i].keybind and ItemRack_Settings.Bindings=="ON" then
			text = GetBindingKey(ItemRack.Indexes[i].keybind)
			_,_,modifier,key = string.find(text or "","(.).+(-.)")
			if modifier and key then
				text = modifier..key
			end
			getglobal("ItemRackInv"..i.."HotKey"):SetText(text)
		else
			getglobal("ItemRackInv"..i.."HotKey"):SetText("")
		end
	end

	-- update bindings for items on the rack
	ItemRack_AgreeOnKeyBindings()

end

local function move_control()

	local item = ItemRack_InvFrame

	ItemRack_Control_Rotate:ClearAllPoints()
	ItemRack_Control_Lock:ClearAllPoints()
	ItemRack_Control_Options:ClearAllPoints()

	if ItemRack_Users[user].MainOrient=="HORIZONTAL" then
		
		if ItemRack_Settings.FlipBar=="OFF" then
			ItemRack_Control_Rotate:SetPoint("TOPLEFT","ItemRack_InvFrame","TOPRIGHT",-2,-3)
		else
			ItemRack_Control_Rotate:SetPoint("TOPRIGHT","ItemRack_InvFrame","TOPLEFT",2,-3)
		end
		ItemRack_Control_Lock:SetPoint("TOPLEFT","ItemRack_Control_Rotate","BOTTOMLEFT")
		ItemRack_Control_Options:SetPoint("TOPLEFT","ItemRack_Control_Lock","BOTTOMLEFT")
	else
		if ItemRack_Settings.FlipBar=="OFF" then
			ItemRack_Control_Rotate:SetPoint("BOTTOMLEFT","ItemRack_InvFrame","TOPLEFT",3,-2)
		else
			ItemRack_Control_Rotate:SetPoint("TOPLEFT","ItemRack_InvFrame","BOTTOMLEFT",3,2)
		end
		ItemRack_Control_Lock:SetPoint("TOPLEFT","ItemRack_Control_Rotate","TOPRIGHT")
		ItemRack_Control_Options:SetPoint("TOPLEFT","ItemRack_Control_Lock","TOPRIGHT")
	end
end

-- moves the minimap icon to last position in settings or default angle of 45
local function move_icon()

	local xpos,ypos
	local angle = ItemRack_Settings.IconPos or 0

	if ItemRack_Settings.SquareMinimap=="ON" then
		-- brute force method until trig solution figured out - min/max a point on a circle beyond square
		xpos = 110 * cos(angle)
		ypos = 110 * sin(angle)
		xpos = math.max(-82,math.min(xpos,84))
		ypos = math.max(-86,math.min(ypos,82))
	else
		xpos = 80*cos(angle)
		ypos = 80*sin(angle)
	end

	ItemRack_IconFrame:SetPoint("TOPLEFT","Minimap","TOPLEFT",52-xpos,ypos-52)

end

local function initialize_data()

	-- if EquipSet isn't defined by some other mod, use it as an alias for ItemRack_EquipSet, since macros are limited by space
	EquipSet = EquipSet or ItemRack_EquipSet -- ItemRack_EquipSet
	SaveSet = SaveSet or ItemRack_SaveSet
	LoadSet = LoadSet or ItemRack_LoadSet
	ToggleSet = ToggleSet or ItemRack_ToggleSet
	IsSetEquipped = IsSetEquipped or ItemRack_IsSetEquipped

	-- create new user if one doesn't exist
	if not ItemRack_Users[user] then
		ItemRack_Users[user] = {} -- create new per-user setting
		ItemRack_Users[user].Inv = {} -- nil or 1, whether inv slot visible
		ItemRack_Users[user].Bar = {} -- 1-number of inventory bars on screen at once
		ItemRack_Users[user].Spaces = {} -- nil or true, whether a space should appear after this slot
		ItemRack_Users[user].Ignore = {} -- list of items to ignore on bar
		for i in pairs(ItemRackOpt_Defaults) do
			ItemRack_Users[user][i] = ItemRackOpt_Defaults[i]
		end
	end
	-- upgrade old version data
	ItemRack_Settings.ShowEmpty = ItemRack_Settings.ShowEmpty or "OFF" -- 1.1
	ItemRack_Settings.FlipMenu = ItemRack_Settings.FlipMenu or "OFF" -- 1.1
	ItemRack_Settings.RightClick = ItemRack_Settings.RightClick or "OFF" -- 1.1
	ItemRack_Settings.TinyTooltip = ItemRack_Settings.TinyTooltip or "OFF" -- 1.2
	ItemRack_Settings.ShowTooltips = ItemRack_Settings.ShowTooltips or "ON" -- 1.2
	ItemRack_Settings.RotateMenu = ItemRack_Settings.RotateMenu or "OFF" -- 1.3
	ItemRack_Users[user].Spaces = ItemRack_Users[user].Spaces or {} -- 1.3
	ItemRack_Users[user].Sets = ItemRack_Users[user].Sets or {} -- 1.4
	ItemRack_Settings.ShowIcon = ItemRack_Settings.ShowIcon or "ON" -- 1.4
	ItemRack_Settings.DisableToggle = ItemRack_Settings.DisableToggle or "ON" -- 1.4
	ItemRack_Settings.FlipBar = ItemRack_Settings.FlipBar or "OFF" -- 1.5
	ItemRack_Users[user].Ignore = ItemRack_Users[user].Ignore or {} -- 1.5
	ItemRack_Settings.EnableEvents = ItemRack_Settings.EnableEvents or "OFF" -- 1.5
	ItemRack_Users[user].Events = ItemRack_Users[user].Events or {} -- 1.7
	ItemRack_Settings.CompactList = ItemRack_Settings.CompactList or "OFF" -- 1.7
	ItemRack_Settings.NotifyThirty = ItemRack_Settings.NotifyThirty or "OFF" -- 1.7
	ItemRack_Settings.ShowAllEvents = ItemRack_Settings.ShowAllEvents or "OFF" -- 1.7
	ItemRack_Settings.AllowHidden = ItemRack_Settings.AllowHidden or "OFF" -- 1.82
	ItemRack_Settings.LargeFont = ItemRack_Settings.LargeFont or "OFF" -- 1.9
	ItemRack_Settings.SquareMinimap = ItemRack_Settings.SquareMinimap or "OFF" -- 1.9
	ItemRack_Settings.BigCooldown = ItemRack_Settings.BigCooldown or "OFF" -- 1.9
	ItemRack_Settings.SetLabels = ItemRack_Settings.SetLabels or "ON" -- 1.91
	ItemRack_Settings.AutoToggle = ItemRack_Settings.AutoToggle or "OFF" -- 1.91

	local _,class = UnitClass("player")
	if class=="WARRIOR" or class=="ROGUE" or class=="HUNTER" or class=="SHAMAN" then
		ItemRack.CanWearOneHandOffHand = 1
	end
end

local function initialize_display()

	-- set scale and position to last saved setting
	ItemRack_InvFrame:SetScale(ItemRack_Users[user].MainScale or 1)
	ItemRack_MenuFrame:SetScale(ItemRack_Users[user].MainScale or 1)
	ItemRack_InvFrame:ClearAllPoints()
	really_setpoint(ItemRack_InvFrame,"TOPLEFT","UIParent","BOTTOMLEFT",ItemRack_Users[user].XPos,ItemRack_Users[user].YPos)
	set_lock(ItemRack_Users[user].Locked)
	update_keybindings()

	ItemRackInv0Count:SetText(CharacterAmmoSlotCount:IsShown() and CharacterAmmoSlotCount:GetText() or "")

	for i in pairs(ItemRack.OptInfo) do
		if ItemRack.OptInfo[i].type=="Check" and getglobal(i) then
			item = getglobal(i.."Text")
			item:SetText(ItemRack.OptInfo[i].text)
			item:SetTextColor(1,1,1)
			if ItemRack.OptInfo[i].info and ItemRack_Settings[ItemRack.OptInfo[i].info]=="ON" then
				getglobal(i):SetChecked(1)
			else
				getglobal(i):SetChecked(0)
			end
		end
		if ItemRack.OptInfo[i].type=="Label" then
			getglobal(i):SetText(ItemRack.OptInfo[i].text)
		end
	end

	if ItemRack_Users[user].Visible=="OFF" then
		ItemRack_InvFrame:Hide()
	end

	move_control() -- docks control buttons on edge of bar
	move_icon() -- moves minimap button

	if ItemRack_Settings.ShowIcon=="OFF" then
		ItemRack_IconFrame:Hide()
	else
		ItemRack_IconFrame:Show()
	end

	make_escable("ItemRack_SetsFrame","add")

	ItemRack_ChangeEventFont()

	for i=1,30 do
		getglobal("ItemRackMenu"..i.."Border"):SetVertexColor(.15,.25,1,1)
		getglobal("ItemRackMenu"..i.."Border"):Hide()
	end

	draw_inv() -- construct the bar
	ItemRack_SetAllCooldownFonts()
	Rack.StartTimer("CooldownUpdate")
end

-- returns true if no swaps are pending
local function all_items_unlocked()

	local i,j,bagged_item_locked,locked
	local bagStart,bagEnd = 0,4

	for i=0,19 do
		locked = locked or IsInventoryItemLocked(i)
	end

	for i=bagStart,bagEnd do
		for j=1,GetContainerNumSlots(i) do
			_,_,bagged_item_locked = GetContainerItemInfo(i,j)
			locked = locked or bagged_item_locked
		end
	end

	return not locked
end

-- displays a quick tooltip note under the sets window
local function sets_message(msg)

	local tooltip = ItemRack_Sets_Message

	tooltip:SetOwner(ItemRack_SetsFrame, "ANCHOR_NONE")
	tooltip:SetPoint("TOP", "ItemRack_SetsFrame", "BOTTOM", 0, 0)
	tooltip:AddLine(msg)
	tooltip:Show()
	tooltip:FadeOut()
end

function newItemRack_CharacterAmmoSlot_OnClick()
	if IsAltKeyDown() then
		newItemRack_PaperDollItemSlotButton_OnClick()
	elseif arg1=="LeftButton" then
		-- only call old function if LeftButton. We never UseInventoryItem(0) (in theory)
		oldItemRack_CharacterAmmoSlot_OnClick()
	end
end


--[[ Frame functions ]]--

function ItemRack_OnLoad()

	-- hook for ALT+click of inventory slots (to add inventory slots to rack)
	oldItemRack_PaperDollItemSlotButton_OnClick = PaperDollItemSlotButton_OnModifiedClick
	PaperDollItemSlotButton_OnModifiedClick = newItemRack_PaperDollItemSlotButton_OnClick

	-- hook for ALT+click of character model (to add set slot to rack)
	oldItemRack_CharacterModelFrame_OnMouseUp = CharacterModelFrame_OnMouseUp
	CharacterModelFrame_OnMouseUp = newItemRack_CharacterModelFrame_OnMouseUp

	-- hook for mouseover of character sheet item slots (to display menu)
	oldItemRack_PaperDollItemSlotButton_OnEnter = PaperDollItemSlotButton_OnEnter
	PaperDollItemSlotButton_OnEnter = newItemRack_PaperDollItemSlotButton_OnEnter

	-- hook for character sheet hiding (to hide menu)
	oldItemRack_PaperDollFrame_OnHide = PaperDollFrame_OnHide
	PaperDollFrame_OnHide = newItemRack_PaperDollFrame_OnHide

	oldItemRack_CharacterAmmoSlot_OnClick = CharacterAmmoSlot:GetScript("OnClick")
	CharacterAmmoSlot:SetScript("OnClick",newItemRack_CharacterAmmoSlot_OnClick)

	hooksecurefunc("UseInventoryItem", newItemRack_UseInventoryItem)
	hooksecurefunc("UseAction", newItemRack_UseAction)

--	oldItemRack_UseInventoryItem = UseInventoryItem
--	UseInventoryItem = newItemRack_UseInventoryItem

--	oldItemRack_UseAction = UseAction
--	UseAction = newItemRack_UseAction

	this:RegisterEvent("PLAYER_LOGIN")
end

local function initialize_events(v1)

	local i,j

	ItemRack_DisableAllEvents()

	if v1 then
		ItemRack_Events = {}
		-- go through and remove all old items from sets
		for i in pairs(Rack_User) do
			if Rack_User[i].Sets then
				for j in pairs(Rack_User[i].Sets) do
					for k=0,19 do
						if Rack_User[i].Sets[j][k] then
							Rack_User[i].Sets[j][k].old = nil
						end
					end
				end
			end
		end
	end

	-- if the events doesn't have a version or it's an old events version, load defaults
	if not tonumber(ItemRack_Events.events_version) or ItemRack_Events.events_version<current_events_version then
		ItemRack_Events.events_version = current_events_version
		for i in pairs(ItemRack_DefaultEvents) do
			ItemRack_Events[i] = {}
			ItemRack_Events[i].trigger = ItemRack_DefaultEvents[i].trigger
			ItemRack_Events[i].delay = ItemRack_DefaultEvents[i].delay
			ItemRack_Events[i].script = ItemRack_DefaultEvents[i].script
		end
	end

	-- if an event is removed, remove the associated sets from the user
	for i in pairs(ItemRack_Users) do
		if ItemRack_Users[i].Events then
			for j in pairs(ItemRack_Users[i].Events) do
				if not ItemRack_Events[j] then
					ItemRack_Users[i].Events[j] = nil
				end
			end
		end
	end

	if TITAN_RIDER_ID and (not TitanRider_EquipToggle or (TitanGetVar and TitanGetVar(TITAN_RIDER_ID,"EquipItems"))) then
		if ItemRack_Users[user].Events["Mount"] and ItemRack_Users[user].Events["Mount"].enabled then
			ItemRack_Users[user].Events["Mount"] = nil
		end
	end

	ItemRack_Events_ScrollFrameScrollBar:SetValue(0)
	ItemRack_Build_eventList()
	ItemRack_EnableAllEvents()
end

function ItemRack_OnEvent(event)

	if event=="UNIT_INVENTORY_CHANGED" then
		if arg1=="player" then
			Rack.StartTimer("InvUpdate")
		end
--		ItemRack_UpdateItemAttributes()
	elseif event=="ACTIONBAR_UPDATE_COOLDOWN" then
		update_inv_cooldowns()
	elseif event=="PLAYER_AURAS_CHANGED" then
		ItemRack_BuffsChanged()

	elseif event=="UPDATE_BINDINGS" then
		update_keybindings()

	elseif event=="BANKFRAME_OPENED" then
		Rack.BankOpened()

	elseif event=="BANKFRAME_CLOSED" then
		Rack.BankClosed()

	elseif event=="BAG_UPDATE" then -- only enabled when bank is open
		Rack.PopulateBank()
		if ItemRack_MenuFrame:IsVisible() and ItemRack.InvOpen then
			ItemRack_BuildMenu(ItemRack.InvOpen,ItemRack.MenuDockedTo)
			if ItemRack.InvOpen==20 then
				local id = GetMouseFocus() and GetMouseFocus():GetID() or ""
				local menuItem = ItemRack.BaggedItems[id or ""]
				if menuItem and menuItem.name and Rack_User[user].Sets[menuItem.name] then
					ItemRack_Sets_Tooltip(menuItem.name,1)
				end
			end
		end

	elseif event=="PLAYER_LOGIN" then

		user = UnitName("player").." of "..GetRealmName()

		SlashCmdList["ItemRackCOMMAND"] = ItemRack_SlashHandler
		SLASH_ItemRackCOMMAND1 = "/itemrack"

		local button
		for i=0,19 do
			button = getglobal("ItemRackInv"..i)
			button:SetAttribute("type","item")
			button:SetAttribute("slot",i)
			button:SetAttribute("shift-item*",ATTRIBUTE_NOOP)
			button:SetAttribute("alt-item*",ATTRIBUTE_NOOP)
		end

		Rack.Initialize()

		initialize_data()
		initialize_events()

		ItemRack_InitializeKeyBindings() -- create key bindings for this user
		initialize_display()

		this:RegisterEvent("UNIT_INVENTORY_CHANGED")
		this:RegisterEvent("UPDATE_BINDINGS")
		this:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN")

		RackFrame:RegisterEvent("PLAYER_REGEN_ENABLED") -- leaving combat
		RackFrame:RegisterEvent("PLAYER_UNGHOST") -- leaving ghost
		RackFrame:RegisterEvent("PLAYER_ALIVE") -- leaving death

		this:RegisterEvent("BANKFRAME_OPENED")
		this:RegisterEvent("BANKFRAME_CLOSED")
	
		if not ItemRack_Settings.Minimap then
			ItemRack_Settings.Minimap = {}
		end
	end
end

function ItemRack_SlashHandler(arg1)

	if arg1 and string.find(arg1,"equip") then
		local _,_,set = string.find(arg1,"equip (.+)")
		if not set then
			DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00Usage: /itemrack equip (set name)")
			DEFAULT_CHAT_FRAME:AddMessage("ie, /itemrack equip pvp gear")
		else
			ItemRack_EquipSet(set)
		end
		return
	elseif arg1 and string.find(arg1,"toggle") then
		local _,_,set = string.find(arg1,"toggle (.+)")
		if not set then
			DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00Usage: /itemrack toggle (set name)")
			DEFAULT_CHAT_FRAME:AddMessage("ie, /itemrack toggle pvp gear")
		else
			ItemRack_ToggleSet(set)
		end
		return
	end

	arg1 = string.lower(arg1)

	if not string.find(arg1,".+") then
		ItemRack_Toggle()
	elseif arg1=="reset everything" then
		StaticPopupDialogs["ITEMRACKRESET"] = {
			text = "Are you sure you want to wipe all ItemRack sets, events and settings?\nThis will restore to default and then Reload the UI.",
			button1 = "Yes", button2 = "No", showAlert=1, timeout = 0, whileDead = 1,
			OnAccept = function() ItemRack_Users=nil ItemRack_Events=nil ItemRack_Settings=nil Rack_User=nil ReloadUI() end,
		}
		StaticPopup_Show("ITEMRACKRESET")
	elseif string.find(arg1,"^reset") then
		if string.find(arg1,"reset event") then
			-- /itemrack reset event will initialize events
			initialize_events("reset")
			ItemRack_Events_ScrollFrameScrollBar:SetValue(0)
			if ItemRack_SetsFrame:IsVisible() then
				sets_message("Default events reset.")
			end
		else
			-- /itemrack reset will reset the bar
			ItemRack_Reset()
		end
	elseif arg1=="lock" then
		set_lock("ON")
	elseif arg1=="unlock" then
		set_lock("OFF")
	elseif string.find(arg1,"scale") then
		local _,_,newscale = string.find(arg1,"scale (.+)")
		if not tonumber(newscale) then
			DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00Usage: /itemrack scale (number)")
			DEFAULT_CHAT_FRAME:AddMessage("ie, /itemrack scale 0.85")
		else
			ItemRack.FrameToScale = ItemRack_InvFrame
			ItemRack_ScaleFrame(newscale)
			ItemRack_Users[user].MainScale = ItemRack_InvFrame:GetScale()
			cooldowns_need_updating()
		end
	elseif string.find(arg1,"^opt") then
		ItemRack_Sets_Toggle()
	elseif arg1=="debug" then
		ItemRack_ListEvents()
		DEFAULT_CHAT_FRAME:AddMessage("Events version: "..tostring(ItemRack_Events.events_version))
		DEFAULT_CHAT_FRAME:AddMessage("ItemRack version: "..tostring(ItemRack_Version))
	else
		DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00ItemRack usage:")
		DEFAULT_CHAT_FRAME:AddMessage("/itemrack : toggle the window")
		DEFAULT_CHAT_FRAME:AddMessage("/itemrack reset : reset window")
		DEFAULT_CHAT_FRAME:AddMessage("/itemrack lock or unlock : toggles window lock")
		DEFAULT_CHAT_FRAME:AddMessage("/itemrack scale (number) : sets an exact scale")
		DEFAULT_CHAT_FRAME:AddMessage("/itemrack equip (set name) : equips a set")
		DEFAULT_CHAT_FRAME:AddMessage("/itemrack toggle (set name) : equips/unequips set")
		DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00Alt+click item slots in the character window to add/remove items.\nWhile locked, hold ALT over the window to access control buttons.")
	end
end

--[[ Main bar add/remove functions ]]--

function ItemRack_Reset()

	local i
	-- restore user settings to default (but keep bar contents)
	for i in pairs(ItemRackOpt_Defaults) do
		ItemRack_Users[user][i] = ItemRackOpt_Defaults[i]
	end
	initialize_data()
	initialize_display()
	initialize_events()
	ItemRack_SetsFrame:ClearAllPoints()
	ItemRack_SetsFrame:SetPoint("CENTER","UIParent","CENTER")
end

local function remove_inv(id)

	local i

	getglobal("ItemRackInv"..id):Hide()
	ItemRack_Users[user].Inv[id] = nil
	for i=1,table.getn(ItemRack_Users[user].Bar) do
		if ItemRack_Users[user].Bar[i]==id then
			table.remove(ItemRack_Users[user].Bar,i)
		end
	end
	draw_inv()
end

--[[ Hooked functions ]]--

-- if action is currently equipped, then reflect its use to the mod
function newItemRack_UseAction(slot,checkCursor,onSelf)

	if IsEquippedAction(slot) and cursor_empty() then
		Rack_TooltipScan:SetAction(slot)
		local usedName = Rack_TooltipScanTextLeft1:GetText()

		local i,name,foundSlot

		-- look for a worn item with same name as clicked item
		for i=1,20 do
			Rack_TooltipScan:SetInventoryItem("player",i)
			name = Rack_TooltipScanTextLeft1:GetText()
			if name==usedName then
				foundSlot = i -- found this item in slot i
				i = 21 -- whimpy break
			end
		end

		if foundSlot and GetActionCooldown(slot)==0 then
			ItemRack_ReactUseInventoryItem(foundSlot)
		end

	end
end

-- Inv slots are added by ALT+clicking the paper doll
function newItemRack_PaperDollItemSlotButton_OnClick(button, ignoreShift)

	if IsAltKeyDown() then
		local item = string.gsub(tostring(this:GetName()),"Character","")
		local id,texture = GetInventorySlotInfo(item)

		if ItemRack_Users[user].Inv[id] then
			remove_inv(id)
		else
			ItemRack_Users[user].Visible="ON"
			ItemRack_Users[user].Inv[id] = 1
			getglobal("ItemRackInv"..id.."Icon"):SetTexture(texture)
			table.insert(ItemRack_Users[user].Bar,id)
			draw_inv()
		end
	else
		oldItemRack_PaperDollItemSlotButton_OnClick(button, ignoreShift)
	end
	
end

-- set slot is added by ALT+clicking the paper doll character model
function newItemRack_CharacterModelFrame_OnMouseUp(button)

	if IsAltKeyDown() then
		if ItemRack_Users[user].Inv[20] then
			remove_inv(20)
		else
			ItemRack_Users[user].Visible="ON"
			ItemRack_Users[user].Inv[20]=1
			table.insert(ItemRack_Users[user].Bar,20)
			draw_inv()
		end
	else
		oldItemRack_CharacterModelFrame_OnMouseUp(button)
	end

end

function newItemRack_PaperDollItemSlotButton_OnEnter()

	local id,i = this:GetName()

	oldItemRack_PaperDollItemSlotButton_OnEnter()

	if IsAltKeyDown() then
		for i=0,19 do
			if ItemRack.Indexes[i].paperdoll_slot==id then
				id = i
				i = 20 -- whimpy break
			end
		end

		if tonumber(id) and not InRepairMode() then
			ItemRack_BuildMenu(id,"CHARACTERSHEET")
		end
	end
end

function newItemRack_PaperDollFrame_OnHide()

	if ItemRack.MenuDockedTo=="CHARACTERSHEET" then
		ItemRack_MenuFrame:Hide()
	end

	oldItemRack_PaperDollFrame_OnHide()
end

--[[ Inv Movement ]]--

function ItemRack_InvFrame_OnMouseDown(arg1)

	if arg1=="LeftButton" and ItemRack_Users[user].Locked=="OFF" then
		this:StartMoving()
	end
end

function ItemRack_InvFrame_OnMouseUp(arg1)

	if arg1=="LeftButton" then
		this:StopMovingOrSizing()
		ItemRack_Users[user].XPos = ItemRack_InvFrame:GetLeft()
		ItemRack_Users[user].YPos = ItemRack_InvFrame:GetTop()
		cooldowns_need_updating()
	end
end

function ItemRack_Toggle()
	if ItemRack_InvFrame:IsVisible() then
		ItemRack_Users[user].Visible="OFF"
		ItemRack_InvFrame:Hide()
	else
		ItemRack_Users[user].Visible="ON"
		ItemRack_InvFrame:Show()
	end
end

--[[ Inventory changes ]]--

-- any inventory changes will cause this OnUpdate to start counting.  After InvUpdateLimit, it processes the inventory change
function ItemRack_InvUpdate_OnUpdate()

	if SpellIsTargeting() then
		-- if we're in disenchant/enchant/applying poison/sharpening stone/etc, check back later. do nothing for now
		Rack.StartTimer("InvUpdate",1)
	else

		local i,j
		local bagStart,bagEnd = 0,4

		Rack.StopTimer("InvUpdate")
		ItemRack.Swapping = false

		draw_inv()

		if ItemRack_Users[user].Inv[0] then
			-- update ammo count
			ItemRackInv0Count:SetText(CharacterAmmoSlotCount:IsShown() and CharacterAmmoSlotCount:GetText() or "")
		end

		if table.getn(ItemRack_Users[user].Bar)>0 then
			for i=1,table.getn(ItemRack_Users[user].Bar) do
				getglobal("ItemRackInv"..ItemRack_Users[user].Bar[i]):SetChecked(0)
				SetDesaturation(getglobal("ItemRackInv"..ItemRack_Users[user].Bar[i].."Icon"),nil)
			end
		end

		-- if set builder is up, change the inventory to reflect the change
		if ItemRack_SetsFrame:IsVisible() then
			for i=0,19 do
				SetDesaturation(getglobal("ItemRack_Sets_Inv"..i.."Icon"),nil)
			end
			ItemRack_Sets_UpdateInventory()
		end

		if ItemRack.InvOpen then
			if ItemRack_Settings.RightClick=="ON" and ItemRack.InvOpen==13 then
				ItemRack.InvOpen = 14
			end
			ItemRack_BuildMenu(ItemRack.InvOpen)
		end

	end
end

--[[ Scaling ]]--

function ItemRack_StartScaling(arg1)
	if arg1=="LeftButton" and unlocked() then
		this:LockHighlight()
		ItemRack.FrameToScale = this:GetParent()
		ItemRack.ScalingWidth = this:GetParent():GetWidth()
		ItemRack_MenuFrame:Hide()
		Rack.StartTimer("ScaleUpdate")
	end
end

function ItemRack_StopScaling(arg1)
	if arg1=="LeftButton" then
		Rack.StopTimer("ScaleUpdate")
		ItemRack.FrameToScale = nil
		cooldowns_need_updating()
		this:UnlockHighlight()
		if this:GetParent():GetName() == "ItemRack_InvFrame" then
			ItemRack_Users[user].MainScale = ItemRack_InvFrame:GetScale()
		end
	end
end

function ItemRack_ScaleFrame(scale)
	local frame = ItemRack.FrameToScale
	local oldscale = frame:GetScale() or 1
	local framex = (frame:GetLeft() or ItemRack_Users[user].XPos)* oldscale
	local framey = (frame:GetTop() or ItemRack_Users[user].YPos)* oldscale

	frame:SetScale(scale)
	really_setpoint(ItemRack_InvFrame,"TOPLEFT","UIParent","BOTTOMLEFT",framex/scale,framey/scale)
end

--[[ Clicks ]]--

-- uses inventory slot v1 (0-19) can be called from key binding and slot doesn't need to be on the bar
function ItemRack_UseItem(v1)
	if SpellIsTargeting() and v1~=0 then -- if poison or sharpening stone being applied (and not an ammo slot)
		DEFAULT_CHAT_FRAME:AddMessage("ItemRack: The ability for mods/macros to apply poisons/sharpening stones/etc is disabled for the time being.  It should be back in a future beta patch.")
--		PickupInventoryItem(v1)
	end
	ItemRack_ReactUseInventoryItem(v1)
end

function ItemRack_ReactUseInventoryItem(slot)

	if ItemRack_Users[user].Inv[slot] then
		getglobal("ItemRackInv"..slot):SetChecked(1)
	end
	Rack.StartTimer("InvUpdate",1.5) -- extra long wait

	_,_,item = string.find(GetInventoryItemLink("player",slot) or "","^.*%[(.*)%].*$")
	if ItemRack_Settings.Notify=="ON" and item then
		ItemRack.NotifyList[item] = { bag=nil, slot=nil, inv=slot }
	end
	if ItemRack_Settings.EnableEvents and item then
		local holdarg1,holdarg2 = arg1,arg2
		arg1 = item
		arg2 = slot
		ItemRack_RegisterFrame_OnEvent("ITEMRACK_ITEMUSED")
		arg1 = holdarg1
		arg2 = holdarg2
	end
end

-- hook for UseInventoryItem
function newItemRack_UseInventoryItem(slot)
	local cooldown = GetInventoryItemCooldown("player",slot)
	if cooldown==0 then
		ItemRack_ReactUseInventoryItem(slot) -- tell the mod an item was used
	end
end

function ItemRack_Inv_OnClick(arg1)

	local id = this:GetID()

	this:SetChecked(0)

	if id==20 and not IsAltKeyDown() then
		if arg1=="RightButton" then
			ItemRack_Sets_Toggle(2)
		else
			local setname = Rack.CurrentSet()
			if setname and Rack_User[user].Sets[setname] then
				if IsShiftKeyDown() then
					Rack.UnequipSet(setname)
				else
					ItemRack_EquipSet(setname)
				end
			end
		end
	elseif arg1=="RightButton" and IsAltKeyDown() and ItemRack_Users[user].Locked=="OFF" then
		-- toggle space after item
		ItemRack_Users[user].Spaces[id] = not ItemRack_Users[user].Spaces[id]
		draw_inv()
	elseif arg1=="LeftButton" and IsAltKeyDown() and ItemRack_Users[user].Locked=="OFF" then
		-- if Alt is down, remove the item from the bar
		if ItemRack_Users[user].Inv[id] then
			remove_inv(id)
			ItemRack_MenuFrame:Hide()
		end
	elseif arg1=="LeftButton" and IsShiftKeyDown() and ChatFrameEditBox:IsVisible() then
		-- if Shift is down, link the item to chat
		ChatFrameEditBox:Insert(GetInventoryItemLink("player",id))
	else
		-- otherwise use the item
		ItemRack_UseItem(id)
	end
end

--[[ Menu ]]--

function ItemRack_Inv_OnEnter()
	local id = this:GetID()

	ItemRack_Inv_Tooltip()

	ItemRack.MenuDockedTo = nil
	if not Rack.TimerEnabled("ScaleUpdate") then
		if IsShiftKeyDown() or ItemRack_Settings.MenuShift=="OFF" then
			if ItemRack_Settings.RightClick=="ON" and (id==13 or id==14) and ItemRack.TrinketsPaired then
				if ItemRack_Users[user].MainOrient=="HORIZONTAL" and corner_info("LEFTRIGHT")=="LEFT" then
					id = 13
				elseif ItemRack_Users[user].MainOrient=="HORIZONTAL" and corner_info("LEFTRIGHT")=="RIGHT" then
					id = 14
				elseif ItemRack_Users[user].MainOrient=="VERTICAL" and corner_info("TOPBOTTOM")=="TOP" then
					id = 13
				else
					id = 14
				end
			end
			ItemRack_BuildMenu(id)
		end
		if IsAltKeyDown() then
			ItemRack_ControlFrame:Show()
			Rack.StartTimer("ControlFrame")
		end
	end
end

function ItemRack_Menu_OnClick(arg1)

	local id,i,unqueue = this:GetID()
	local name = ItemRack.BaggedItems[id].name

	this:SetChecked(0)

	if SpellIsTargeting() or CursorHasItem() then return end -- prohibit swaps while in spell target/disenchant mode

	if ItemRack.BankIsOpen then
		if ItemRack.InvOpen~=20 then
			Rack.ClearLockList()
			local bag,slot
			if ItemRack.BankedItems[name] then
				bag,slot = Rack.FindSpace()
				if bag then
					PickupContainerItem(ItemRack.BaggedItems[id].bag,ItemRack.BaggedItems[id].slot)
					PickupContainerItem(bag,slot)
				else
					Rack.NoMoreRoom()
				end
			else
				bag,slot = Rack.FindSpace(1)
				if bag then
					PickupContainerItem(ItemRack.BaggedItems[id].bag,ItemRack.BaggedItems[id].slot)
					PickupContainerItem(bag,slot)
				else
					Rack.NoMoreRoom()
				end
				-- *** swap from bag to bank
			end
		else
			if Rack.SetHasBanked(name) then
				Rack.PullSetFromBank(name)
			else
				Rack.PushSetToBank(name)
			end
		end
		return
	end

	if ItemRack_Settings.RightClick=="ON" and arg1=="LeftButton" and ItemRack.InvOpen==14 then
		ItemRack.InvOpen = 13
	elseif ItemRack_Settings.RightClick=="ON" and arg1=="RightButton" and ItemRack.InvOpen==13 then
		ItemRack.InvOpen = 14
	end

	if (ItemRack_Settings.AllowHidden=="ON" or ItemRack.InvOpen==20) and IsAltKeyDown() and ItemRack.MenuDockedTo~="CHARACTERSHEET" then

		if ItemRack.InvOpen==20 then
			-- sets ignore flag is with the set
			if Rack_User[user].Sets[name].hide then
				Rack_User[user].Sets[name].hide = nil
			else
				Rack_User[user].Sets[name].hide = 1
			end
		elseif not ItemRack.BaggedItems[id].bag then
			-- empty slot, do nothing
		elseif ItemRack_Users[user].Ignore[ItemRack.BaggedItems[id].name] then
			ItemRack_Users[user].Ignore[ItemRack.BaggedItems[id].name] = nil
		else
			ItemRack_Users[user].Ignore[ItemRack.BaggedItems[id].name] = 1
		end
		ItemRack_BuildMenu(ItemRack.InvOpen,ItemRack.MenuDockedTo)

	elseif arg1=="LeftButton" and IsShiftKeyDown() and ChatFrameEditBox:IsVisible() then
		-- if linking a menu item with shift+left click
		ChatFrameEditBox:Insert(GetContainerItemLink(ItemRack.BaggedItems[id].bag,ItemRack.BaggedItems[id].slot))

	elseif ItemRack.InvOpen==20 then
		-- if selecting a set menu item
		if ItemRack.BaggedItems[id].name then
			if (not UnitAffectingCombat("player") and not Rack.IsPlayerReallyDead()) and (IsShiftKeyDown() or ItemRack_Settings.AutoToggle=="ON") then
				-- toggle set if shift key is down or AutoToggle on
				ItemRack_ToggleSet(ItemRack.BaggedItems[id].name)
			else -- otherwise equip it
				ItemRack_EquipSet(ItemRack.BaggedItems[id].name)
			end
			ItemRack_MenuFrame:Hide()
			Rack.StartTimer("InvUpdate",1) -- extra long wait, force an update, set may not swap
		end

	elseif (UnitAffectingCombat("player") and not ItemRack.Indexes[ItemRack.InvOpen].swappable) or Rack.IsPlayerReallyDead() then
		-- if selecting a menu item while dead
		if ItemRack.BaggedItems[id].name=="(empty)" then
			Rack.AddToCombatQueue(ItemRack.InvOpen,0)
		else
			local _,itemID = Rack.GetItemInfo(ItemRack.BaggedItems[id].bag,ItemRack.BaggedItems[id].slot)
			Rack.AddToCombatQueue(ItemRack.InvOpen,itemID)
		end
		ItemRack_MenuFrame:Hide()

	elseif ItemRack.InvOpen and cursor_empty() and not SpellIsTargeting() then
		-- if this is a normal swap of a single item out of combat and the cursor is free/not doing anything

		ItemRack.Swapping = true
		if ItemRack.BaggedItems[id].bag then
			-- find out if incoming item is two-hand or offhand that may leave a loose item in bags for later move
			local _,_,equipslot = get_item_info(ItemRack.BaggedItems[id].bag,ItemRack.BaggedItems[id].slot)
			if equipslot=="INVTYPE_2HWEAPON" then
				if GetInventoryItemLink("player",17) then
					-- something is in offhand slot, move it out
					Rack.ClearLockList()
					bag,slot = Rack.FindSpace()
					if not bag then
						UIErrorsFrame:AddMessage(ERR_INV_FULL,1,.1,.1,1,UIERRORS_HOLD_TIME)
					else
						PickupInventoryItem(17)
						PickupContainerItem(bag,slot)
					end
				end
			end
			PickupContainerItem(ItemRack.BaggedItems[id].bag,ItemRack.BaggedItems[id].slot)
			PickupInventoryItem(ItemRack.InvOpen)
		else
			local j,bag,slot
			-- swapping to an empty slot, create freespace
			Rack.ClearLockList()
			bag,slot = Rack.FindSpace()
			if not bag then
				UIErrorsFrame:AddMessage(ERR_INV_FULL,1,.1,.1,1,UIERRORS_HOLD_TIME)
			else
				PickupInventoryItem(ItemRack.InvOpen)
				PickupContainerItem(bag,slot)
			end
		end
		SetDesaturation(getglobal("ItemRackInv"..ItemRack.InvOpen.."Icon"),1)

		if ItemRack_SetsFrame:IsVisible() then
			SetDesaturation(getglobal("ItemRack_Sets_Inv"..ItemRack.InvOpen.."Icon"),1)
		end

		if not IsShiftKeyDown() or ItemRack_Settings.RightClick=="OFF" then
			ItemRack_MenuFrame:Hide()
		end
		Rack.StartTimer("InvUpdate",1.25)

	end
end

function ItemRack_MenuFrame_OnShow()
	Rack.StartTimer("MenuFrame")
end

function ItemRack_MenuFrame_OnHide()

	Rack.StopTimer("MenuFrame")
	local i
	for i=0,20 do
		getglobal("ItemRackInv"..i):UnlockHighlight()
	end
	ItemRack.InvOpen = nil
end

--[[ Tooltips ]]--

function ItemRack_Inv_Tooltip()

	local id = this:GetID()

	if Rack.TimerEnabled("ScaleUpdate") or ItemRack_Settings.ShowTooltips=="OFF" then
		return
	end

	if id==20 then
		-- if mouseover set on bar, display current set tooltip
		ItemRack_Sets_Tooltip(Rack.CurrentSet())
	else
		-- otherwise set up tooltip for an inventory item
		ItemRack.TooltipOwner = this
		ItemRack.TooltipType = "INVENTORY"
		ItemRack.TooltipSlot = id

		ItemRack.TooltipBag = Rack.GetNameByID(Rack.CombatQueue[id])

		Rack.StartTimer("TooltipUpdate",0)
	end
end

function ItemRack_Menu_Tooltip()

	local id = this:GetID()

	if Rack.TimerEnabled("ScaleUpdate") or ItemRack_Settings.ShowTooltips=="OFF" then
		return
	end

	if ItemRack.InvOpen==20 then
		-- if a sets menu, display set tooltip
		ItemRack_Sets_Tooltip(ItemRack.BaggedItems[id].name)

	elseif ItemRack.BaggedItems[id].bag then
		-- otherwise set up tooltip for a bagged item
		ItemRack.TooltipOwner = this
		ItemRack.TooltipType = "BAG"
		ItemRack.TooltipBag = ItemRack.BaggedItems[id].bag
		ItemRack.TooltipSlot = ItemRack.BaggedItems[id].slot

		Rack.StartTimer("TooltipUpdate",0)
	end
end

function ItemRack_ClearTooltip()

	GameTooltip:Hide()
	ItemRack.TooltipType = nil
	Rack.StopTimer("TooltipUpdate")
	if not ItemRack.InvOpen then
		ItemRack_MenuFrame_OnHide()
	end
end

local function set_tooltip_anchor(owner)

	if ItemRack.MenuDockedTo=="CHARACTERSHEET" and ItemRack.InvOpen then
		-- if this is a tooltip of an item docked to character sheet, anchor it to the paperdoll_slot
		GameTooltip:SetOwner(owner,"ANCHOR_RIGHT")
	elseif ItemRack_Settings.TooltipFollow=="ON" then
		if (owner:GetLeft() or 0)<400 then
			GameTooltip:SetOwner(owner,"ANCHOR_RIGHT")
		else
			GameTooltip:SetOwner(owner,"ANCHOR_LEFT")
		end
	else
		GameTooltip_SetDefaultAnchor(GameTooltip,UIParent)
	end
end

-- takes the currently-built tooltip and rebuilds with just name, durability and cooldown
local function shrink_tooltip()

	local nameline_line,durability_line,cooldown_line,tooltip_line,item_color

	name_line = GameTooltipTextLeft1:GetText()

	if name_line then

		for i=2,GameTooltip:NumLines() do
			tooltip_line = getglobal("GameTooltipTextLeft"..i):GetText() or ""
			if string.find(tooltip_line,durability_pattern) then
				durability_line = tooltip_line
			elseif string.find(tooltip_line,COOLDOWN_REMAINING) then
				cooldown_line = "|cFFFFFFFF"..tooltip_line
			end
		end

		if ItemRack.TooltipType=="BAG" then
			item_color = string.sub(GetContainerItemLink(ItemRack.TooltipBag,ItemRack.TooltipSlot) or "",1,10) or ""
		else
			item_color = string.sub(GetInventoryItemLink("player",ItemRack.TooltipSlot) or "",1,10) or ""
		end

		set_tooltip_anchor(ItemRack.TooltipOwner)
		GameTooltip:ClearLines()
		GameTooltip:AddLine(item_color..name_line)
		GameTooltip:AddLine(durability_line)
		GameTooltip:AddLine(cooldown_line)
	end
end

--[[ Cooldowns ]]--

local function format_time(seconds)

	if seconds<60 then
		return math.floor(seconds+.5)..((ItemRack_Settings.BigCooldown=="ON") and "" or " s")
	else
		if seconds < 3600 then 
			return math.ceil((seconds/60)).." m"
		else
			return math.ceil((seconds/3600)).." h"
		end
	end

end

local function write_cooldown(where,start,duration)

	local cooldown = duration - (ItemRack.CurrentTime - start)

	if start==0 then
		where:SetText("")
	elseif cooldown<3 and not where:GetText() then
		-- this is a global cooldown. don't display it. not accurate but at least not annoying
	else
		where:SetText(format_time(cooldown))
	end
end

local function notify(v1)

	local text = string.format((ItemRack_Settings.NotifyThirty=="OFF") and ItemRackText.READY or ItemRackText.READYTHIRTY,v1 or "")

	if v1 then
		PlaySound("GnomeExploration")
		if SCT_Display then
			-- send via SCT if it exists
			SCT_Display(text,{r=.2,g=.7,b=.9})
		elseif SHOW_COMBAT_TEXT=="1" then
			CombatText_AddMessage(text, CombatText_StandardScroll, .2, .7, .9) -- or default UI's SCT
		else
			-- send vis UIErrorsFrame if SCT doesn't exit
			UIErrorsFrame:AddMessage(text,.2,.7,.9,1,UIERRORS_HOLD_TIME)
		end
		DEFAULT_CHAT_FRAME:AddMessage("|cff33b2e5"..text)
		if ItemRack_Settings.EnableEvents then
			local holdarg1= arg1
			arg1 = v1
			ItemRack_RegisterFrame_OnEvent("ITEMRACK_NOTIFY")
			arg1 = holdarg1
		end
	end
end

-- populate ItemRack.NotifyList[v1] with the location of item named 'v1'
local function notify_find_item(v1)

	local found_inv,found_bag,found_slot = Rack.FindItem(nil,v1,"passive")

	if found_inv then
		ItemRack.NotifyList[v1].inv = found_inv
		ItemRack.NotifyList[v1].bag = nil
		ItemRack.NotifyList[v1].slot = nil
	elseif found_bag then
		ItemRack.NotifyList[v1].inv = nil
		ItemRack.NotifyList[v1].bag = found_bag
		ItemRack.NotifyList[v1].slot = found_slot
	else
		ItemRack.NotifyList[v1] = nil
	end
end

function ItemRack_CooldownUpdate_OnUpdate()
	if ItemRack.CooldownsNeedUpdating then
		ItemRack.CooldownsNeedUpdating = false
		update_inv_cooldowns()
	end

	if ItemRack_Settings.CooldownNumbers=="ON" then

		local i,start,duration
		ItemRack.CurrentTime = GetTime()

		if ItemRack_InvFrame:IsVisible() then
			for i=1,table.getn(ItemRack_Users[user].Bar) do
				start, duration = GetInventoryItemCooldown("player",ItemRack_Users[user].Bar[i])
				write_cooldown(getglobal("ItemRackInv"..ItemRack_Users[user].Bar[i].."Time"),start,duration)
			end
		end

		if ItemRack_MenuFrame:IsVisible() and ItemRack.InvOpen then
			for i=1,ItemRack.NumberOfItems do
				if ItemRack.BaggedItems[i].bag then
					start, duration = GetContainerItemCooldown(ItemRack.BaggedItems[i].bag,ItemRack.BaggedItems[i].slot)
					write_cooldown(getglobal("ItemRackMenu"..i.."Time"),start,duration)
				end
			end
		end

	end

	if ItemRack_Settings.Notify=="ON" then

		local i,name,start,duration,cooldown
		ItemRack.CurrentTime = GetTime()

		-- go down notify list and check up on each item used
		for i in pairs(ItemRack.NotifyList) do
			if ItemRack.NotifyList[i].inv then
				_,_,name=string.find(GetInventoryItemLink("player",ItemRack.NotifyList[i].inv) or "","^.*%[(.*)%].*$")
			else
				_,_,name=string.find(GetContainerItemLink(ItemRack.NotifyList[i].bag,ItemRack.NotifyList[i].slot) or "","^.*%[(.*)%].*$")
			end
			if i ~= name then
				notify_find_item(i) -- item has moved, go find it!
			end

			if ItemRack.NotifyList[i] then -- if item still on person (wasn't banked)
				if ItemRack.NotifyList[i].inv then -- if it has an inventory spot
					start, duration = GetInventoryItemCooldown("player",ItemRack.NotifyList[i].inv)
				else -- otherwise it's in a bag
					start, duration = GetContainerItemCooldown(ItemRack.NotifyList[i].bag,ItemRack.NotifyList[i].slot)
				end
				cooldown = (start==0) and 0 or (duration - (ItemRack.CurrentTime - start))
					if cooldown>3 then
					ItemRack.NotifyList[i].hadcooldown = true
				end
					if ItemRack.NotifyList[i].hadcooldown and (cooldown==0 or (ItemRack_Settings.NotifyThirty=="ON" and cooldown<30)) then
					notify(i)
					ItemRack.NotifyList[i] = nil
				end
			end
		end
	end

end

function ItemRack_CooldownUpdate_OnHide()

	local i

	for i=0,19 do
		getglobal("ItemRackInv"..i.."Time"):SetText("")
	end
	for i=1,30 do
		getglobal("ItemRackMenu"..i.."Time"):SetText("")
	end
end

-- changes the cooldown number on button named "button" to big/small depending on .BigCooldown
function ItemRack_SetCooldownFont(button)

	local item = getglobal(button.."Time")

	if ItemRack_Settings.BigCooldown=="ON" then
		item:SetFont("Fonts\\FRIZQT__.TTF",16,"OUTLINE")
		item:SetTextColor(1,.82,0,1)
		item:ClearAllPoints()
		item:SetPoint("CENTER",button,"CENTER")
	else
		item:SetFont("Fonts\\ARIALN.TTF",14,"OUTLINE")
		item:SetTextColor(1,1,1,1)
		item:ClearAllPoints()
		item:SetPoint("BOTTOM",button,"BOTTOM")
	end
end

-- changes all cooldown fonts
function ItemRack_SetAllCooldownFonts()

	for i=0,20 do
		ItemRack_SetCooldownFont("ItemRackInv"..i)
	end
	for i=1,30 do
		ItemRack_SetCooldownFont("ItemRackMenu"..i)
	end
end

--[[ Options ]]--

function ItemRack_OnTooltip(v1,v2)

	if ItemRack_Settings.ShowTooltips=="ON" then
		set_tooltip_anchor(this)
		GameTooltip:AddLine(v1)
		GameTooltip:AddLine(v2,.8,.8,.8,1)
		GameTooltip:Show()
	end
end

function ItemRack_Opt_OnEnter()

	local id = this:GetName()

	if ItemRack.OptInfo[id] and not Rack.TimerEnabled("ScaleUpdate") then
		ItemRack_OnTooltip(ItemRack.OptInfo[id].text,ItemRack.OptInfo[id].tooltip)
	end
end

function ItemRack_Control_OnClick()

	local id = this:GetName()

	if id=="ItemRack_Control_Rotate" and ItemRack_Users[user].Locked=="OFF" then
		-- rotate the window
		ItemRack_Users[user].MainOrient = ItemRack_Users[user].MainOrient=="HORIZONTAL" and "VERTICAL" or "HORIZONTAL"
		ItemRack_MenuFrame:Hide()
		draw_inv()
		move_control()
	elseif id=="ItemRack_Control_Lock" or id=="ItemRack_Sets_Lock" then
		-- lock/unlock the window
		ItemRack_Users[user].Locked = ItemRack_Users[user].Locked=="ON" and "OFF" or "ON"
		set_lock(ItemRack_Users[user].Locked)
	elseif id=="ItemRack_Control_Options" then
		ItemRack_Sets_Toggle()
	end
end

function ItemRack_Opt_OnClick(overrideID)

	local id = overrideID or this:GetName()

	if ItemRack.OptInfo[id] and ItemRack.OptInfo[id].info then
		local info = ItemRack.OptInfo[id].info

		if this:GetChecked() then
			ItemRack_Settings[info] = "ON"
			PlaySound("igMainMenuOptionCheckBoxOn")
		else
			ItemRack_Settings[info] = "OFF"
			PlaySound("igMainMenuOptionCheckBoxOff")
		end

		if id=="ItemRack_Opt_Bindings" then
			update_keybindings()
		elseif id=="ItemRack_Opt_CooldownNumbers" then
			ItemRack_CooldownUpdate_OnHide()
			Rack.StartTimer("CooldownUpdate",0)
		elseif id=="ItemRack_Opt_RightClick" then
			draw_inv()
		elseif id=="ItemRack_Opt_ShowIcon" then
			if ItemRack_Settings[info]=="ON" then
				ItemRack_IconFrame:Show()
			else
				ItemRack_IconFrame:Hide()
			end
		elseif id=="ItemRack_Opt_FlipBar" then
			move_control()
			draw_inv()
		elseif id=="ItemRack_Opt_CompactList" then
			ItemRack_Sets_SavedScrollFrameScrollBar:SetValue(0)
			ItemRack_Sets_SavedScrollFrame_Update()
		elseif id=="ItemRack_Opt_EnableEvents" then
			sets_message((ItemRack_Settings.EnableEvents=="ON") and "Events enabled" or "Events disabled")
		elseif id=="ItemRack_Opt_DisableToggle" then
			draw_minimap_icon()
		elseif id=="ItemRack_Opt_ShowAllEvents" then
			ItemRack_Events_ScrollFrameScrollBar:SetValue(0)
			ItemRack_Build_eventList()
		elseif id=="ItemRack_Opt_LargeFont" then
			ItemRack_ChangeEventFont()
		elseif id=="ItemRack_Opt_SquareMinimap" then
			move_icon()
		elseif id=="ItemRack_Opt_BigCooldown" then
			ItemRack_SetAllCooldownFonts()
		elseif id=="ItemRack_Opt_SetLabels" then
			draw_inv()
		end
	end
end

function ItemRack_OptList_ScrollFrame_Update()

	local i, idx, item, optinfo, optscroll, opttext, optbutton
	local offset = FauxScrollFrame_GetOffset(ItemRack_OptList_ScrollFrame)

	FauxScrollFrame_Update(ItemRack_OptList_ScrollFrame, table.getn(ItemRack.OptScroll), 11, 19 )

	for i=1,11 do
		item = getglobal("ItemRackOptList"..i)
		idx = offset+i
		if idx<=table.getn(ItemRack.OptScroll) then
			optscroll = ItemRack.OptScroll[idx]
			optinfo = ItemRack.OptInfo[optscroll.idx]
			opttext = getglobal("ItemRackOptList"..i.."CheckButtonText")
			optbutton = getglobal("ItemRackOptList"..i.."CheckButton")
			opttext:SetText(optinfo.text)
			opttext:SetTextColor(1,1,1,1)
			optbutton:Enable()
			if optscroll.dependency then
				item:SetWidth(128)
				if ItemRack_Settings[ItemRack.OptInfo[optscroll.dependency].info]=="OFF" then
					opttext:SetTextColor(.5,.5,.5,1)
					optbutton:Disable()
				end
			else
				item:SetWidth(142)
			end
			if ItemRack_Settings[optinfo.info]=="ON" then
				optbutton:SetChecked(1)
			else
				optbutton:SetChecked(0)
			end
			item:Show()
		else
			item:Hide()
		end
	end

end

-- tooltip for scrolling options
function ItemRack_OptList_OnEnter()
	local idx = FauxScrollFrame_GetOffset(ItemRack_OptList_ScrollFrame) + this:GetParent():GetID()
	local optinfo = ItemRack.OptInfo[ItemRack.OptScroll[idx].idx]
	ItemRack_OnTooltip(optinfo.text,optinfo.tooltip)
end

-- onclick for scrolling options, gets name of option and sends to _Opt_OnClick to process
function ItemRack_OptList_OnClick()
	local idx = FauxScrollFrame_GetOffset(ItemRack_OptList_ScrollFrame) + this:GetParent():GetID()
	local optinfo = ItemRack.OptInfo[ItemRack.OptScroll[idx].idx]
	ItemRack_Opt_OnClick(ItemRack.OptScroll[idx].idx)
	ItemRack_OptList_ScrollFrame_Update()
end

-- sets the state of a checkbutton to nil, 0 or 1
function ItemRack_TriStateCheck_SetState(button,value)
	local label = getglobal(button:GetName().."Text")
	button.tristate = value
	if not value then
		button:SetCheckedTexture("Interface\\Buttons\\UI-ScrollBar-Knob")
		button:SetChecked(1)
		label:SetTextColor(.5,.5,.5)
	elseif value==0 then
		button:SetCheckedTexture("Interface\\Buttons\\UI-CheckBox-Check")
		button:SetChecked(0)
		label:SetTextColor(1,1,1)
	elseif value==1 then
		button:SetCheckedTexture("Interface\\Buttons\\UI-CheckBox-Check")
		button:SetChecked(1)
		label:SetTextColor(1,1,1)
	end
end

-- rotates a checkbutton from indeterminate->unchecked->checked (for show helm/cloak)
function ItemRack_TriStateCheck_OnClick()
	if not this.tristate then
		ItemRack_TriStateCheck_SetState(this,0)
	elseif this.tristate==0 then
		ItemRack_TriStateCheck_SetState(this,1)
	elseif this.tristate==1 then
		ItemRack_TriStateCheck_SetState(this,nil)
	end
	ItemRack_TriStateCheck_Tooltip()
end

-- initializes tristate buttons to be indeterminate
function ItemRack_TriStateCheck_OnLoad()
	ItemRack_TriStateCheck_SetState(this,nil)
end

function ItemRack_TriStateCheck_Tooltip()
	local tristate_names = { ["nil"] = "Ignore", ["0"] = "Hide", ["1"] = "Show" }
	local which = (this==ItemRack_ShowHelm) and "Helm" or "Cloak"
	ItemRack_OnTooltip(which..": "..tristate_names[tostring(this.tristate)],"This determines if the "..string.lower(which).." is shown or hidden when equipped.")
end

--[[ Sets ]]--

ItemRack.SetBuild = {}

local function build_icon()

	local setname = ItemRack_Sets_Name:GetText()

	ItemRack_Sets_ChosenIcon:SetNormalTexture(ItemRack.SelectedIcon)
	ItemRack_Sets_ChosenIconName:SetText(setname)
	if Rack_User[user].Sets[setname] then
		local _,_,modifier,basekey = string.find(Rack_User[user].Sets[setname].key or "","(.).+(-.)")
		ItemRack_Sets_ChosenIconHotKey:SetText((modifier or "")..(basekey or ""))
	else
		ItemRack_Sets_ChosenIconHotKey:SetText("")
	end
end

-- initializes .SetIcons mostly, the list of icons for the set
function ItemRack_Sets_Initialize()

	local i

	ItemRack.SetIcons = {}

	for i=0,19 do
		-- add 20 spaces at start of list - this list is constructed once and only first 20 slots change
		table.insert(ItemRack.SetIcons,"")
	end

	for i=1,table.getn(ItemRackExtraIcons) do
		-- add ExtraIcons defined in ItemRackExtraIcons.lua
		table.insert(ItemRack.SetIcons,ItemRackExtraIcons[i])
	end
	for i=1,GetNumMacroIcons() do
		-- add the macro icons to choose from
		table.insert(ItemRack.SetIcons,GetMacroIconInfo(i))
	end

end

local function highlight_set_item(v1)

	if ItemRack.SetBuild[v1]==1 then
		getglobal("ItemRack_Sets_Inv"..v1.."Icon"):SetVertexColor(1,1,1,1)
		getglobal("ItemRack_Sets_Inv"..v1):SetAlpha(1)
		getglobal("ItemRack_Sets_Inv"..v1):LockHighlight()
	else
		getglobal("ItemRack_Sets_Inv"..v1.."Icon"):SetVertexColor(.4,.4,.4,1)
		getglobal("ItemRack_Sets_Inv"..v1):SetAlpha(.5)
		getglobal("ItemRack_Sets_Inv"..v1):UnlockHighlight()
	end
end

function ItemRack_Sets_UpdateInventory()

	local i,texture

	if not ItemRack.SetIcons then
		ItemRack_Sets_Initialize()
	end

	for i=0,19 do
		texture = GetInventoryItemTexture("player",i)
		if not texture then
			_,texture = GetInventorySlotInfo(string.gsub(ItemRack.Indexes[i].paperdoll_slot,"Character",""))
		end
		getglobal("ItemRack_Sets_Inv"..i.."Icon"):SetTexture(texture)
		ItemRack.SetIcons[i+1] = texture
		highlight_set_item(i)
	end
	ItemRack_Sets_ScrollFrame_Update()
end

function ItemRack_Sets_NewSet()

	local i,texture

	ItemRack_Sets_UpdateInventory()

	for i=0,19 do
		texture = GetInventoryItemTexture("player",i)
		if not texture then
			_,texture = GetInventorySlotInfo(string.gsub(ItemRack.Indexes[i].paperdoll_slot,"Character",""))
		end
		ItemRack.SetBuild[i] = ItemRack_Users[user].Inv[i] or 0
		highlight_set_item(i)
		ItemRack.SetIcons[i+1] = texture
	end

	ItemRack_Sets_Saved:Hide()
	ItemRack_Sets_Icons:Show() -- start out showing icons

	ItemRack.SelectedName = ""
	ItemRack_Sets_Name:SetText("")
	ItemRack.SelectedIcon = ItemRack.SetIcons[math.random(1,table.getn(ItemRack.SetIcons))]
	build_icon()

	ItemRack_TriStateCheck_SetState(ItemRack_ShowHelm,nil)
	ItemRack_TriStateCheck_SetState(ItemRack_ShowCloak,nil)

	for i=1,25 do
		getglobal("ItemRack_Sets_Icon"..i):UnlockHighlight()
	end

	ItemRack_Sets_NameLabel:SetText(ItemRackText.SETS_NAMELABEL_TEXT)
	ItemRack_Tab() -- flip to tab 2 (sets)

	ItemRack_SetsFrame:Show()
	ItemRack_Sets_Name:ClearFocus()

end

local function validate_set_buttons()

	local setname,found = ItemRack_Sets_Name:GetText()

	if Rack_User[user].Sets[setname] then
		ItemRack_Sets_RemoveButton:Enable()
		ItemRack_Sets_BindButton:Enable()
		ItemRack_Sets_HideSet:Enable()
		ItemRack_Sets_HideSetText:SetTextColor(1,1,1)
	else
		ItemRack_Sets_RemoveButton:Disable()
		ItemRack_Sets_BindButton:Disable()
		ItemRack_Sets_HideSet:Disable()
		ItemRack_Sets_HideSetText:SetTextColor(.4,.4,.4)
	end

	for i=0,19 do
		found = found or (ItemRack.SetBuild[i]==1)
	end
	setname = (found and setname) or nil

	if setname and string.len(setname)>0 then
		ItemRack_Sets_SaveButton:Enable()
	else
		ItemRack_Sets_SaveButton:Disable()
	end

	build_icon()
end

function ItemRack_Sets_InvToggle()

	local id = this:GetID()

	this:SetChecked(0)
	ItemRack.SetBuild[id] = 1-(ItemRack.SetBuild[id] or 0)

	highlight_set_item(id)

	if IsAltKeyDown() then

		if ItemRack_Users[user].Inv[id] and ItemRack.SetBuild[id]==0 then
			remove_inv(id)
		elseif not ItemRack_Users[user].Inv[id] and ItemRack.SetBuild[id]==1 then
			ItemRack_Users[user].Visible="ON"
			ItemRack_Users[user].Inv[id] = 1
			getglobal("ItemRackInv"..id.."Icon"):SetTexture(get_item_info(id))
			table.insert(ItemRack_Users[user].Bar,id)
			draw_inv()
		end
	end

	if ItemRack.SetBuild[id]==1 then
		ItemRack_BuildMenu(id,"SET")
	end

	validate_set_buttons()
end

function ItemRack_Sets_ScrollFrame_Update()

	local i, item, texture, idx
	local offset = FauxScrollFrame_GetOffset(ItemRack_Sets_ScrollFrame)

	FauxScrollFrame_Update(ItemRack_Sets_ScrollFrame, ceil(table.getn(ItemRack.SetIcons) / 5) , 5, 24 )
	
	for i=1,25 do
		item = getglobal("ItemRack_Sets_Icon"..i)
		idx = (offset*5) + i
		if idx<=table.getn(ItemRack.SetIcons) then
			texture = ItemRack.SetIcons[idx]
			item:SetNormalTexture(texture)
			item:SetPushedTexture(texture)
			item:Show()
		else
			item:Hide()
		end
		if ItemRack.SetIcons[idx]==ItemRack.SelectedIcon then
			item:LockHighlight()
		else
			item:UnlockHighlight()
		end
	end
end

function ItemRack_Sets_Icon_OnClick()

	local id = this:GetID()
	local offset = FauxScrollFrame_GetOffset(ItemRack_Sets_ScrollFrame)
	local idx = offset*5+id

	if idx<=table.getn(ItemRack.SetIcons) then
		ItemRack.SelectedIcon = ItemRack.SetIcons[idx]
		ItemRack_Sets_ScrollFrame_Update()
	end
	build_icon()
end

function ItemRack_Sets_Name_OnTextChanged()

	ItemRack.SelectedName = ItemRack_Sets_Name:GetText()
	ItemRack_Sets_ChosenIconName:SetText(ItemRack.SelectedName)
	validate_set_buttons()
end

-- save button clicked
function ItemRack_Sets_Save_OnClick()

	local setname = ItemRack_Sets_Name:GetText()
	local itemcount, itemname, itemslot = 0
	local oldkey, oldkeyindex

	ItemRack_Sets_Name:ClearFocus()

	-- if a 2H weapon in mainhand, ignore offhand (don't try to equip empty slot to offhand)
	_,_,itemslot = get_item_info(16)
	if itemslot=="INVTYPE_2HWEAPON" then
		ItemRack.SetBuild[17] = nil
	end

	if string.len(setname)>0 then
		if Rack_User[user].Sets[setname] then
			-- grab old key binding before recreating set
			oldkey = Rack_User[user].Sets[setname].key
			oldkeyindex = Rack_User[user].Sets[setname].keyindex
		end
		Rack_User[user].Sets[setname] = { icon=ItemRack.SelectedIcon, key=oldkey, keyindex=oldkeyindex }
		for i=0,19 do
			if ItemRack.SetBuild[i]==1 then
				Rack_User[user].Sets[setname][i] = {}
				itemcount = itemcount + 1
				_,itemname = get_item_info(i)
				Rack_User[user].Sets[setname][i].name = itemname or "(empty)"
				_,Rack_User[user].Sets[setname][i].id = Rack.GetItemInfo(i)
			end
		end
		sets_message(string.format(ItemRackText.SAVED,setname,itemcount))
		Rack_User[user].Sets[setname].hide = ItemRack_Sets_HideSet:GetChecked()
		Rack_User[user].Sets[setname].showhelm = ItemRack_ShowHelm.tristate
		Rack_User[user].Sets[setname].showcloak = ItemRack_ShowCloak.tristate
	end
	draw_minimap_icon()
	validate_set_buttons()
end

-- equips a set by name, usable in macros. now a wrapper to Rack.EquipSet()
function ItemRack_EquipSet(setname)

	if not setname and ItemRack.EventSetName then
		setname = ItemRack.EventSetName
	end

	Rack.EquipSet(setname)
end

-- hitting the dropdown button toggles between icons and savedsets
function ItemRack_Sets_DropDownButton_OnClick()

	ItemRack_Sets_Name:ClearFocus()
	ItemRack_Sets_Build_Dropdown()
	ItemRack_Sets_SubFrame2:Hide()
	ItemRack_Sets_SubFrame4:Hide()
	ItemRack_Sets_SetSelect:Show()
	ItemRack_Sets_Saved:Show()
end

-- clicking one of the drop-down saved sets
function ItemRack_Sets_Saved_OnClick(arg1)

	local id = this:GetID()
	local idx = FauxScrollFrame_GetOffset(ItemRack_Sets_SavedScrollFrame) + id
	local i,setname

	ItemRack_Sets_SetSelect:Hide()

	if ItemRack.SetsListSize<2 then
		-- do nothing
	elseif ItemRack.SelectedTab==2 then
		-- chose a set from the Sets tab (SubFrame2)
		setname = ItemRack.SetsList[idx].Name
		ItemRack_Sets_Name:SetText(setname)
		ItemRack.SelectedIcon = ItemRack.SetsList[idx].Icon
		ItemRack.SelectedName = setname
		build_icon()
		for i=0,19 do
			ItemRack.SetBuild[i] = Rack_User[user].Sets[setname][i] and 1 or 0
			highlight_set_item(i)
		end
		ItemRack_Sets_HideSet:SetChecked(Rack_User[user].Sets[setname].hide)
		ItemRack_TriStateCheck_SetState(ItemRack_ShowHelm,Rack_User[user].Sets[setname].showhelm)
		ItemRack_TriStateCheck_SetState(ItemRack_ShowCloak,Rack_User[user].Sets[setname].showcloak)
		ItemRack_EquipSet(setname)
	elseif ItemRack.SelectedTab==4 then
		-- chose a set from the Events tab (SubFrame4)
		setname = ItemRack.SetsList[idx].Name
		if not ItemRack_Users[user].Events[eventList[ItemRack.SelectedEvent].name] then
			ItemRack_Users[user].Events[eventList[ItemRack.SelectedEvent].name] = { setname=setname, enabled=1 }
		else
			ItemRack_Users[user].Events[eventList[ItemRack.SelectedEvent].name].setname = setname
		end
		ItemRack_Build_eventList()
	end

end

-- gather all sets into a numerically-indexes list to be used by SavedScrollFrame
function ItemRack_Sets_Build_Dropdown()

	local idx,i,j,count=1

	ItemRack.SetsList = ItemRack.SetsList or {}

	for i in pairs(Rack_User[user].Sets) do
		if not string.find(i,"^ItemRack-") and not string.find(i,"^Rack-") then -- skip special sets (ItemRack-Queue and ItemRack-Normal)
			count = 0
			ItemRack.SetsList[idx] = ItemRack.SetsList[idx] or {}
			ItemRack.SetsList[idx].Icon = Rack_User[user].Sets[i].icon
			ItemRack.SetsList[idx].Name = i
			for j=0,19 do
				if Rack_User[user].Sets[i][j] then
					count = count + 1
				end
			end
			ItemRack.SetsList[idx].Count = string.format(ItemRackText.COUNTFORMAT,count)
			ItemRack.SetsList[idx].Key = Rack_User[user].Sets[i].key
			ItemRack.SetsList[idx].Hide = Rack_User[user].Sets[i].hide

			idx = idx +1
		end
	end
	ItemRack.SetsListSize = idx

	-- sort drop-down list alphabetically
	table.sort(ItemRack.SetsList,function(e1,e2) if e1 and e2 and (e1.Name<e2.Name) then return true else return false end end)
	ItemRack_Sets_SavedScrollFrameScrollBar:SetValue(0)
	ItemRack_Sets_SavedScrollFrame_Update()

end

-- scrollbar update for saved sets (dropdown)
function ItemRack_Sets_SavedScrollFrame_Update()

	local i, idx, item
	local offset = FauxScrollFrame_GetOffset(ItemRack_Sets_SavedScrollFrame)

	local listsize,listheight,liststub,compact = 8,28,"ItemRack_Sets_Saved",nil

	for i=1,8 do
		getglobal("ItemRack_Sets_Saved"..i):Hide()
	end
	for i=1,11 do
		getglobal("ItemRack_Sets_Compact"..i):Hide()
	end

	if ItemRack_Settings.CompactList=="ON" then
		listsize,listheight,liststub,compact = 11,21,"ItemRack_Sets_Compact",1
	end

	FauxScrollFrame_Update(ItemRack_Sets_SavedScrollFrame, ItemRack.SetsListSize-1, listsize, listheight )

	for i=1,listsize do
		idx = offset + i
		if idx<ItemRack.SetsListSize then
			getglobal(liststub..i.."Name"):SetText(ItemRack.SetsList[idx].Name)
			getglobal(liststub..i.."Icon"):SetTexture(ItemRack.SetsList[idx].Icon)
			if not compact then
				getglobal(liststub..i.."Count"):SetText(ItemRack.SetsList[idx].Count)
				getglobal(liststub..i.."Key"):SetText(ItemRack.SetsList[idx].Key)
			end
			if Rack_User[user].Sets[ItemRack.SetsList[idx].Name].hide then
				getglobal(liststub..i.."Name"):SetTextColor(.5,.5,.5)
				if not compact then
					getglobal(liststub..i.."Count"):SetTextColor(.5,.5,.5)
					getglobal(liststub..i.."Key"):SetTextColor(.5,.5,.5)
				end
			else
				getglobal(liststub..i.."Name"):SetTextColor(1,1,1)
				if not compact then
					getglobal(liststub..i.."Count"):SetTextColor(1,1,1)
					getglobal(liststub..i.."Key"):SetTextColor(1,1,1)
				end
			end
			getglobal(liststub..i):Show()
		else
			getglobal(liststub..i):Hide()
		end
	end

	if ItemRack.SetsListSize<=1 then
		ItemRack_Sets_Saved1Icon:SetTexture("")
		ItemRack_Sets_Saved1Name:SetText(ItemRackText.NOSAVEDSETS)
		ItemRack_Sets_Saved1Count:SetText("")
		ItemRack_Sets_Saved1Key:SetText("")
		ItemRack_Sets_Saved1:Show()
		for i=2,8 do
			getglobal("ItemRack_Sets_Saved"..i):Hide()
		end
		for i=1,11 do
			getglobal("ItemRack_Sets_Compact"..i):Hide()
		end
	end

end

-- displays a tooltip of a set's contents, set show_contents to override TinyTooltip option
function ItemRack_Sets_Tooltip(setname,show_contents)

	local count,missing,r,g,b,i,name,inv,bag = 0,0

	setname = setname or ItemRack.SelectedName

	if setname and Rack_User[user].Sets[setname] then

		set_tooltip_anchor(this)
		GameTooltip:AddLine(string.format(ItemRackText.SETTOOLTIPFORMAT,setname))
		for i=0,19 do
			if Rack_User[user].Sets[setname][i] then
				name = Rack_User[user].Sets[setname][i].name or "(empty)"
				count = count + 1
				inv,bag = Rack.FindItem(nil,name,"passive")
				if ItemRack.BankedItems[name] then
					r,g,b = .3,.5,1
				elseif inv or bag or name=="(empty)" then
					r,g,b = .85,.85,.85
				else
					r,g,b = 1,.1,.1 -- mark missing items red
					missing = missing + 1
				end
				if ItemRack_Settings.TinyTooltip=="OFF" or show_contents then
					GameTooltip:AddLine(ItemRack.Indexes[i].name..": "..tostring(name),r,g,b)
				end
			end
		end
		if ItemRack_Settings.TinyTooltip=="ON" and not show_contents then
			-- if Tiny Tooltip enabled, just show a count
			GameTooltip:AddLine(string.format(ItemRackText.SETTOOLTIPCOUNT,count),.85,.85,.85)
			if missing>0 then
				GameTooltip:AddLine(string.format(ItemRackText.SETTOOLTIPMISSING,missing),1,.1,.1)
			end
		end
		GameTooltip:Show()
	end
end	

-- tooltips displaying contents of a set in the dropdown list
function ItemRack_Sets_Saved_OnEnter()

	if ItemRack.SetsListSize>1 then
		local id = this:GetID()
		local idx = FauxScrollFrame_GetOffset(ItemRack_Sets_SavedScrollFrame) + id
		local setname = ItemRack.SetsList[idx].Name
		ItemRack_Sets_Tooltip(setname,1) -- ,1 forces contents to show in tooltip
	end
end

--[[ Key bindings ]]--

-- returns a number 1-10 (or MaxKeyBindings) of an available key binding, nil if none are free
local function get_free_binding()

	local i,j,found,freeindex

	for i=1,ItemRackText.MaxKeyBindings do
		found = false
		for j in pairs(Rack_User[user].Sets) do
			if Rack_User[user].Sets[j].keyindex == i then
				found = true
			end
		end
		if not found then
			freeindex = i
			i = ItemRackText.MaxKeyBindings+1 -- whimpy break
		end
	end

	return freeindex
end

-- removes a key by index, 1-10 (or .MaxKeyBindings)
local function unbind_key_index(idx)

	if idx then
		local oldkey = GetBindingKey("EQUIPSET"..idx)
		if oldkey then
			SetBinding(oldkey)
			setglobal("BINDING_NAME_EQUIPSET"..idx,string.format(ItemRackText.BINDINGFORMAT,idx))
		end
	end
end

-- removes old key bindings, for initialization and preparation to bind a key
-- if no setname given, then all keys are unbound
local function unbind_keys(setname)

	local idx

	if not setname then
		for idx=1,ItemRackText.MaxKeyBindings do
			unbind_key_index(idx)
		end
	else
		idx = Rack_User[user].Sets[setname].keyindex
		unbind_key_index(idx)
	end
	SaveBindings(GetCurrentBindingSet())

end	

-- this takes the current key bindings and updates the .Sets info -- usually as a result of user changing bindings outside the mod
function ItemRack_AgreeOnKeyBindings()
	local i,oldkey,setname

	if ItemRack.KeyBindingsSettled then
		-- don't do this at startup, wait until we had a chance to initialize this player's keys

		for i in pairs(Rack_User[user].Sets) do
			Rack_User[user].Sets[i].key = nil
			Rack_User[user].Sets[i].keyindex = nil
		end

		for i=1,10 do
			oldkey = GetBindingKey("EQUIPSET"..i)
			if oldkey then
				_,_,setname = string.find(getglobal("BINDING_NAME_EQUIPSET"..i) or "",ItemRackText.BINDINGSEARCH)
				if setname and Rack_User[user].Sets[setname] then
					Rack_User[user].Sets[setname].key = oldkey
					Rack_User[user].Sets[setname].keyindex = i
				end
			end
		end
	end
end

-- removes the currently selected set
function ItemRack_Sets_Remove_OnClick()

	if Rack_User[user].Sets[ItemRack.SelectedName] then
		unbind_keys(ItemRack.SelectedName)
		Rack_User[user].Sets[ItemRack.SelectedName] = nil
		sets_message(string.format(ItemRackText.SETREMOVE,ItemRack.SelectedName))
		local i
		-- if an event has this set, remove the association
		for i in pairs(ItemRack_Users[user].Events) do
			if ItemRack_Users[user].Events[i].setname==ItemRack.SelectedName then
				ItemRack_Users[user].Events[i] = nil
			end
		end
		ItemRack.SelectedName=nil
		ItemRack_Sets_Name:SetText("")
		build_icon()
		validate_set_buttons()
		ItemRack_Build_eventList()
	end
end

function ItemRack_UseSetBinding(v1)

	local i,setname

	-- look for sets with this key index
	for i in pairs(Rack_User[user].Sets) do
		if Rack_User[user].Sets[i].keyindex == v1 then
			setname = i
		end
	end

	-- if we found a set with this key index, equip it
	if setname then
		ItemRack_EquipSet(setname)
	end
end

local function bind_key_to_set(key,setname)

	-- check if another set has this key binding
	local i
	for i in pairs(Rack_User[user].Sets) do
		if Rack_User[user].Sets[i].key == key then
			unbind_keys(i) -- remove the other set's key binding if so
			Rack_User[user].Sets[i].keyindex = nil -- remove them from our table
			Rack_User[user].Sets[i].key = nil
		end
	end

	-- get the next free key binding
	local idx = Rack_User[user].Sets[setname].keyindex or get_free_binding()

	if idx then -- if we previously had a key binding or there's space for a new one, continue
			-- associate the key and index (1-10) with the set
		Rack_User[user].Sets[setname].key = key
		Rack_User[user].Sets[setname].keyindex = idx
		-- release any binding this key previously had
		unbind_keys(setname)

		-- finally bind the key
		ItemRack.KeyBindingsSettled = nil -- don't do an agree on key bindings until set
		local success = SetBinding(key,"EQUIPSET"..idx)
		if success then
			setglobal("BINDING_NAME_EQUIPSET"..idx,string.format(ItemRackText.BINDINGFORMAT,setname))
			sets_message(string.format(ItemRackText.BINDSET,key))
		else
			-- couldn't bind the key, forget we tried (should never get to this part)
			Rack_User[user].Sets[setname].key = nil
			Rack_User[user].Sets[setname].keyindex = nil
		end
		ItemRack.KeyBindingsSettled = 1
		SaveBindings(GetCurrentBindingSet())
		ItemRack_KeyBindFrame:Hide()
	end
end

-- hitting a key when "Press a key" mode up
function ItemRack_Sets_KeyBind_OnKeyDown(arg1)

	local setname = ItemRack.SelectedName

	if arg1=="ESCAPE" then
		sets_message(ItemRackText.BINDCLEAR)
		ItemRack_KeyBindFrame:Hide()

		-- if this set has a key, restore its name and release the key binding
		unbind_keys(setname)
		Rack_User[user].Sets[setname].key = nil
		Rack_User[user].Sets[setname].keyindex = nil

	elseif arg1~="SHIFT" and arg1~="ALT" and arg1~="CTRL" then
		local key = arg1
		if IsShiftKeyDown() then
			key = "SHIFT-"..key
		elseif IsAltKeyDown() then
			key = "ALT-"..key
		elseif IsControlKeyDown() then
			key = "CTRL-"..key
		end

		local action
		action = GetBindingAction(key)
		if action and action~="" then
			StaticPopupDialogs["ITEMRACKKEYCONFIRM"] = {
				text = key.." is already used for "..tostring(getglobal("BINDING_NAME_"..action)).."\n\nOverwrite?",
				button1 = "Yes",
				button2 = "No",
				OnAccept = function() bind_key_to_set(key,setname) end,
				OnCancel = function() sets_message("No key binding set.") end,
				timeout = 0,
				whileDead = 1
			}
			StaticPopup_Show("ITEMRACKKEYCONFIRM")
		else
			bind_key_to_set(key,setname)
		end
	end

	build_icon()

end

function ItemRack_InitializeKeyBindings()

	local i, keyidx

	unbind_keys() -- remove all 10 keybindings (from previous users perhaps)

	for i in pairs(Rack_User[user].Sets) do
		keyidx = Rack_User[user].Sets[i].keyindex
		if keyidx then
			setglobal("BINDING_NAME_EQUIPSET"..keyidx,string.format(ItemRackText.BINDINGFORMAT,i))
			SetBinding(Rack_User[user].Sets[i].key,"EQUIPSET"..keyidx)
		end
	end
	SaveBindings(GetCurrentBindingSet())
	ItemRack.KeyBindingsSettled = true
end

function ItemRack_Sets_Inv_OnEnter()

	local id = this:GetID()

	if ItemRack.SetBuild and ItemRack.SetBuild[id]==1 then
		ItemRack_BuildMenu(id,"SET")
	end

end

function ItemRack_Sets_ChosenIcon_OnClick()

	if IsAltKeyDown() then

		if ItemRack_Users[user].Inv[20] then
			remove_inv(20)
		elseif not ItemRack_Users[user].Inv[20] then
			ItemRack_Users[user].Visible="ON"
			ItemRack_Users[user].Inv[20] = 1
			ItemRackInv20Icon:SetTexture(get_item_info(20))
			table.insert(ItemRack_Users[user].Bar,20)
			draw_inv()
		end
	end

	validate_set_buttons()
end

--[[ Minimap button ]]--

function ItemRack_Sets_Toggle(v1)

	if v1 then
		if not getglobal("ItemRack_Sets_SubFrame"..v1):IsVisible() then
			ItemRack_SetsFrame:Hide()
		end
	end

	if ItemRack_SetsFrame:IsVisible() then
		ItemRack_SetsFrame:Hide()
	else
		ItemRack_Sets_NewSet()
	end
	if v1 then
		ItemRack_Tab(v1)
	end
end

-- when user clicks the minimap button
function ItemRack_IconFrame_OnClick(arg1)

	if arg1=="LeftButton" and ItemRack_Settings.DisableToggle=="OFF" then
		-- toggle bar
		ItemRack_Toggle()
	elseif arg1=="LeftButton" then
		-- toggle menu		
		if ItemRack_MenuFrame:IsVisible() then
			ItemRack_MenuFrame:Hide()
		else
			ItemRack_BuildMenu(20,"MINIMAP")
		end
	else
		ItemRack_Sets_Toggle()
	end

end

--[[ Tabs ]]--

function ItemRack_Tab(v1)

	ItemRack_Sets_SetSelect:Hide()
	ItemRack_EditEvent:Hide()
	ItemRack.SelectedTab = (v1 or ItemRack.SelectedTab) or 1
	for i=1,4 do
		getglobal("ItemRack_Sets_Tab"..i):UnlockHighlight()
		getglobal("ItemRack_Sets_SubFrame"..i):Hide()
	end
	getglobal("ItemRack_Sets_Tab"..ItemRack.SelectedTab):LockHighlight()
	getglobal("ItemRack_Sets_SubFrame"..ItemRack.SelectedTab):Show()
end

-- when set chooser dropdown shown
function ItemRack_SetSelect_OnShow()
  -- remove ItemRack_SetsFrame from UISpecialFrames and add ItemRack_Sets_SetSelect
	make_escable("ItemRack_SetsFrame","remove")
	make_escable("ItemRack_Sets_SetSelect","add")
end

-- when set chooser dropdown hidden
function ItemRack_SetSelect_OnHide()
	-- remove ItemRack_Sets_SetSelect from UISpecialFrames and add ItemRack_SetsFrame
	make_escable("ItemRack_Sets_SetSelect","remove")
	make_escable("ItemRack_SetsFrame","add")
	getglobal("ItemRack_Sets_SubFrame"..ItemRack.SelectedTab):Show()
end

-- when event editor shown
function ItemRack_EditEvent_OnShow()
	ItemRack_Sets_SubFrame4:Hide()
	make_escable("ItemRack_SetsFrame","remove")
	make_escable("ItemRack_EditEvent","add")
end

-- when event editor hidden
function ItemRack_EditEvent_OnHide()
	make_escable("ItemRack_EditEvent","remove")
	make_escable("ItemRack_SetsFrame","add")
	ItemRack_Sets_SubFrame4:Show()
end

-- when the sets frame is hidden, enable all events if events are on
function ItemRack_SetsFrame_OnHide()
	ItemRack_MenuFrame:Hide()
	ItemRack_EnableAllEvents()
end

-- when the sets frame is shown, disable all events
function ItemRack_SetsFrame_OnShow()
	if not ItemRack.SetIcons then
		ItemRack_Sets_Initialize()
	end
	ItemRack_DisableAllEvents()
	if ItemRack_Settings.EnableEvents=="ON" then
		sets_message(ItemRackText.EVENTSSUSPENDED)
	end
end

--[[ Events ]]--

local function check_for_titanrider()

	if TITAN_RIDER_ID and (not TitanRider_EquipToggle or (TitanGetVar and TitanGetVar(TITAN_RIDER_ID,"EquipItems"))) then
		StaticPopupDialogs["ITEMRACK_TITANRIDER"] = {
			text = "It appears you have Titan Rider (a module of Titan Panel) enabled.  Do not use this Mount event with Titan Rider enabled or it will create a mess (items on cursor, gear swapping back).",
			button1 = "Ok", timeout = 0, whileDead = 1, showAlert = 1, hideOnEscape = 1 }
		StaticPopup_Show("ITEMRACK_TITANRIDER")
		return 1
	else
		return nil
	end
end

-- changes the font size in the event edit window
function ItemRack_ChangeEventFont()
	if ItemRack_Settings.LargeFont=="ON" then
		ItemRack_EventScript:SetFont("Fonts\\ARIALN.TTF",15)
	else
		ItemRack_EventScript:SetFont("Fonts\\FRIZQT__.TTF",11)
	end
end

-- builds eventList, a numerically-indexed list of events and their associated sets
function ItemRack_Build_eventList()

	local i
	eventListSize = 1
	local oldeventname,eventname

	if ItemRack.SelectedEvent>0 then
		-- remember old selected event, it may move in the list
		oldeventname = eventList[ItemRack.SelectedEvent].name
	end

	scratchTableSize[1] = 1 -- size of each table for secondary sort
	scratchTableSize[2] = 1

	-- problems with secondary sort, so doing one manually - first split events into two tables: ones with a setname, ones without
	for i in pairs(ItemRack_Events) do
		-- first split 
		if i~="events_version" then
			_,_,class = string.find(i,"^(.+)%:")
			if ItemRack_Settings.ShowAllEvents=="ON" or (not class or class==UnitClass("player")) then
				if ItemRack_Users[user].Events[i] and Rack_User[user].Sets[ItemRack_Users[user].Events[i].setname] then
					scratchTable[1][scratchTableSize[1]] = i
					scratchTableSize[1] = scratchTableSize[1] + 1
				else
					scratchTable[2][scratchTableSize[2]] = i
					scratchTableSize[2] = scratchTableSize[2] + 1
				end
			end
		end
	end
	scratchTable[1][scratchTableSize[1]] = nil
	scratchTable[2][scratchTableSize[2]] = nil

	-- sort each half
	table.sort(scratchTable[1],function(e1,e2) return e1 and e2 and e1<e2 end)
	table.sort(scratchTable[2],function(e1,e2) return e1 and e2 and e1<e2 end)

	-- merge the halves
	eventListSize = 1
	for i=1,scratchTableSize[1]-1 do
		eventname = scratchTable[1][i]
		if ItemRack_Events[eventname] then
			eventList[eventListSize] = eventList[eventListSize] or {}
			eventList[eventListSize].name = eventname
			eventList[eventListSize].trigger = ItemRack_Events[eventname].trigger
			eventList[eventListSize].delay = ItemRack_Events[eventname].delay
			eventList[eventListSize].script = ItemRack_Events[eventname].script
			eventList[eventListSize].setname = ItemRack_Users[user].Events[eventname].setname
			eventList[eventListSize].texture = Rack_User[user].Sets[eventList[eventListSize].setname].icon
			eventList[eventListSize].enabled = ItemRack_Users[user].Events[eventname].enabled
			eventListSize = eventListSize + 1
		end
	end
	for i=1,scratchTableSize[2]-1 do
		eventname = scratchTable[2][i]
		if ItemRack_Events[eventname] then
			eventList[eventListSize] = eventList[eventListSize] or {}
			eventList[eventListSize].name = eventname
			eventList[eventListSize].trigger = ItemRack_Events[eventname].trigger
			eventList[eventListSize].delay = ItemRack_Events[eventname].delay
			eventList[eventListSize].script = ItemRack_Events[eventname].script
			eventList[eventListSize].setname = nil
			eventList[eventListSize].texture = nil
			eventList[eventListSize].enabled = nil
			eventListSize = eventListSize + 1
		end
	end

	ItemRack.SelectedEvent=0
	for i=1,eventListSize-1 do
		if eventList[i].name==oldeventname then
			ItemRack.SelectedEvent = i
		end
	end

	ItemRack_Validate_EventList_Buttons()
	ItemRack_Events_ScrollFrame_Update()
end

-- update for the list of events scrollframe
function ItemRack_Events_ScrollFrame_Update()

	local i, texture, idx, button, icon, enable, name
	local offset = FauxScrollFrame_GetOffset(ItemRack_Events_ScrollFrame)

	FauxScrollFrame_Update(ItemRack_Events_ScrollFrame, eventListSize-1, 7, 26 )
	
	for i=1,7 do
		idx = offset + i
		button = getglobal("ItemRack_Event"..i)
		if idx<eventListSize then
			if ItemRack_Settings.ShowAllEvents=="ON" then
				getglobal("ItemRack_Event"..i.."Name"):SetText(eventList[idx].name)
			else
				_,_,name = string.find(eventList[idx].name,"^.+%:(.+)")
				name = name or eventList[idx].name
				getglobal("ItemRack_Event"..i.."Name"):SetText(name)
			end
			icon = getglobal("ItemRack_Event"..i.."Icon")
			enable = getglobal("ItemRack_Event"..i.."Enable")
			if eventList[idx].setname then
				icon:SetNormalTexture(eventList[idx].texture)
				icon:SetPushedTexture(eventList[idx].texture)
				enable:SetChecked(eventList[idx].enabled)
				enable:Show()
			else
				icon:SetNormalTexture("Interface\\Icons\\INV_Misc_QuestionMark")
				icon:SetPushedTexture("Interface\\Icons\\INV_Misc_QuestionMark")
				enable:Hide()
			end
			button:Show()
		else
			button:Hide()
		end
		if idx==ItemRack.SelectedEvent then
			button:LockHighlight()
		else
			button:UnlockHighlight()
		end
	end
end

-- onclick for events in the list, to lock highlight
function ItemRack_EventsList_OnClick(arg1)

	local idx = this:GetID() + FauxScrollFrame_GetOffset(ItemRack_Events_ScrollFrame)

	if idx<eventListSize then
		ItemRack.SelectedEvent = (ItemRack.SelectedEvent==idx) and 0 or idx
	end
	ItemRack_Validate_EventList_Buttons()
	ItemRack_Events_ScrollFrame_Update()
end

-- returns the eventList index for the *parent* of "this"
local function event_idx()
	return this:GetParent():GetID() + FauxScrollFrame_GetOffset(ItemRack_Events_ScrollFrame)
end

function ItemRack_EventsList_EnableOnClick()

	local idx = event_idx()

	if idx<eventListSize then
		local eventname = eventList[idx].name
		if not ItemRack_Users[user].Events[eventname] then
			ItemRack_Users[user].Events[eventname] = {}
		end
		ItemRack_Users[user].Events[eventname].enabled = this:GetChecked()
		if eventname=="Mount" and check_for_titanrider() then
			ItemRack_Users[user].Events[eventname].enabled = nil
		end
	end
	ItemRack_Build_eventList()
end

-- onenter of an event icon either a tooltip for an undefined event or the contents of the set
function ItemRack_EventsListIcon_OnEnter()

	local idx = event_idx()

	if idx<eventListSize then
		local setname = eventList[idx].setname
		if not setname then
			ItemRack_OnTooltip(ItemRackText.UNDEFINEDEVENT_TEXT,ItemRackText.UNDEFINEDEVENT_TOOLTIP)
		else
			ItemRack_Sets_Tooltip(setname)
		end
	end
end

function ItemRack_Validate_EventList_Buttons()

	if ItemRack.SelectedEvent==0 then
		ItemRack_Events_DeleteButton:Disable()
		ItemRack_Events_EditButton:Disable()
	else
		ItemRack_Events_DeleteButton:Enable()
		ItemRack_Events_EditButton:Enable()
	end
end

-- clicking event icon summons set selection window
function ItemRack_EventsListIcon_OnClick()

	local idx = event_idx()

	if idx<eventListSize then
		if eventList[idx].name=="Mount" and check_for_titanrider() then
			return
		else
			ItemRack.SelectedEvent = idx
			ItemRack_Events_ScrollFrame_Update()
			ItemRack_Validate_EventList_Buttons()
			ItemRack_Sets_DropDownButton_OnClick()
		end
	end
end

-- handler for buttons clicked in the events pane
function ItemRack_EventButtons(v1)

	local i
	local others = ""

	if v1=="Delete" then
		local eventname = eventList[ItemRack.SelectedEvent].name

		ItemRack_DisableEvent(eventname)

		i = ItemRack_Users[user].Events[eventname]
		ItemRack_Users[user].Events[eventname] = nil

		if not i then

			for i in pairs(ItemRack_Users) do
				if ItemRack_Users[i].Events and ItemRack_Users[i].Events[eventname] then
					others = others..i..", "
				end
			end

			if others=="" then
				sets_message("\""..eventname.."\" removed completely.")
				ItemRack_Events[eventname] = nil
			else
				others = string.gsub(others,", $","")
				sets_message("\""..eventname.."\" is used by: "..others)
			end
		else
			sets_message("\""..eventname.."\" disassociated.")
		end
		ItemRack_Events_ScrollFrameScrollBar:SetValue(0)
	elseif v1=="New" then
		ItemRack_EventScript:SetText("")
		ItemRack_EventName:SetText("")
		ItemRack_EventTrigger:SetText("")
		ItemRack_EventDelay:SetText("")
		ItemRack_EditEvent:Show()
		ItemRack_EventName:SetFocus()
	elseif v1=="Edit" then
		if ItemRack.SelectedEvent==0 then
			-- if we're here and SelectedEvent==0, it was a double-click to a selected event. Reselect it
			ItemRack_EventsList_OnClick("LeftButton")
		end
		-- if SelectedEvent defined, then either edit button or double-click to previously unselected event
		local eventname = eventList[ItemRack.SelectedEvent].name

		ItemRack_EventScript:SetText(ItemRack_Events[eventname].script or "")
		ItemRack_EventName:SetText(eventname)
		ItemRack_EventTrigger:SetText(ItemRack_Events[eventname].trigger or "")
		ItemRack_EventDelay:SetText(ItemRack_Events[eventname].delay or "")
		ItemRack_EditEvent:Show()
		ItemRack_EventScript:SetFocus()

	elseif v1=="Save" then
		local eventname = eventList[ItemRack.SelectedEvent] and eventList[ItemRack.SelectedEvent].name
		local name = ItemRack_EventName:GetText()
		if name and string.len(name)>0 then
			if not ItemRack_Events[name] then
				ItemRack_Events[name] = {}
			end
			ItemRack_Events[name].script = ItemRack_EventScript:GetText()
			ItemRack_Events[name].trigger = ItemRack_EventTrigger:GetText()
			ItemRack_Events[name].delay = tonumber(ItemRack_EventDelay:GetText()) or 0
			sets_message("Event \""..name.."\" saved.")
			ItemRack_EditEvent:Hide()
		else
			sets_message("Event not saved.  Need a name at least.")
		end
	elseif v1=="Test" then
		RunScript(ItemRack_EventScript:GetText() or "")
	end
	ItemRack_Build_eventList()
end

--[[ Event Registration ]]--

ItemRack.Register = {} -- game events (UNIT_AURA, etc) are stored here

-- debug function, to list registered game events and the mod events they are for
function ItemRack_ListEvents()
	local i,j,foundtrigger,foundevent

	for i in pairs(ItemRack.Register) do
		foundtrigger=1
		foundevent=nil
		DEFAULT_CHAT_FRAME:AddMessage("__"..i.."__")
		for j in pairs(ItemRack.Register[i]) do
			foundevent=1
			DEFAULT_CHAT_FRAME:AddMessage(j)
		end
		if not foundevent then
			DEFAULT_CHAT_FRAME:AddMessage("none")
		end
	end
	if not foundtrigger then
		DEFAULT_CHAT_FRAME:AddMessage("No events registered.")
	end
end

-- enables a specific eventname ("Riding","Warrior:Berserk",etc)
function ItemRack_EnableEvent(eventname)

	if not ItemRack_Events[eventname] then return end

	local trigger = ItemRack_Events[eventname].trigger

	if not ItemRack.Register[trigger] then
		ItemRack_RegisterFrame:RegisterEvent(trigger)
		ItemRack.Register[trigger] = {}
	end
	ItemRack.Register[trigger][eventname] = 1
end

-- disables a sepcific eventname ("Riding","Warrior:Berserk",etc)
function ItemRack_DisableEvent(eventname)

	if not ItemRack_Events[eventname] then return end

	local trigger = ItemRack_Events[eventname].trigger
	local has_event,i

	if ItemRack.Register[trigger] then
		ItemRack.Register[trigger][eventname] = nil
		for i in pairs(ItemRack.Register[trigger]) do has_event=1 end
		if not has_event then
			ItemRack_RegisterFrame:UnregisterEvent(trigger)
			ItemRack.Register[trigger] = nil
		end
	else
		ItemRack_RegisterFrame:UnregisterEvent(trigger)
	end
end

-- use this to initialize, enable or refresh Register
function ItemRack_EnableAllEvents()
	local i

	if ItemRack_Settings.Notify=="OFF" then
		-- if notify is off, see if any ITEMRACK_NOTIFY events are registered and turn on notify
		for i in pairs(ItemRack_Users[user].Events) do
			if ItemRack_Users[user].Events[i].enabled and ItemRack_Events[i].trigger=="ITEMRACK_NOTIFY" then
				ItemRack_Settings.Notify="ON"
--				ItemRack_Opt_Notify:SetChecked(1)
				DEFAULT_CHAT_FRAME:AddMessage("ItemRack: Notify has been turned on for Event: |cFFFFFF00"..i)
			end
		end
	end

	if ItemRack_Settings.EnableEvents=="ON" then
		for i in pairs(ItemRack_Users[user].Events) do
			if ItemRack_Users[user].Events[i].enabled then
				ItemRack_EnableEvent(i)
			else
				ItemRack_DisableEvent(i)
			end
		end
		ItemRackFrame:RegisterEvent("PLAYER_AURAS_CHANGED")
	else
		ItemRack_DisableAllEvents()
	end
end

-- use this to trun off all event watching.
function ItemRack_DisableAllEvents()
	local i

	for i in pairs(ItemRack_Users[user].Events) do
		ItemRack_DisableEvent(i)
	end
	ItemRackFrame:UnregisterEvent("PLAYER_AURAS_CHANGED")
end

--[[ Event Processing ]]--

ItemRack.EventQueue = {} -- indexed by events ("Riding", "Warrior:Battle") of GetTime()+delay to run

-- these two holders of arg1 and arg2 are separate for minimal processing to happen
-- a loop holding arg1-9 in a table takes 3.1 seconds for 100k iterations
-- storing just the first two values and moving on drops processing to 0.28 seconds
ItemRack.EventQueueArg1 = {} -- indexed by events also, values of arg1
ItemRack.EventQueueArg2 = {} -- indexed by events also, values of arg2

-- runs the eventname script, event = "Riding", "Warrior:Battle", etc
local function run_event_script(eventname)

	if eventname and ItemRack_Users[user].Events[eventname] then
		ItemRack.EventSetName = ItemRack_Users[user].Events[eventname].setname
		ItemRack.EventEventName = eventname
		if ItemRack.EventSetName then
			RunScript(ItemRack_Events[eventname].script)
			ItemRack.EventSetName = nil
			ItemRack.EventEventName = nil
		end
	end
end

-- events("triggers") defined in game go through here
function ItemRack_RegisterFrame_OnEvent(event)
	local i,j

	if ItemRack.Register[event] then
		for i in pairs(ItemRack.Register[event]) do
			if ItemRack_Events[i].delay==0 then
				-- EventSetName is the name of the set to use for EquipSet(), it's the set associated with the event
				run_event_script(i)
			else
				ItemRack.EventQueue[i] = GetTime()+ItemRack_Events[i].delay
				ItemRack.EventQueueArg1[i] = arg1
				ItemRack.EventQueueArg2[i] = arg2
				ItemRack_RegisterFrame:Show() -- turn on OnUpdate
			end
		end
	end
end

local register_timer = 0
function ItemRack_RegisterFrame_OnUpdate()

	local i

	-- check every .25 seconds if time has elapsed for events with a delay
	register_timer = register_timer + arg1
	if register_timer > .25 then
		register_timer = 0
		local current_time = GetTime()
		local queue_exists = nil
		for i in pairs(ItemRack.EventQueue) do
			queue_exists = 1 -- something is in the queue
			if ItemRack.EventQueue[i]<current_time then
				local holdarg1,holdarg2 = arg1,arg2
				arg1 = ItemRack.EventQueueArg1[i]
				arg2 = ItemRack.EventQueueArg2[i]
				run_event_script(i)
				arg1 = holdarg1
				arg2 = holdarg2
				ItemRack.EventQueue[i] = nil
			end
		end
		if not queue_exists then
			ItemRack_RegisterFrame:Hide() -- shut down OnUpdates when nothing left to process
		end
	end
end

-- saves the items currently worn that EventSetName defines into a set named "ItemRack-Saved"
-- a parameter passed will use the passed setname, for use in macros, ie SaveSet("pvp")
function ItemRack_SaveSet(v1)
-- NO LONGER NEEDED: EquipSet saves what was worn previously
end

-- equips the items previous saved in SaveSet(). it only equips items in the slots associated with the set
-- a parameter passed will use the passed setname, for use in macros, ie LoadSet("pvp")
function ItemRack_LoadSet(setname)

	if not setname and ItemRack.EventSetName then
		setname = ItemRack.EventSetName
	end

	Rack.UnequipSet(setname)
end

function ItemRack_ToggleEvents()

	if ItemRack_Settings.EnableEvents=="OFF" then
		ItemRack_Settings.EnableEvents="ON"
		ItemRack_Opt_EnableEvents:SetChecked(1)
		ItemRack_EnableAllEvents()
		DEFAULT_CHAT_FRAME:AddMessage("|cFF66AAFFItemRack Automatic Events are now |cFF55FF55ON")
	else
		ItemRack_Settings.EnableEvents="OFF"
		ItemRack_Opt_EnableEvents:SetChecked(nil)
		ItemRack_DisableAllEvents()
		DEFAULT_CHAT_FRAME:AddMessage("|cFF66AAFFItemRack Automatic Events are now |cFFFF5555OFF")
	end
end

function ItemRack_EventsList_OnEnter()
	local idx,notes = this:GetID() + FauxScrollFrame_GetOffset(ItemRack_Events_ScrollFrame)

	if eventList[idx].name and ItemRack_Settings.ShowTooltips=="ON" then
		set_tooltip_anchor(this)
		GameTooltip:AddLine(eventList[idx].name)
		if eventList[idx].setname then
			GameTooltip:AddLine("Set: "..eventList[idx].setname)
		end
		_,_,notes = string.find(eventList[idx].script or "","--%[%[(.+)%]%]")
		if notes then
			GameTooltip:AddLine(notes,.8,.8,.8,1)
		end
		GameTooltip:Show()
	end
end

-- toggles a set, remembering what it wore previous to equipping the set
function ItemRack_ToggleSet(setname)
	Rack.ToggleSet(setname)
end

--[[ Event script helper functions

	These are not necessary.  They can be completely encapsulated in the scripts themselves.  They're here for convenience. ]]

-- this is a special function to use for mount events. returns true if player is mounted, nil otherwise
-- pass a non-nil value for v1 to do a slow/thorough scan
function ItemRack_PlayerMounted(v1)
	local buff
	for i=1,24 do
		_,_,buff = UnitBuff("player",i)
		if buff then
			if problem_mounts[buff] or v1 or string.find(buff,"QirajiCrystal_") then
				-- hunter could be in group, could be warlock epic mount etc, check if this is truly a mount
				-- or if v1 is set to true, always check every buff. sigh this is slow but really no way around it without more data from users
				Rack_TooltipScan:SetUnitBuff("player",i)
				if string.find(Rack_TooltipScanTextLeft2:GetText() or "",ItemRackText.MOUNTCHECK) then
					return 1
				end
			elseif string.find(buff,"Mount_") then
				return 1
			end
		else
			return nil
		end
	end
end

-- returns the name of the form the player is in
function ItemRack_GetForm()

	local i,name,form

	for i=1,GetNumShapeshiftForms() do
		_,name,is_active = GetShapeshiftFormInfo(i)
		if is_active then
			form = name
		end
	end
	return form
end

-- gathers active buffs into ItemRack.Buffs and sends it via RegisterFrame for events
function ItemRack_BuffsChanged()
	local buffName,buffTexture
	for i in pairs(ItemRack.Buffs) do
		ItemRack.Buffs[i] = nil
	end
	for i=1,24 do
		Rack_TooltipScan:SetUnitBuff("player",i)
		buffName = Rack_TooltipScanTextLeft1:GetText()
		_,_,buffTexture = UnitBuff("player",i)
		if buffTexture then
			ItemRack.Buffs[buffName] = 1
			ItemRack.Buffs[buffTexture] = 1
		else
			break
		end
	end
	local oldarg1 = arg1
	arg1=ItemRack.Buffs
	ItemRack_RegisterFrame_OnEvent("ITEMRACK_BUFFS_CHANGED")
	arg1 = oldarg1
end

-- returns 1 if all pieces of setname are equipped, nil otherwise
function ItemRack_IsSetEquipped(setname)
	return Rack.IsSetEquipped(setname)
end

-- returns three parameters: table of sets, current set name, current set texture
function ItemRack_GetUserSets()
	local texture = "Interface\\AddOns\\ItemRack\\ItemRack-Icon"
	local setname = ItemRack_CurrentSet()
	if setname and Rack_User[user].Sets[setname] and not string.find(setname,"^Rack") and not string.find(setname,"^ItemRack") then
		texture = Rack_User[user].Sets[setname].icon
	end
	return Rack_User[user].Sets, setname, texture
end

--[[ Rack 2.0 code begins here ]]--

--[[ There is some redundancy because everything that follows is a part of ItemRack 2.0.
	 Everything above this section will be scrapped for 2.0 ]]

Rack = {
	version = 1.9,
	debug = nil,

	TimerPool = {}, -- timer tables added here ["InvUpdate"]={timer,limit,func,rep}

	SetSwapping = nil, -- name of a set currently being swapped
	SwapList = {}, -- individual item swap details go here
	LockList = {}, -- tables of bags where slots are locked (to be skipped in FindItem and FindSpace)
	CombatQueue = {} -- table of items to swap in when dropping out of combat/death
}

Rack.SlotInfo = {
	[0] = { name="AmmoSlot", swappable=1, INVTYPE_AMMO=1 },
	[1] = { name="HeadSlot", INVTYPE_HEAD=1 },
	[2] = { name="NeckSlot", INVTYPE_NECK=1 },
	[3] = { name="ShoulderSlot", INVTYPE_SHOULDER=1 },
	[4] = { name="ShirtSlot", INVTYPE_BODY=1 },
	[5] = { name="ChestSlot", INVTYPE_CHEST=1, INVTYPE_ROBE=1 },
	[6] = { name="WaistSlot", INVTYPE_WAIST=1 },
	[7] = { name="LegsSlot", INVTYPE_LEGS=1 },
	[8] = { name="FeetSlot", INVTYPE_FEET=1 },
	[9] = { name="WristSlot", INVTYPE_WRIST=1 },
	[10] = { name="HandsSlot", INVTYPE_HAND=1 },
	[11] = { name="Finger0Slot", INVTYPE_FINGER=1 },
	[12] = { name="Finger1Slot", INVTYPE_FINGER=1 },
	[13] = { name="Trinket0Slot", INVTYPE_TRINKET=1 },
	[14] = { name="Trinket1Slot", INVTYPE_TRINKET=1 },
	[15] = { name="BackSlot", INVTYPE_CLOAK=1 },
	[16] = { name="MainHandSlot", swappable=1, INVTYPE_WEAPONMAINHAND=1, INVTYPE_2HWEAPON=1, INVTYPE_WEAPON=1 },
	[17] = { name="SecondaryHandSlot", swappable=1, INVTYPE_WEAPON=1, INVTYPE_WEAPONOFFHAND=1, INVTYPE_SHIELD=1, INVTYPE_HOLDABLE=1 },
	[18] = { name="RangedSlot", swappable=1, INVTYPE_RANGED=1, INVTYPE_THROWN=1, INVTYPE_RANGEDRIGHT=1 },
	[19] = { name="TabardSlot", INVTYPE_TABARD=1 },
}

--[[ Initialization ]]

function Rack.Initialize()

	for i=0,19 do Rack.SwapList[i]={} end -- create blank SwapList table
	for i=-2,10 do Rack.LockList[i]={} end -- create a blank Locked table

	if not Rack_User then
		Rack_User = {}
		Rack.ConvertOldSets()
	end

	if not Rack_User[user] then
		Rack_User[user] = { Sets={}, CurrentSet=nil }
	end

	Rack_User[user].Sets["Rack-CombatQueue"] = {}
	for i=0,19 do
		Rack_User[user].Sets["Rack-CombatQueue"][i] = { id=nil, name=nil }
	end

	-- note: these timers are added to a pool, they don't affect processing time unless started
	Rack.CreateTimer("WaitToIterate",Rack.IterateWait,1) -- itemlock iterate pause
	Rack.CreateTimer("IconDragging",Rack.IconDragging,0,1) -- minimap button dragging
	Rack.CreateTimer("ScaleUpdate",Rack.ScaleUpdate,.1,1) -- scaling
	Rack.CreateTimer("TooltipUpdate",Rack.TooltipUpdate,1,1) -- tooltip update
	Rack.CreateTimer("ControlFrame",Rack.ControlFrame,.75,1) -- controls on bar
	Rack.CreateTimer("CooldownUpdate",ItemRack_CooldownUpdate_OnUpdate,1,1) -- cooldown/notify
	Rack.CreateTimer("MenuFrame",Rack.MenuFrame,.25,1) -- menu mouseover check
	Rack.CreateTimer("InvUpdate",ItemRack_InvUpdate_OnUpdate,.25,1) -- inventory throttle
end

function Rack.OnEvent()

	if event=="ITEM_LOCK_CHANGED" then
		Rack.OnItemLockChanged()
	elseif (event=="PLAYER_REGEN_ENABLED" or event=="PLAYER_UNGHOST" or event=="PLAYER_ALIVE") and (not Rack.IsPlayerReallyDead() and not UnitAffectingCombat("player")) then
		-- player is coming out of combat or being res'ed.  EquipSet the CombatQueue
		local somethingQueued
		for i=0,19 do
			if Rack.CombatQueue[i] then
				Rack_User[user].Sets["Rack-CombatQueue"][i].id=Rack.CombatQueue[i]
				Rack.CombatQueue[i] = nil
				somethingQueued = 1
			else
				Rack_User[user].Sets["Rack-CombatQueue"][i].id = nil
				Rack_User[user].Sets["Rack-CombatQueue"][i].name = nil
			end
			getglobal("ItemRackInv"..i.."Queue"):Hide()
			getglobal(ItemRack.Indexes[i].paperdoll_slot.."Queue"):Hide()
		end
		if somethingQueued then
			Rack.EquipSet("Rack-CombatQueue")
		end
	end
end

--[[ Item information ]]

-- returns itemTexture, itemID, itemName, itemSlot of an item in container(bag,slot) or inventory("player",bag)
function Rack.GetItemInfo(bag,slot)
	local i,j,id,itemLink,itemID,itemSlot,itemTexture,itemName

	if slot then -- this is a container item
		itemLink = GetContainerItemLink(bag,slot)
	else
		itemLink = GetInventoryItemLink("player",bag)
	end

	if itemLink then
		_,_,id = string.find(itemLink,"(item:%d+:%d+:%d+:%d+)")
		_,_,itemID = string.find(id or "","item:(%d+:%d+:%d+):%d+")
		itemName,_,_,_,_,_,_,_,itemSlot,itemTexture = GetItemInfo(id)
	elseif not slot then -- if no link and this is an inventory slot, missing or ammo
		_,itemTexture = GetInventorySlotInfo(Rack.SlotInfo[bag].name) -- get paperdoll texture
		itemID = 0 -- assume empty slot
		if bag==0 then -- this is an ammo slot
			local ammoTexture = GetInventoryItemTexture("player",0)
			if ammoTexture then
				Rack_TooltipScan:SetInventoryItem("player",0)
				itemName = Rack_TooltipScanTextLeft1:GetText()
				for i=0,4 do -- look through containers to get itemID of this ammo
					for j=1,GetContainerNumSlots(i) do
						if itemName==Rack.GetContainerItemName(i,j) then
							_,itemID,_,itemSlot = Rack.GetItemInfo(i,j)
							i,j = 99,99
						end
					end
				end
				itemTexture = ammoTexture
			end
		end
	end

	return itemTexture, itemID, itemName, itemSlot
end

-- returns the name of an item in bag,slot
function Rack.GetContainerItemName(bag,slot)
	local _,_,name = string.find(GetContainerItemLink(bag,slot) or "","%[(.+)%]")
	return name
end

-- converts a name to an itemID by searching through inventory, bags and bank for the item
function Rack.GetItemID(itemName)

	local inv,bag,slot = Rack.FindItem(nil,itemName)
	local itemID

	if inv then
		_,itemID = Rack.GetItemInfo(inv)
	elseif bag and slot then
		_,itemID = Rack.GetItemInfo(bag,slot)
	end

	return itemID
end

-- returns the name and texture of an item by its itemID
function Rack.GetNameByID(itemID)
	local name,texture
	local _,_,id = string.find(itemID or "","(%d+):%d+:%d+")
	name,_,_,_,_,_,_,_,_,texture = GetItemInfo(id or "")
	if itemID==0 then
		name = "(empty)"
		texture = "Interface\\PaperDoll\\UI-Backpack-EmptySlot"
	end
	return name,texture
end

-- returns true if the bagid (0-4) is a normal "Container", as opposed to quivers and ammo pouches
function Rack.ValidBag(bagid)

	local linkid,bagtype,legal

	if bagid==0 or bagid==-1 then
		legal = true
	else
		local invID = ContainerIDToInventoryID(bagid)
		_,_,linkid = string.find(GetInventoryItemLink("player",invID) or "","item:(%d+)")
		if linkid then
			_,_,_,_,_,_,bagtype = GetItemInfo(linkid)
			if bagtype==ItemRackText.INVTYPE_CONTAINER then -- "Bag" for enUS clients, "Container" for other clients
				legal = true -- this is a true container
			end
		end
	end

	return legal
end

function Rack.FindSpaceInBag(bag)
	if Rack.ValidBag(bag) then
		for j=1,GetContainerNumSlots(bag) do
			if not Rack.LockList[bag][j] and not GetContainerItemLink(bag,j) then
				return j
			end
		end
	end
end

-- pass a value for bank to find a free bank slot
function Rack.FindSpace(bank)
	local slot
	if bank and ItemRack.BankIsOpen then -- search bank
		for _,i in pairs(ItemRack.BankSlots) do
			slot = Rack.FindSpaceInBag(i)
			if slot then
				Rack.LockList[i][slot] = 1
				return i,slot
			end
		end
	else
		for i=4,0,-1 do
			slot = Rack.FindSpaceInBag(i)
			if slot then
				Rack.LockList[i][slot] = 1
				return i,slot
			end
		end
	end
end

-- clears locks on all bag slots
function Rack.ClearLockList(bag,slot)
	if not bag then
		for i=-2,10 do
			for j in pairs(Rack.LockList[i]) do
				Rack.LockList[i][j] = nil
			end
		end
	else
		Rack.LockList[bag][slot] = nil
	end
end

-- returns inv,bag,slot of an itemID, or itemName if itemID doesn't exist
function Rack.FindItem(itemID,itemName,passive)

	local bagStart,bagEnd,i,j,id,name = 0,4
	local inv,bag,slot

	if itemID then

		for i=bagStart,bagEnd do -- check bags for itemID
			for j=1,GetContainerNumSlots(i) do
				if not Rack.LockList[i][j] or passive then
					_,id = Rack.GetItemInfo(i,j)
					if id==itemID then
						bag,slot = i,j
						i = 99
						break
					end
				end
			end
			if (i==99) then break end
		end

		if not bag then
			for i=0,19 do -- if not in bags, check on person for itemID
				if not Rack.LockList[-2][i] or passive then
					_,id = Rack.GetItemInfo(i)
					if id==itemID then
						inv = i
						i = 99
						break
					end
				end
			end
		end
	end

	-- if an exact itemID match wasn't found, search by name
	if not inv and not bag and not slot then
		itemName = itemName or Rack.GetNameByID(itemID)
		if itemName then

			for i=bagStart,bagEnd do -- check bags for itemID
				for j=1,GetContainerNumSlots(i) do
					if not Rack.LockList[i][j] or passive then
						_,_,name = Rack.GetItemInfo(i,j)
						if name==itemName then
							bag,slot = i,j
							i = 99
							break
						end
					end
				end
				if (i==99) then break end
			end

			if not bag then
				for i=0,19 do -- check on person for itemName
					if not Rack.LockList[-2][i] or passive then
						_,_,name = Rack.GetItemInfo(i)
						if name==itemName then
							inv = i
							break
						end
					end
				end
			end
		end
	end

	return inv,bag,slot
end

-- performs a FindItem on a set entry ([0]-[19]) and updates itemid if one wasn't there
function Rack.FindSetItem(setslot)
	local inv,bag,slot = Rack.FindItem(setslot.id,setslot.name)
	if not setslot.id and setslot.name and (inv or bag or slot) then
		setslot.id = Rack.GetItemID(setslot.name)
	end
	return inv,bag,slot
end

function Rack.InvToInvSwap(inv1,inv2)

	local swap = Rack.SwapList

	if swap[inv1].sourceInv==inv2 and swap[inv2].sourceInv==inv1 then
		PickupInventoryItem(inv1)
		PickupInventoryItem(inv2)
		Rack.ClearSwapListEntry(inv1)
		Rack.ClearSwapListEntry(inv2)
	end
end


--[[ Queue maintenance

	To prevent garbage creation in creating/destroying multi-level tables, queue entries are reused and only
	the index to that queue entry is created/destroyed.

	.SwapQueue is where the tables sit, indexed arbitrarily by the first available one returned by GetFreeQueueEntry
	.SwapQueueOrder is a numerically-indexed table of indexes into .SwapQueue in the order the swap should happen
]]

Rack.SwapQueue = { [1]={ direction = "END" } } -- numerically-indexed queue of swaps to perform
Rack.SwapQueueOrder = {} -- numerically-indexed queue of numbers in the order they're to be performed

-- wipes out an entry without creating garbage
function Rack.ClearQueueEntry(idx)

	Rack.SwapQueue[idx].direction = nil
	Rack.SwapQueue[idx].setname = nil
	for i=0,19 do
		Rack.SwapQueue[idx][i].id = nil
		Rack.SwapQueue[idx][i].fromBag = nil
		Rack.SwapQueue[idx][i].fromSlot = nil
	end
end

-- creates a new table and sub-table queue entries, only call when entry didn't exist before
function Rack.ConstructQueueEntry(idx)
	Rack.SwapQueue[idx] = {}
	for i=0,19 do
		Rack.SwapQueue[idx][i] = {}
	end
end

-- get first available index into SwapQueue for use for a set. to avoid garbage creation these are reused
function Rack.GetFreeQueueEntry()
	local i,found = 1,1

	while found do
		found = Rack.SwapQueue[i]
		if found and not found.direction then
			return i
		end
		i = i + 1
	end
	i = i - 1 -- backtrack to last available entry

	Rack.ConstructQueueEntry(i)
	return i
end

-- adds the .SwapQueue idx to the end of the QueueOrder
function Rack.AddQueueEntry(idx)
	table.insert(Rack.SwapQueueOrder,idx)
end

-- removes the .SwapQueue idx from QueueOrder and clears the .SwapQueue entry
function Rack.RemoveQueueEntry(idx)
	for i=1,table.getn(Rack.SwapQueueOrder) do
		if Rack.SwapQueueOrder[i]==idx then
			table.remove(Rack.SwapQueueOrder,i)
			Rack.ClearQueueEntry(idx)
			return
		end
	end
end

-- this sorts the queue, moving all NEWSETs to the end
function Rack.SortQueue()

	if table.getn(Rack.SwapQueueOrder)>1 then
		local found,temp = 1
		local queue = Rack.SwapQueueOrder
		-- simple shell sort (this is a small, 5-6 max typically) table of indexes that must remain in order otherwise
		while found do
			found = nil
			for i=1,(table.getn(queue)-1) do
				if Rack.SwapQueue[queue[i]].direction=="NEWSET" and Rack.SwapQueue[queue[i+1]].direction~="NEWSET" then
					temp = queue[i]
					queue[i] = queue[i+1]
					queue[i+1] = temp
					found = 1
				end
			end
		end
	end
end

--[[ Set Maintenance

	Sets are tables in Rack_User[user].Sets[setname] structured as:
	{ hide=1/nil, icon="Interface\\Icons\\etc", ["0"]={id=string,name=string,old=string} }
	id = itemID for the item to wear in the set, 0 for (empty) (can be nil)
	name = name of the item to wear in the set, (empty) for empty
	old = itemID for the item worn in this slot prior to this set equipped, usually nil
]]

-- this converts all sets from ItemRack_Users
function Rack.ConvertOldSets()

	local old,new

	DEFAULT_CHAT_FRAME:AddMessage("Performing one-time conversion of ItemRack sets.")
	for u in pairs(ItemRack_Users) do
		if not Rack_User[u] then
			Rack_User[u] = { Sets={} }
		end
		Rack_User[u].CurrentSet = ItemRack_Users[u].CurrentSet
		for sets in pairs(ItemRack_Users[u].Sets) do
			if not Rack_User[u].Sets[sets] then
				Rack_User[u].Sets[sets] = {}
			end
			old = ItemRack_Users[u].Sets[sets]
			new = Rack_User[u].Sets[sets]

			for i=0,19 do
				if old[i] then
					new[i] = { name=old[i] }
					if old[i]=="(empty)" then
						new[i].id = 0
					else
						new[i].id = Rack.GetItemID(old[i]) or nil
					end
				end
			end
			new.icon = old.icon
			new.hide = old.hide
			new.key = old.key
			new.keyindex = old.keyindex
		end
	end
end

function Rack.ClearSwapListEntry(idx)
	Rack.SwapList[idx].needsSwap = nil
	Rack.SwapList[idx].sourceInv = nil
	Rack.SwapList[idx].sourceBag = nil
	Rack.SwapList[idx].sourceSlot = nil
	Rack.SwapList[idx].direction = nil
	Rack.SwapList[idx].desiredName = nil
	Rack.SwapList[idx].needsEmptied = nil
end

-- returns the currently worn set, or last set worn
function Rack.CurrentSet()
	return Rack_User[user].CurrentSet
end

--[[ EquipSet
	This function takes a setname and then equips the set, saving what it's replacing within the set.
]]

function Rack.EquipSet(setname)

	local i,bag,slot,id,swap,idx
	local mustWait -- flag that this swap must happen in stages (INV needs to move out of the way)
	local set = Rack_User[user].Sets[setname]
	local hasTWOHAND, hasINVTOBAG, hasINVTOINV, hasBAGTOINV, hasAMMO
	local missing = "ItemRack could not find: "
	local invStart,invEnd = 1,19 -- changes to 16,18 if in combat

	if not set then
		DEFAULT_CHAT_FRAME:AddMessage("Rack: Set \""..setname.."\" doesn't exist.")
		return
	end

	if Rack.IsSetEquipped(setname) then
		if not string.find(setname,"^Rack-") and not string.find(setname,"^ItemRack") then
			Rack_User[user].CurrentSet = setname
		end
		Rack.StartTimer("InvUpdate")
		return
	end

	Rack.ClearLockList()

	if Rack.SetSwapping==setname then
		Rack.OnItemLockChanged() -- if trying to swap this already, move along in queue
		return
	end

	if (Rack.SetSwapping or Rack.IsPlayerReallyDead()) and not UnitAffectingCombat("player") then
		-- come back later, an EquipSet is in progress
		idx = Rack.GetFreeQueueEntry()
		Rack.AddQueueEntry(idx)
		Rack.SwapQueue[idx].direction = "NEWSET"
		Rack.SwapQueue[idx].setname = setname
		return
	end

	-- pre-scan for items that don't need to move
	for i=0,19 do
		_,id = Rack.GetItemInfo(i)

		if set[i] and (set[i].id or set[i].name) then
			Rack.FindSetItem(set[i])
			if set[i].id==id then
				Rack.LockList[-2][i] = 1
			else
				set[i].old = id -- store what was there previously
			end
		end
		Rack.ClearSwapListEntry(i)
	end

	if UnitAffectingCombat("player") then -- player is in combat
		for i=1,19 do
			if set[i] and set[i].id and not Rack.SlotInfo[i].swappable then
				Rack.AddToCombatQueue(i,set[i].id)
			end
		end
		invStart,invEnd = 16,18 -- restrict swap to weapons only
	elseif Rack.IsPlayerReallyDead() then -- player is dead
		for i=0,19 do
			if set[i] and set[i].id then
				Rack.AddToCombatQueue(i,set[i].id)
			end
		end
		return
	end

	-- determine what needs swapped and populate Rack.SwapList
	-- skipping ammo slot the black sheep of inventory slots
	for i=invStart,invEnd do
		if set[i] and set[i].id then
			_,id = Rack.GetItemInfo(i)
			if id~=set[i].id then
				swap = Rack.SwapList[i]
				swap.needsSwap = 1
				swap.desiredName = Rack.GetNameByID(set[i].id)
				if set[i].id==0 then -- empty slot
					swap.direction="INVTOBAG"
					swap.needsEmptied = 1
					hasINVTOBAG = 1
				else
					inv,bag,slot = Rack.FindSetItem(set[i])
					if inv then -- found it in another inventory slot
						Rack.LockList[-2][inv] = 1
						_,_,_,sourceEquipSlot = Rack.GetItemInfo(inv)
						_,_,_,destEquipSlot = Rack.GetItemInfo(i)
						swap.direction="INVTOINV" -- if the item can exist in both slots
						swap.sourceInv = inv
						hasINVTOINV = 1
						if destEquipSlot and sourceEquipSlot and not (Rack.SlotInfo[i][sourceEquipSlot] and Rack.SlotInfo[inv][destEquipSlot]) then
							swap.needsEmptied = 1
							hasINVTOBAG = 1
						end
					elseif bag and slot then -- found it in a bag slot
						Rack.LockList[bag][slot] = 1
						swap.direction="BAGTOINV"
						swap.sourceBag = bag
						swap.sourceSlot = slot
						if i==16 then -- this is a mainhand weapon
							_,_,_,slot = Rack.GetItemInfo(swap.sourceBag,swap.sourceSlot)
							if slot=="INVTYPE_2HWEAPON" and GetInventoryItemLink("player",17) then
								hasTWOHAND = 1 -- flag a 2h swap in this set if there's an offhand equipped
								if not set[17] then
									set[17] = {}
								end
								set[17].id = 0
								Rack.SwapList[17].needsEmptied = 1
							end
						end
						hasBAGTOINV = 1
					else -- couldn't find it
						missing = missing..tostring(swap.desiredName)..", "
						swap.needsSwap = nil
					end
				end
			end
		elseif set[i] and set[i].name then -- item has no id yet, not seen since conversion
			missing = missing..tostring(set[i].name)..", "
		end
	end

	if missing~="ItemRack could not find: " then
		DEFAULT_CHAT_FRAME:AddMessage(string.gsub(missing,", $",""))
	end

	-- at this stage, Rack.SwapList[0]-[19] is populated

	-- INVTOBAG and .needsEmptied swaps first
	if hasINVTOBAG then
		idx = Rack.GetFreeQueueEntry()
		Rack.AddQueueEntry(idx)
		Rack.SwapQueue[idx].direction = "INVTOBAG"
		Rack.SwapQueue[idx].setname = setname
		for i=0,19 do
			if Rack.SwapList[i].needsEmptied then
				Rack.SwapQueue[idx][i].id = 0
			end
		end
	end

	-- INVTOINV swaps next
	if hasINVTOINV then
		idx = Rack.GetFreeQueueEntry()
		Rack.AddQueueEntry(idx)
		Rack.SwapQueue[idx].direction = "INVTOINV"
		Rack.SwapQueue[idx].setname = setname
		for i=0,19 do
			if Rack.SwapList[i].direction=="INVTOINV" then
				Rack.SwapQueue[idx][i].id = set[i].id
				Rack.SwapQueue[idx][i].fromSlot = Rack.SwapList[i].sourceInv
			end
		end
	end

	-- BAGTOINV swaps last (90% of swaps this is only bit that queues)
	if hasBAGTOINV then
		idx = Rack.GetFreeQueueEntry()
		Rack.AddQueueEntry(idx)
		Rack.SwapQueue[idx].direction = "BAGTOINV"
		Rack.SwapQueue[idx].setname = setname
		for i=0,19 do
			if Rack.SwapList[i].direction=="BAGTOINV" then
				Rack.SwapQueue[idx][i].id = set[i].id
				Rack.SwapQueue[idx][i].fromBag = Rack.SwapList[i].sourceBag
				Rack.SwapQueue[idx][i].fromSlot = Rack.SwapList[i].sourceSlot
			end
		end
	end

	-- now deal with ammo why oh why can't this slot be normal
	-- perform ammo swaps directly. since it never takes up a new bag slot it's ok to do pickups and move on
	if set[0] and set[0].id then
		_,id = Rack.GetItemInfo(0)
		if id~=set[0].id then
			if set[0].id==0 then -- unequip ammo
				bag,slot = Rack.FindSpace()
				if bag then
					PickupInventoryItem(0)
					PickupContainerItem(bag,slot)
				end
			else
				_,bag,slot = Rack.FindSetItem(set[0])
				if bag then
					PickupContainerItem(bag,slot)
					PickupInventoryItem(0)
				end
			end
		end
	end

	Rack_User[user].Sets[setname].oldsetname = Rack_User[user].CurrentSet

	if Rack_User[user].Sets[setname].showhelm then
		ShowHelm(Rack_User[user].Sets[setname].showhelm)
	end
	if Rack_User[user].Sets[setname].showcloak then
		ShowCloak(Rack_User[user].Sets[setname].showcloak)
	end

	-- at last, perform the swaps by iterating over the queue
	Rack.IterateSwapQueue()

end

-- waits for some timed reason to do another iteration. for now just to check SpellIsTargeting once a second
function Rack.IterateWait()

	if SpellIsTargeting() or CursorHasItem() then
		Rack.StartTimer("WaitToIterate",1)
	else
		Rack.StopTimer("WaitToIterate")
		Rack.IterateSwapQueue()
	end
end

function Rack.ShutdownQueue()
	RackFrame:UnregisterEvent("ITEM_LOCK_CHANGED")
	Rack.SetSwapping = nil
	for i=1,table.getn(Rack.SwapQueueOrder) do
		Rack.RemoveQueueEntry(Rack.SwapQueueOrder[i])
	end
end

-- this function grabs the next swap QueueEntry and performs the swap
-- swaps only happen one direction at a time: INVTOBAG->INVTOINV->BAGTOINV
-- complex swaps can require running this a few times
function Rack.IterateSwapQueue()

	if table.getn(Rack.SwapQueueOrder)<1 then
		-- if queue is empty, unregister and leave
		Rack.SetSwapping = nil
		RackFrame:UnregisterEvent("ITEM_LOCK_CHANGED")
		return
	
	elseif SpellIsTargeting() or CursorHasItem() then
		-- check if in Spell Targeting mode to prevent disenchants/enchants
		Rack.StartTimer("WaitToIterate")
		return

	elseif Rack.IsPlayerReallyDead() then
		-- if player is dead, they can't swap anything, leave for now
		return

	else

		Rack.SortQueue() -- move NEWSETs to end of queue

		local bag,slot,id
		local idx = Rack.SwapQueueOrder[1]
		local queue = Rack.SwapQueue[idx]

		if queue.direction == "NEWSET" then
			Rack.SetSwapping = nil
			local setname = queue.setname
			Rack.RemoveQueueEntry(idx)
			RackFrame:UnregisterEvent("ITEM_LOCK_CHANGED")
			Rack.EquipSet(setname)

		else

			-- something to process
			RackFrame:RegisterEvent("ITEM_LOCK_CHANGED")

			Rack.SetSwapping = Rack.SwapQueue[idx].setname

			Rack.ClearLockList()

			if queue.direction == "INVTOBAG" then
				for i=0,19 do
					if queue[i].id==0 then
						bag,slot = Rack.FindSpace()
						if bag then
							queue[i].fromBag = bag
							queue[i].fromSlot = slot
							PickupInventoryItem(i)
							PickupContainerItem(bag,slot)
						else
							Rack.NoMoreRoom()
							queue[i].id = nil
							queue[i].fromBag = nil
							queue[i].fromSlot = nil
							Rack.ShutdownQueue() -- we ran out of room, stop all swaps now
						end
					end
				end
			elseif queue.direction == "INVTOINV" then
				for i=0,19 do
					if queue[i].fromSlot then
						PickupInventoryItem(queue[i].fromSlot)
						PickupInventoryItem(i)
					end
				end
			elseif queue.direction == "BAGTOINV" then
				for i=0,19 do
					if queue[i].fromBag then
						_,id = Rack.GetItemInfo(queue[i].fromBag,queue[i].fromSlot)
						if id == queue[i].id then
							PickupContainerItem(queue[i].fromBag,queue[i].fromSlot)
							PickupInventoryItem(i)
						else
							-- didn't find it at same bag spot when queued
							_,bag,slot = Rack.FindSetItem(queue[i])
							if bag then
								queue[i].fromBag = bag
								queue[i].fromSlot = slot
								PickupContainerItem(bag,slot)
								PickupInventoryItem(i)
							else
								queue[i].id = nil -- forget we tried
								queue[i].fromBag = nil
								queue[i].fromSlot = nil
							end
						end
					end
				end
			end
		end
	end

end

--[[ Debug ]]--

function Rack.PrintSet(setname)

	local i,j
	for i=0,19 do
		j=Rack_User[user].Sets[setname]
		if j and j[i] then DEFAULT_CHAT_FRAME:AddMessage(Rack.SlotInfo[i].name..": "..tostring(j[i].id).." : "..tostring(Rack.GetNameByID(j[i].id)).." old:"..tostring(j[i].old)) end
	end
end

function Rack.PrintQueue()

	local txt = ""

	local function debug(msg)
		DEFAULT_CHAT_FRAME:AddMessage(msg)
	end

	debug("__ Rack.PrintQueue() __")
	debug("table.getn(Rack.SwapQueueOrder)="..tostring(table.getn(Rack.SwapQueueOrder)))
	txt = "Rack.SwapQueueOrder = { "
	for i=1,table.getn(Rack.SwapQueueOrder) do
		txt = txt.."["..i.."]="..tostring(Rack.SwapQueueOrder[i])..", "
	end
	txt = txt.."}"
	debug(txt)
	txt = "Rack.SwapQueue = { "
	for i in pairs(Rack.SwapQueue) do
		txt = txt.."["..i.."]= { direction="..tostring(Rack.SwapQueue[i].direction)..", "
		txt = txt.."setname="..tostring(Rack.SwapQueue[i].setname)..", "
		for j=0,19 do
			if Rack.SwapQueue[i][j] and Rack.SwapQueue[i][j].id then
				txt = txt.."["..j.."]="..Rack.SwapQueue[i][j].id..", "
				txt = txt.."fromBag="..tostring(Rack.SwapQueue[i][j].fromBag)..", "
				txt = txt.."fromSlot="..tostring(Rack.SwapQueue[i][j].fromSlot)..", "
			end
		end
		txt = txt .."} "
	end
	txt = txt.."}"
	debug(txt)

end

--[[ Locks ]]--

-- returns true if anything is locked
function Rack.AnyLocked()

	local locked,isLocked

	for i=0,19 do
		if IsInventoryItemLocked(i) then
			locked = 1
			i = 99
		end
	end

	if not locked then
		for i=0,4 do
			for j=1,GetContainerNumSlots(i) do
				_,_,isLocked = GetContainerItemInfo(i,j)
				if isLocked then
					locked = 1
					i,j=99,99
				end
			end
		end
	end

	return locked
end

-- when an iteration begins, on each ITEM_LOCK_CHANGED check if the swaps are done
-- if so, remove the current queue entry and go to the next one
-- this is where swaps end
function Rack.OnItemLockChanged()

	if not Rack.SwapQueueOrder[1] then
		Rack.SetSwapping = nil
		RackFrame:UnregisterEvent("ITEM_LOCK_CHANGED")
		return
	end

	if not Rack.AnyLocked() then
		local setname = Rack.SwapQueue[Rack.SwapQueueOrder[1]].setname
		if not string.find(setname,"^Rack-") and not string.find(setname,"^ItemRack") then
			Rack_User[user].CurrentSet = setname
		end
		Rack.RemoveQueueEntry(Rack.SwapQueueOrder[1])
		Rack.IterateSwapQueue()
	end	
end

--[[ Combat/Death Queue Processing ]]

function Rack.IsPlayerReallyDead()
	local dead = UnitIsDeadOrGhost("player")
	local _,class = UnitClass("player")
	if class~="HUNTER" then
		return dead
	end
	for i=1,24 do
		if select(3,UnitBuff("player",i))=="Interface\\Icons\\Ability_Rogue_FeignDeath" then
			dead = nil -- player is just FD, not really dead
		end
	end
	return dead
end

-- adds an item 'id' to 'slot' queue for post-combat/death swap
function Rack.AddToCombatQueue(slot,id)
	local button = getglobal("ItemRackInv"..slot.."Queue")
	local paperdoll = getglobal(ItemRack.Indexes[slot].paperdoll_slot.."Queue")
	local _,wornId = Rack.GetItemInfo(slot)
	if Rack.CombatQueue[slot]==id or id==wornId then
		Rack.CombatQueue[slot] = nil
		button:Hide()
		paperdoll:Hide()
	elseif id and id~=wornId then
		Rack.CombatQueue[slot] = id
		local _,texture = Rack.GetNameByID(id)
		button:SetTexture(texture)
		button:Show()
		paperdoll:SetTexture(texture)
		paperdoll:Show()
	end
end

--[[ EquipSet wrappers ]]--

-- returns true if the entire set is currently worn
function Rack.IsSetEquipped(setname)

	local set,missing,id = Rack_User[user].Sets[setname or ""]

	if set then
		for i=0,19 do
			if set[i] then
				-- if item queued, check queued item instead of GetItemInfo
				if Rack.CombatQueue[i] then
					id = Rack.CombatQueue[i]
				else
					_,id = Rack.GetItemInfo(i)
				end
				if set[i].id ~= id then
					missing = 1
				end
			end
		end
	end
	return set and not missing
end

-- equips a set if it's worn, unequips it if not
function Rack.ToggleSet(setname)

	if Rack_User[user].Sets[setname or ""] then
		if Rack.IsSetEquipped(setname) then
			Rack.UnequipSet(setname)
		else
			Rack.EquipSet(setname)
		end
	end
end

-- This function creates a set of all the items replaced by setname, and then does does an .EquipSet()
function Rack.UnequipSet(setname)

--	if not Rack.IsSetEquipped(setname) then
--		return
--	end

	local unequip_setname = "Rack-Unequip-"..setname
	Rack_User[user].Sets[unequip_setname] = {}
	local old = Rack_User[user].Sets[setname]
	local new = Rack_User[user].Sets[unequip_setname]

	for i=0,19 do
		if old[i] and old[i].old then
			new[i] = { id=old[i].old }
			old[i].old = nil -- comment this line when a mechanism to catch enchant changes
		end
	end

	Rack.EquipSet(unequip_setname)
	if old.oldsetname and not string.find(old.oldsetname,"^Rack") and not string.find(old.oldsetname,"^ItemRack") then
		Rack_User[user].CurrentSet = Rack_User[user].Sets[setname].oldsetname
	end
	Rack_User[user].Sets[setname].oldsetname = nil
end

--[[ Timer maintenance

	The goal is to reduce the processing of timer checks to as little as possible.  One OnUpdate is a central
	repository of this mod's timers.
]]


function Rack.CreateTimer(name,func,limit,rep)
	Rack.TimerPool[name] = { func=func, limit=limit, timer=limit, rep=rep, enabled=nil }
end

function Rack.StartTimer(name,timer)
	Rack.TimerPool[name].timer = timer or Rack.TimerPool[name].limit
	Rack.TimerPool[name].enabled = 1
	RackFrame:Show()
end

function Rack.StopTimer(name)
	Rack.TimerPool[name].enabled = nil
	Rack.ClearStoppedTimers()
end

function Rack.ClearStoppedTimers()
	local stuffLeft
	for i in pairs(Rack.TimerPool) do
		stuffLeft = stuffLeft or Rack.TimerPool[i].enabled
	end
	if not stuffLeft then
		RackFrame:Hide()
	end
end

function Rack.TimerEnabled(name)
	if Rack.TimerPool[name] and Rack.TimerPool[name].enabled then
		return 1
	else
		return nil
	end
end

function Rack.OnUpdate()

	local clock,stopped
	local elapse = tonumber(arg1) or 0.1

	for i in pairs(Rack.TimerPool) do
		clock = Rack.TimerPool[i]
		if clock.enabled then
			clock.timer = clock.timer - elapse
			if clock.timer<0 then
				if clock.rep then
					clock.timer = clock.limit -- rewind to start if 'rep' set
				else
					clock.enabled = nil
					stopped = 1
				end
				clock.func()
			end
		end
	end
	if stopped then
		Rack.ClearStoppedTimers()
	end

end

--[[ old OnUpdates : now gathered under Rack.Timers ]]

-- formerly ItemRack_IconDraggingFrame_OnUpdate
function Rack.IconDragging()
	local xpos,ypos = GetCursorPosition()
	local xmin,ymin = Minimap:GetLeft(), Minimap:GetBottom()

	xpos = xmin-xpos/Minimap:GetEffectiveScale()+70
	ypos = ypos/Minimap:GetEffectiveScale()-ymin-70

	ItemRack_Settings.IconPos = math.deg(math.atan2(ypos,xpos))
	move_icon()
end
ItemRack_IconDraggingFrame_OnUpdate = Rack.IconDragging -- legacy

-- formerly ItemRack_ScaleUpdate_OnUpdate
function Rack.ScaleUpdate()
	local frame = ItemRack.FrameToScale
	local oldscale = frame:GetEffectiveScale()
	local framex, framey, cursorx, cursory = frame:GetLeft()*oldscale, frame:GetTop()*oldscale, GetCursorPosition()

	if (cursorx-framex)>32 then
		local newscale = (cursorx-framex)/ItemRack.ScalingWidth
		ItemRack_ScaleFrame(newscale)
	end
end

-- formerly ItemRack_TooltipUpdate_OnUpdate
function Rack.TooltipUpdate()
	if ItemRack.TooltipType then

		local cooldown

		set_tooltip_anchor(ItemRack.TooltipOwner)

		if ItemRack.TooltipType=="BAG" then
			if ItemRack.TooltipBag==-1 then
				local _,_,id = string.find(GetContainerItemLink(ItemRack.TooltipBag,ItemRack.TooltipSlot) or "","item:(%d+)")
				if id then
					_,id = GetItemInfo(id)
					if id then
						-- :SetBagItem doesn't appear to work for -1 bank slot
						GameTooltip:SetHyperlink(id)
					end
				end
			else
				GameTooltip:SetBagItem(ItemRack.TooltipBag,ItemRack.TooltipSlot)
			end
			cooldown = GetContainerItemCooldown(ItemRack.TooltipBag,ItemRack.TooltipSlot)
			if ItemRack_Settings.TinyTooltip=="ON" and not IsAltKeyDown() then
				shrink_tooltip()
			end
		elseif ItemRack.TooltipType=="INVENTORY" then
			GameTooltip:SetInventoryItem("player",ItemRack.TooltipSlot)
			cooldown = GetInventoryItemCooldown("player",ItemRack.TooltipSlot)
			if ItemRack_Settings.TinyTooltip=="ON" and not IsAltKeyDown() then
				shrink_tooltip()
			end
			if ItemRack.TooltipBag then
				GameTooltip:AddLine(string.format(ItemRackText.QUEUED,ItemRack.TooltipBag))
			end
		end
		GameTooltip:Show()
		if cooldown==0 then
			-- stop updates if this item has no cooldown
			Rack.StopTimer("TooltipUpdate")
		end
	end
end

-- formerly ItemRack_ControlFrame_OnUpdate
function Rack.ControlFrame()
	if ItemRack_Users[user].Locked=="ON" and not IsAltKeyDown() then
		Rack.StopTimer("ControlFrame")
		ItemRack_ControlFrame:Hide()
	end
end

Rack.MenuFrameSources = { "ItemRack_InvFrame", "ItemRack_MenuFrame", "ItemRack_IconFrame",
						  "TitanPanelItemRackButton" }

-- formerly ItemRack_MenuFrame_OnUpdate
function Rack.MenuFrame()

	local over,frame
	
	for i=1,table.getn(Rack.MenuFrameSources) do
		frame = getglobal(Rack.MenuFrameSources[i])
		over = over or (frame and MouseIsOver(frame))
	end

	if MouseIsOver(ItemRack_SetsFrame) and
	  (string.sub(GetMouseFocus():GetName() or "",1,17)=="ItemRack_Sets_Inv") and
	  GetMouseFocus():GetAlpha()>.5 then
		over = 1
	end

	if not over then
		ItemRack_MenuFrame:Hide()
	end
end

--[[ Bank support ]]

function Rack.PopulateBank()
	Rack.UnpopulateBank()
	local itemLink,itemID,itemName,equipLoc
	for i=1,table.getn(ItemRack.BankSlots) do
		for j=1,GetContainerNumSlots(ItemRack.BankSlots[i]) do
			itemLink = GetContainerItemLink(ItemRack.BankSlots[i],j) or ""
			_,_,itemID = string.find(itemLink,"item:(%d+)")
			if itemID then
				itemName,_,_,_,_,_,_,_,equipLoc = GetItemInfo(itemID)
				if equipLoc and equipLoc~="" then
					ItemRack.BankedItems[itemName] = 1
				end
			end
		end
	end
end

function Rack.UnpopulateBank()
	for i in pairs(ItemRack.BankedItems) do
		ItemRack.BankedItems[i] = nil
	end
end

function Rack.BankOpened()
	Rack.PopulateBank()
	ItemRack.BankIsOpen = 1
	ItemRackFrame:RegisterEvent("BAG_UPDATE")
end

function Rack.BankClosed()
	ItemRack.BankIsOpen = nil
	ItemRack_MenuFrame:Hide()
	Rack.UnpopulateBank()
	ItemRackFrame:UnregisterEvent("BAG_UPDATE")
end

function Rack.SetHasBanked(setname)
	if not setname or not Rack_User[user].Sets[setname] then return end
	local name,item
	for i=0,19 do
		item = Rack_User[user].Sets[setname][i]
		if item and item.name and ItemRack.BankedItems[item.name] then
			return 1
		end
	end
end

function Rack.FindBankedItem(name)
	for _,i in pairs(ItemRack.BankSlots) do
		for j=1,GetContainerNumSlots(i) do
			if strfind(GetContainerItemLink(i,j) or "",name,1,1) then
				return i,j
			end
		end
	end
end

function Rack.PullSetFromBank(setname)
	Rack.ClearLockList()
	local set = Rack_User[user].Sets[setname]
	if not set or SpellIsTargeting() or CursorHasItem() then return end
	local bag,slot,freeBag,freeSlot
	for i=0,19 do
		if set[i] then
			if ItemRack.BankedItems[set[i].name] then
				bag,slot = Rack.FindBankedItem(set[i].name)
				if bag then
					freeBag,freeSlot = Rack.FindSpace()
					if freeBag then
						PickupContainerItem(bag,slot)
						PickupContainerItem(freeBag,freeSlot)
					else
						Rack.NoMoreRoom()
						return
					end
				end
			end
		end
	end
end

function Rack.PushSetToBank(setname)
	Rack.ClearLockList()
	local set = Rack_User[user].Sets[setname]
	if not set or SpellIsTargeting() or CursorHasItem() then return end
	local bag,slot,freeBag,freeSlot
	for i=0,19 do
		if set[i] then
			freeBag,freeSlot = Rack.FindSpace(1)
			if freeBag then
				inv,bag,slot = Rack.FindItem(set[i].id,set[i].name)
				if inv then
					PickupInventoryItem(inv)
					PickupContainerItem(freeBag,freeSlot)
				elseif bag then
					PickupContainerItem(bag,slot)
					PickupContainerItem(freeBag,freeSlot)
				end
			else
				Rack.NoMoreRoom()
				return
			end
		end
	end
end

function Rack.NoMoreRoom()
	DEFAULT_CHAT_FRAME:AddMessage("ItemRack: Not enough room to complete the swap.")
end

function Rack.BindSlots()
	local t={}
	for i=0,19 do
		table.insert(t,{string.match(ItemRack.Indexes[i].keybind,"(.+) Item"),"CLICK ItemRackInv"..i..":LeftButton"})
	end
	ClickBinder.Toggle("ItemRack",t)
end
