--[[ ItemRack 1.95 3/29/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.95

--[[ 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.95 -- 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

local 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)

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_ResetEventsButton"] = { text=ItemRackText.RESETEVENTSBUTTON_TEXT, tooltip=ItemRackText.RESETEVENTSBUTTON_TOOLTIP },
	["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" },
}

-- 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 i, found
	for i in 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,i,txt = false

	for i=2,15 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,15 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 i,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 i, 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 ItemRack.AmmoCounts do
			ItemRack.AmmoCounts[i] = 0
		end
	end

	if invslot<20 then

		-- 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 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
				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("")
			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

-- 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

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 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" 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 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()

	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


--[[ Frame functions ]]--

function ItemRack_OnLoad()

	-- hook for ALT+click of inventory slots (to add inventory slots to rack)
	oldItemRack_PaperDollItemSlotButton_OnClick = PaperDollItemSlotButton_OnClick
	PaperDollItemSlotButton_OnClick = 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_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 = {}
	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 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 ItemRack_Users do
		if ItemRack_Users[i].Events then
			for j in 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

local testtime = 0
function ItemRack_OnEvent(event)

	if event=="UNIT_INVENTORY_CHANGED" then
		if arg1=="player" then
			Rack.StartTimer("InvUpdate")
		end
	elseif event=="UPDATE_BINDINGS" then
		update_keybindings()

	elseif event=="PLAYER_LOGIN" then

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

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

		Rack.Initialize()
		initialize_data()
		initialize_events()

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


		this:RegisterEvent("UNIT_INVENTORY_CHANGED") -- read below
--[[

** READ IF ABOUT TO REGISTER/UNREGISTER ON PLAYER_ENTERING/LEAVING_WORLD **

This mod uses the "dirty flag" approach to frequent updates. I'm familiar 
with the unregister in PLAYER_LEAVING_WORLD and register in 
PLAYER_ENTERING_WORLD process (I've written posts describing how to 
implement it before).  There should be negligible processing going on 
while zoning with this mod. An actual inventory update only happens once 
on zoning, when the flurry of events are done.

How the dirty flag approach works is:
When an event happens, a timer is set to 0.
After X seconds, process the event.

Long ago when this mod was first created the inventory updates used to 
happen more often. For performance reasons I chose the dirty flag 
approach so it would always catch the last update and ignore the rest.

This mod will eventually abandon the dirty flag for 
UNIT_INVENTORY_CHANGED since the UI only fires one of these now even on 
full set swaps.  At that time I'll change to registering/unregistering 
when entering/leaving world, but for the moment it's an unnecessary 
change. (And will be more unnecessary in 1.11 when the default UI 
throttles the events on zoning)

/script local t,i=GetTime() for i=1,100000 do 
ItemRack_OnEvent("UNIT_INVENTORY_CHANGED") end print(GetTime()-t)
0.0039999 seconds

Given ~300 UNIT_INVENTORY_CHANGED can fire on zoning, that's 0.000012 
seconds to call all of them. That doesn't include event dispatch time so 
an actual test:

Zone time with dirty flag alone:
12.719 seconds
12.580 seconds
12.597 seconds
average: 12.632

Unregistering UNIT_INVENTORY_CHANGED on leaving, registering on entering world
12.665 seconds
12.556 seconds
12.645 seconds
average: 12.622

I'd say that's statistically negligible.

For scripted events, if you add a delay to the event then it will behave 
the same way as a dirty flag. The mod will ignore the entire flurry of 
events and only process the last one.

The other events this mod registers for (the ones below) do not fire
while zoning, except when they are necessary to fire while zoning.
If you do register/unregister the events, leave the RackFrame events
be or the queue system will behave unpredictably.
]]
		this:RegisterEvent("UPDATE_BINDINGS")
		
		RackFrame:RegisterEvent("PLAYER_REGEN_ENABLED") -- leaving combat
		RackFrame:RegisterEvent("PLAYER_UNGHOST") -- leaving ghost
		RackFrame:RegisterEvent("PLAYER_ALIVE") -- leaving death

		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 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

	oldItemRack_UseAction(slot,checkCursor,onSelf)

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)
		PickupInventoryItem(v1)
	elseif v1 and not MerchantFrame:IsVisible() then
		UseInventoryItem(v1)
	end
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)
	oldItemRack_UseInventoryItem(slot) -- call original UseInventoryItem
	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()

	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 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,30 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})
		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 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 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 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 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 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 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 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 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 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 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]
		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
	for i=1,scratchTableSize[2]-1 do
		eventname = scratchTable[2][i]
		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

	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 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 ItemRack.Register do
		foundtrigger=1
		foundevent=nil
		DEFAULT_CHAT_FRAME:AddMessage("__"..i.."__")
		for j in 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)
	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)
	local trigger = ItemRack_Events[eventname].trigger
	local has_event,i

	if ItemRack.Register[trigger] then
		ItemRack.Register[trigger][eventname] = nil
		for i in 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 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 ItemRack_Users[user].Events do
			if ItemRack_Users[user].Events[i].enabled then
				ItemRack_EnableEvent(i)
			else
				ItemRack_DisableEvent(i)
			end
		end
	else
		ItemRack_DisableAllEvents()
	end
end

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

	for i in ItemRack_Users[user].Events do
		ItemRack_DisableEvent(i)
	end
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 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 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 i,buff,mounted

	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
					mounted = true
					i = 25
				end
			elseif string.find(buff,"Mount_") then
				mounted = true
				i = 25
			end
		else
			i = 25
		end
	end

	return mounted
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

-- 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 i,linkid,bagtype,legal

	if bagid==0 then
		legal = true
	elseif bagid>=1 and bagid<=4 then
		_,_,linkid = string.find(GetInventoryItemLink("player",19+bagid) 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.FindSpace()

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

	if BankFrame:IsVisible() then bgStart,bgEnd = -1,10 end

	for i=bagEnd,bagStart,-1 do
		if Rack.ValidBag(i) then
			for j=1,GetContainerNumSlots(i) do
				if not Rack.LockList[i][j] and not GetContainerItemLink(i,j) then
					bag,slot = i,j
					Rack.LockList[i][j] = 1
					i,j = -99,99
				end
			end
		end
	end

	return bag,slot
end

-- clears locks on all bag slots
function Rack.ClearLockList(bag,slot)
	local i,j
	if not bag then
		for i=-2,10 do
			for j in 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 BankFrame:IsVisible() then bagStart,bagEnd = -1,10 end

	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,j = 99,99
					end
				end
			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
					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,j = 99,99
						end
					end
				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
							i = 99
						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 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 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
							DEFAULT_CHAT_FRAME:AddMessage("ItemRack: Not enough room to perform the swap.")
							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 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.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 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 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 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
			GameTooltip:SetBagItem(ItemRack.TooltipBag,ItemRack.TooltipSlot)
			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
