--[[

	LootLink 3.5: An in-game item database
		copyright 2004 by Telo
	
	- Watches all chat links you see to cache link color and link data
	- Automatically extracts data from items already in or added to your inventory
	- Automatically caches link data from items already in or added to your bank
	- Automatically inspects your target and extracts data for each of their equipped items
	- Automatically gets link data from your auction queries
	- Can perform a fully automatic scan of the entire auction house inventory
	- Stores sell prices for items that you've had in inventory when you've talked to a merchant
	  and displays them in the tooltips for stacks of items that you are looting, stacks of items
	  in your inventory and entries in the LootLink browse window
	- Converts green loot messages into correctly colored item messages if the item is cached
	- Provides a browsable, searchable window that allows you to find any item in the cache
	- Allows you to shift-click items in the browse window to insert links in the chat edit box
	
]]

--------------------------------------------------------------------------------------------------
-- Local LootLink variables
--------------------------------------------------------------------------------------------------

-- Function hooks
local lOriginal_CanSendAuctionQuery;
local lOriginal_AuctionFrameBrowse_OnEvent;
local lOriginal_ContainerFrameItemButton_OnEnter;
local lOriginal_ContainerFrame_Update;
local lOriginal_GameTooltip_SetLootItem;
local lOriginal_GameTooltip_SetOwner;
local lOriginal_GameTooltip_OnHide;
local lOriginal_GameTooltip_ClearMoney;
local lOriginal_GameTooltip_ClearMoney_Temp;
local lOriginal_ShoppingTooltip1_SetMerchantCompareItem;
local lOriginal_ShoppingTooltip2_SetMerchantCompareItem;
local lOriginal_ShoppingTooltip1_SetAuctionCompareItem;
local lOriginal_ShoppingTooltip2_SetAuctionCompareItem;
local lOriginal_SetItemRef;
local lOriginal_OnTooltipAddMoney;

--local ll = {};

-- If non-nil, kick off a full auction scan next time auctioneer is used
local lScanAuction;

-- Used for scanning inventory items for their sell prices at a merchant
local lBagID;
local lSlotID;

-- If non-nil, don't add extra information to the tooltip on GameTooltip_ClearMoney
local lSuppressInfoAdd;

-- If non-nil, check for appearance of GameTooltip for adding information
local lCheckTooltip;

-- Timer for frequency of tooltip checks
local lCheckTimer = 0;

-- The current owner of the GameTooltip
local lGameToolTipOwner;

-- Cache of auction item information
local lAuctionItemInfo;

-- Used to remember that confirmation is needed of irreversible commands
local lResetNeedsConfirm;
local lMakeHomeNeedsConfirm;
local lLightModeNeedsConfirm;

-- If non-nil, the data version upgrade reminder is not displayed on every /lootlink or /ll command
local lDisableVersionReminder;

-- The current server name and index
local lServer;
local lServerIndex;

-- The number of items in the database, total and for this server
local lItemLinksSizeTotal;
local lItemLinksSizeServer;

local STATE_NAME = 0;
local STATE_BOUND = 1;
local STATE_UNIQUE = 2;
local STATE_LOCATION = 3;
local STATE_TYPE = 4;
local STATE_DAMAGE = 5;
local STATE_DPS = 6;
local STATE_ARMOR = 7;
local STATE_BLOCK = 8;
local STATE_CONTAINER = 9;
local STATE_REQUIRES = 10;
local STATE_FINISH = 11;
local STATE_SPACE = 12
local STATE_SET = 13;

local tooltipSpace = " "..string.char(10);

local BINDS_DOES_NOT_BIND = 0;
local BINDS_EQUIP = 1;
local BINDS_PICKUP = 2;
local BINDS_USED = 3;

local TYPE_ARMOR = 0;
local TYPE_WEAPON = 1;
local TYPE_SHIELD = 2;
local TYPE_RECIPE = 3;
local TYPE_CONTAINER = 4;
local TYPE_MISC = 5;

local SUBTYPE_ARMOR_CLOTH = 0;
local SUBTYPE_ARMOR_LEATHER = 1;
local SUBTYPE_ARMOR_MAIL = 2;
local SUBTYPE_ARMOR_PLATE = 3;
local SUBTYPE_ARMOR_RELIC = 4;

local lColorSortTable = { };
lColorSortTable["ffff8000"] = 1;	-- legendary, orange
lColorSortTable["ffa335ee"] = 2;	-- epic, purple
lColorSortTable["ff0070dd"] = 3;	-- rare, blue
lColorSortTable["ff1eff00"] = 4;	-- uncommon, green
lColorSortTable["ffffffff"] = 5;	-- common, white
lColorSortTable["ff9d9d9d"] = 6;	-- poor, gray
lColorSortTable["ff40ffc0"] = 100;	-- unknown, teal

local ArmorSubtypes = { };
ArmorSubtypes[CLOTH] = SUBTYPE_ARMOR_CLOTH;
ArmorSubtypes[LEATHER] = SUBTYPE_ARMOR_LEATHER;
ArmorSubtypes[MAIL] = SUBTYPE_ARMOR_MAIL;
ArmorSubtypes[PLATE] = SUBTYPE_ARMOR_PLATE;
ArmorSubtypes[INVTYPE_RELIC] = SUBTYPE_ARMOR_RELIC;

local SUBTYPE_WEAPON_AXE = 0;
local SUBTYPE_WEAPON_BOW = 1;
local SUBTYPE_WEAPON_DAGGER = 2;
local SUBTYPE_WEAPON_MACE = 3;
local SUBTYPE_WEAPON_FISHING_POLE = 4;
local SUBTYPE_WEAPON_STAFF = 5;
local SUBTYPE_WEAPON_SWORD = 6;
local SUBTYPE_WEAPON_GUN = 7;
local SUBTYPE_WEAPON_WAND = 8;
local SUBTYPE_WEAPON_THROWN = 9;
local SUBTYPE_WEAPON_POLEARM = 10;
local SUBTYPE_WEAPON_FIST_WEAPON = 11;
local SUBTYPE_WEAPON_CROSSBOW = 12;

local WeaponSubtypes = { };
WeaponSubtypes[AXE] = SUBTYPE_WEAPON_AXE;
WeaponSubtypes[BOW] = SUBTYPE_WEAPON_BOW;
WeaponSubtypes[DAGGER] = SUBTYPE_WEAPON_DAGGER;
WeaponSubtypes[MACE] = SUBTYPE_WEAPON_MACE;
WeaponSubtypes[FISHING_POLE] = SUBTYPE_WEAPON_FISHING_POLE;
WeaponSubtypes[STAFF] = SUBTYPE_WEAPON_STAFF;
WeaponSubtypes[SWORD] = SUBTYPE_WEAPON_SWORD;
WeaponSubtypes[GUN] = SUBTYPE_WEAPON_GUN;
WeaponSubtypes[WAND] = SUBTYPE_WEAPON_WAND;
WeaponSubtypes[THROWN] = SUBTYPE_WEAPON_THROWN;
WeaponSubtypes[POLEARM] = SUBTYPE_WEAPON_POLEARM;
WeaponSubtypes[FIST_WEAPON] = SUBTYPE_WEAPON_FIST_WEAPON;
WeaponSubtypes[CROSSBOW] = SUBTYPE_WEAPON_CROSSBOW;

local SUBTYPE_SHIELD_BUCKLER = 0;
local SUBTYPE_SHIELD_SHIELD = 1;

local ShieldSubtypes = { };
ShieldSubtypes["Buckler"] = SUBTYPE_SHIELD_BUCKLER;
ShieldSubtypes[SHIELD] = SUBTYPE_SHIELD_SHIELD;

local SUBTYPE_RECIPE_ALCHEMY = 0;
local SUBTYPE_RECIPE_BLACKSMITHING = 1;
local SUBTYPE_RECIPE_COOKING = 2;
local SUBTYPE_RECIPE_ENCHANTING = 3;
local SUBTYPE_RECIPE_ENGINEERING = 4;
local SUBTYPE_RECIPE_LEATHERWORKING = 5;
local SUBTYPE_RECIPE_TAILORING = 6;
local SUBTYPE_RECIPE_FIRST_AID = 7;
local SUBTYPE_RECIPE_FISHING = 8;

local RecipeSubtypes = { };
RecipeSubtypes[ALCHEMY] = SUBTYPE_RECIPE_ALCHEMY;
RecipeSubtypes[BLACKSMITHING] = SUBTYPE_RECIPE_BLACKSMITHING;
RecipeSubtypes[COOKING] = SUBTYPE_RECIPE_COOKING;
RecipeSubtypes[ENCHANTING] = SUBTYPE_RECIPE_ENCHANTING;
RecipeSubtypes[ENGINEERING] = SUBTYPE_RECIPE_ENGINEERING;
RecipeSubtypes[LEATHERWORKING] = SUBTYPE_RECIPE_LEATHERWORKING;
RecipeSubtypes[TAILORING] = SUBTYPE_RECIPE_TAILORING;
RecipeSubtypes[FIRST_AID] = SUBTYPE_RECIPE_FIRST_AID;
RecipeSubtypes[FISHING] = SUBTYPE_RECIPE_FISHING;

local SUBTYPE_CONTAINER_BAG = 0;
local SUBTYPE_CONTAINER_AMMO_POUCH = 1;
local SUBTYPE_CONTAINER_QUIVER = 2;
local SUBTYPE_CONTAINER_SOUL_BAG = 3;
local SUBTYPE_CONTAINER_HERB_BAG = 4;
local SUBTYPE_CONTAINER_ENCHANTING_BAG = 5;
local SUBTYPE_CONTAINER_ENGINEERING_BAG = 6;

local ContainerSubtypes = { };
ContainerSubtypes[BAG] = SUBTYPE_CONTAINER_BAG;
ContainerSubtypes[AMMO_POUCH] = SUBTYPE_CONTAINER_AMMO_POUCH;
ContainerSubtypes[QUIVER] = SUBTYPE_CONTAINER_QUIVER;
ContainerSubtypes[SOUL_BAG] = SUBTYPE_CONTAINER_SOUL_BAG;
ContainerSubtypes[HERB_BAG] = SUBTYPE_CONTAINER_HERB_BAG;
ContainerSubtypes[ENCHANTING_BAG] = SUBTYPE_CONTAINER_ENCHANTING_BAG;
ContainerSubtypes[ENGINEERING_BAG] = SUBTYPE_CONTAINER_ENGINEERING_BAG;

local lTypeAndSubtypeToSkill = { };
lTypeAndSubtypeToSkill[TYPE_ARMOR] = { };
lTypeAndSubtypeToSkill[TYPE_ARMOR][SUBTYPE_ARMOR_CLOTH] = CLOTH;
lTypeAndSubtypeToSkill[TYPE_ARMOR][SUBTYPE_ARMOR_LEATHER] = LEATHER;
lTypeAndSubtypeToSkill[TYPE_ARMOR][SUBTYPE_ARMOR_MAIL] = MAIL;
lTypeAndSubtypeToSkill[TYPE_ARMOR][SUBTYPE_ARMOR_PLATE] = PLATE_MAIL;
lTypeAndSubtypeToSkill[TYPE_ARMOR][SUBTYPE_ARMOR_RELIC] = INVTYPE_RELIC;
lTypeAndSubtypeToSkill[TYPE_WEAPON] = { };
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_AXE] = { }
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_AXE][0] = AXES;
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_AXE][1] = TWO_HANDED_AXES;
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_BOW] = BOWS;
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_DAGGER] = DAGGERS;
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_MACE] = { }
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_MACE][0] = MACES;
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_MACE][1] = TWO_HANDED_MACES;
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_FISHING_POLE] = FISHING_POLE;
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_STAFF] = STAVES;
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_SWORD] = { };
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_SWORD][0] = SWORDS;
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_SWORD][1] = TWO_HANDED_SWORDS;
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_GUN] = GUNS;
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_WAND] = WANDS;
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_THROWN] = THROWN;
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_POLEARM] = POLEARMS;		--@todo Telo: unconfirmed
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_FIST_WEAPON] = UNARMED;
lTypeAndSubtypeToSkill[TYPE_WEAPON][SUBTYPE_WEAPON_CROSSBOW] = CROSSBOWS;
lTypeAndSubtypeToSkill[TYPE_SHIELD] = { };
lTypeAndSubtypeToSkill[TYPE_SHIELD][SUBTYPE_SHIELD_BUCKLER] = SHIELD;			--@todo Telo: deprecated subtype, should remove
lTypeAndSubtypeToSkill[TYPE_SHIELD][SUBTYPE_SHIELD_SHIELD] = SHIELD;
lTypeAndSubtypeToSkill[TYPE_RECIPE] = { };
lTypeAndSubtypeToSkill[TYPE_RECIPE][SUBTYPE_RECIPE_ALCHEMY] = ALCHEMY;
lTypeAndSubtypeToSkill[TYPE_RECIPE][SUBTYPE_RECIPE_BLACKSMITHING] = BLACKSMITHING;
lTypeAndSubtypeToSkill[TYPE_RECIPE][SUBTYPE_RECIPE_COOKING] = COOKING;
lTypeAndSubtypeToSkill[TYPE_RECIPE][SUBTYPE_RECIPE_ENCHANTING] = ENCHANTING;
lTypeAndSubtypeToSkill[TYPE_RECIPE][SUBTYPE_RECIPE_ENGINEERING] = ENGINEERING;
lTypeAndSubtypeToSkill[TYPE_RECIPE][SUBTYPE_RECIPE_LEATHERWORKING] = LEATHERWORKING;
lTypeAndSubtypeToSkill[TYPE_RECIPE][SUBTYPE_RECIPE_TAILORING] = TAILORING;
lTypeAndSubtypeToSkill[TYPE_RECIPE][SUBTYPE_RECIPE_FIRST_AID] = FIRST_AID;
lTypeAndSubtypeToSkill[TYPE_RECIPE][SUBTYPE_RECIPE_FISHING] = FISHING;
lTypeAndSubtypeToSkill[TYPE_CONTAINER] = { };
lTypeAndSubtypeToSkill[TYPE_CONTAINER][SUBTYPE_CONTAINER_BAG] = BAG;
lTypeAndSubtypeToSkill[TYPE_CONTAINER][SUBTYPE_CONTAINER_AMMO_POUCH] = AMMO_POUCH;
lTypeAndSubtypeToSkill[TYPE_CONTAINER][SUBTYPE_CONTAINER_QUIVER] = QUIVER;
lTypeAndSubtypeToSkill[TYPE_CONTAINER][SUBTYPE_CONTAINER_SOUL_BAG] = SOUL_BAG;
lTypeAndSubtypeToSkill[TYPE_CONTAINER][SUBTYPE_CONTAINER_ENCHANTING_BAG] = ENCHANTING_BAG;

local LocationTypes = { };
LocationTypes[INVTYPE_HOLDABLE]		= { i = 0, type = TYPE_MISC };
LocationTypes[INVTYPE_CLOAK]		= { i = 1, type = TYPE_ARMOR, subtypes = ArmorSubtypes };
LocationTypes[INVTYPE_WEAPON]		= { i = 2, type = TYPE_WEAPON, subtypes = WeaponSubtypes };
LocationTypes[INVTYPE_2HWEAPON]		= { i = 3, type = TYPE_WEAPON, subtypes = WeaponSubtypes };
LocationTypes[INVTYPE_WEAPONOFFHAND]	= { i = 4, type = TYPE_SHIELD, subtypes = ShieldSubtypes };
LocationTypes[INVTYPE_WRIST]		= { i = 5, type = TYPE_ARMOR, subtypes = ArmorSubtypes };
LocationTypes[INVTYPE_CHEST]		= { i = 6, type = TYPE_ARMOR, subtypes = ArmorSubtypes };
LocationTypes[INVTYPE_LEGS]		= { i = 7, type = TYPE_ARMOR, subtypes = ArmorSubtypes };
LocationTypes[INVTYPE_FEET]		= { i = 8, type = TYPE_ARMOR, subtypes = ArmorSubtypes };
LocationTypes[INVTYPE_BODY]		= { i = 9, type = TYPE_MISC };
LocationTypes[INVTYPE_RANGED]		= { i = 10, type = TYPE_WEAPON, subtypes = WeaponSubtypes };
LocationTypes[INVTYPE_WEAPONMAINHAND]	= { i = 11, type = TYPE_WEAPON, subtypes = WeaponSubtypes };
LocationTypes[INVTYPE_WAIST]		= { i = 12, type = TYPE_ARMOR, subtypes = ArmorSubtypes };
LocationTypes[INVTYPE_HEAD]		= { i = 13, type = TYPE_ARMOR, subtypes = ArmorSubtypes };
LocationTypes[GUN]			= { i = 14, type = TYPE_WEAPON, subtype = SUBTYPE_WEAPON_GUN };
LocationTypes[INVTYPE_FINGER]		= { i = 15, type = TYPE_MISC };
LocationTypes[INVTYPE_HAND]		= { i = 16, type = TYPE_ARMOR, subtypes = ArmorSubtypes };
LocationTypes[INVTYPE_SHOULDER]		= { i = 17, type = TYPE_ARMOR, subtypes = ArmorSubtypes };
LocationTypes[WAND]			= { i = 18, type = TYPE_WEAPON, subtype = SUBTYPE_WEAPON_WAND };
LocationTypes[INVTYPE_TRINKET]		= { i = 19, type = TYPE_MISC };
LocationTypes[INVTYPE_TABARD]		= { i = 20, type = TYPE_MISC };
LocationTypes[INVTYPE_NECK]		= { i = 21, type = TYPE_MISC };
LocationTypes[THROWN]			= { i = 22, type = TYPE_WEAPON, subtype = SUBTYPE_WEAPON_THROWN };
LocationTypes[CROSSBOW]			= { i = 23, type = TYPE_WEAPON, subtype = SUBTYPE_WEAPON_CROSSBOW };
LocationTypes[INVTYPE_RELIC]		= { i = 24, type = TYPE_ARMOR, subtype = SUBTYPE_ARMOR_RELIC };

local INVENTORY_SLOT_LIST = {
	{ name = "HeadSlot" },
	{ name = "NeckSlot" },
	{ name = "ShoulderSlot" },
	{ name = "BackSlot" },
	{ name = "ChestSlot" },
	{ name = "ShirtSlot" },
	{ name = "TabardSlot" },
	{ name = "WristSlot" },
	{ name = "HandsSlot" },
	{ name = "WaistSlot" },
	{ name = "LegsSlot" },
	{ name = "FeetSlot" },
	{ name = "Finger0Slot" },
	{ name = "Finger1Slot" },
	{ name = "Trinket0Slot" },
	{ name = "Trinket1Slot" },
	{ name = "MainHandSlot" },
	{ name = "SecondaryHandSlot" },
	{ name = "RangedSlot" },
};

local ChatMessageTypes = {
	"CHAT_MSG_SYSTEM",
	"CHAT_MSG_SAY",
	"CHAT_MSG_TEXT_EMOTE",
	"CHAT_MSG_YELL",
	"CHAT_MSG_WHISPER",
	"CHAT_MSG_PARTY",
	"CHAT_MSG_GUILD",
	"CHAT_MSG_OFFICER",
	"CHAT_MSG_CHANNEL",
	"CHAT_MSG_RAID",
	"CHAT_MSG_LOOT",
};

local LOOTLINK_DROPDOWN_LIST = {
	{ name = SORT_NAME, sortType = "name" },
	{ name = SORT_RARITY, sortType = "rarity" },
	{ name = SORT_LOCATION, sortType = "location" },
	{ name = SORT_TYPE, sortType = "type" },
	{ name = SORT_SUBTYPE, sortType = "subtype" },
	{ name = SORT_LEVEL, sortType = "level" },
	{ name = SORT_BINDS, sortType = "binds" },
	{ name = SORT_UNIQUE, sortType = "unique" },
	{ name = SORT_ARMOR, sortType = "armor" },
	{ name = SORT_BLOCK, sortType = "block" },
	{ name = SORT_MINDAMAGE, sortType = "minDamage" },
	{ name = SORT_MAXDAMAGE, sortType = "maxDamage" },
	{ name = SORT_DPS, sortType = "DPS" },
	{ name = SORT_SPEED, sortType = "speed" },
	{ name = SORT_SLOTS, sortType = "slots" },
	{ name = SORT_SKILL, sortType = "skill" },
	{ name = SORT_SET, sortType = "set" },
};

local LLS_RARITY_LIST = {
	{ name = ANY, value = nil },
	{ name = UNKNOWN, value = "ff40ffc0", r = 64 / 255, g = 255 / 255, b = 192 / 255 },
	{ name = POOR, value = "ff9d9d9d", r = 157 / 255, g = 157 / 255, b = 157 / 255 },
	{ name = COMMON, value = "ffffffff", r = 1, g = 1, b = 1 },
	{ name = UNCOMMON, value = "ff1eff00", r = 30 / 255, g = 1, b = 0 },
	{ name = RARE, value = "ff0070dd", r = 0, g = 70 / 255, b = 221 / 255 },
	{ name = EPIC, value = "ffa335ee", r = 163 / 255, g = 53 / 255, b = 238 / 255 },
	{ name = LEGENDARY, value = "ffff8000", r = 1, g = 128 / 255, b = 0 },
};

local LLS_BINDS_LIST = {
	{ name = ANY, value = nil },
	{ name = DOES_NOT, value = BINDS_DOES_NOT_BIND },
	{ name = ON_EQUIP, value = BINDS_EQUIP },
	{ name = ON_PICKUP, value = BINDS_PICKUP },
	{ name = ON_USE, value = BINDS_USE },
};

local LLS_TYPE_LIST = {
	{ name = ANY, value = nil },
	{ name = ARMOR, value = TYPE_ARMOR },
	{ name = CONTAINER, value = TYPE_CONTAINER },
	{ name = OTHER, value = TYPE_MISC },
	{ name = RECIPE, value = TYPE_RECIPE },
	{ name = SHIELD, value = TYPE_SHIELD },
	{ name = WEAPON, value = TYPE_WEAPON },
};

local LLS_SUBTYPE_ARMOR_LIST = {
	{ name = ANY, value = nil },
	{ name = CLOTH, value = SUBTYPE_ARMOR_CLOTH },
	{ name = LEATHER, value = SUBTYPE_ARMOR_LEATHER },
	{ name = MAIL, value = SUBTYPE_ARMOR_MAIL },
	{ name = PLATE, value = SUBTYPE_ARMOR_PLATE },
	{ name = INVTYPE_RELIC, value = SUBTYPE_ARMOR_RELIC },
};

local LLS_SUBTYPE_WEAPON_LIST = {
	{ name = ANY, value = nil },
	{ name = AXE, value = SUBTYPE_WEAPON_AXE },
	{ name = BOW, value = SUBTYPE_WEAPON_BOW },
	{ name = CROSSBOW, value = SUBTYPE_WEAPON_CROSSBOW },
	{ name = DAGGER, value = SUBTYPE_WEAPON_DAGGER },
	{ name = FISHING_POLE, value = SUBTYPE_WEAPON_FISHING_POLE },
	{ name = FIST_WEAPON, value = SUBTYPE_WEAPON_FIST_WEAPON },
	{ name = GUN, value = SUBTYPE_WEAPON_GUN },
	{ name = MACE, value = SUBTYPE_WEAPON_MACE },
	{ name = POLEARM, value = SUBTYPE_WEAPON_POLEARM },
	{ name = STAFF, value = SUBTYPE_WEAPON_STAFF },
	{ name = SWORD, value = SUBTYPE_WEAPON_SWORD },
	{ name = THROWN, value = SUBTYPE_WEAPON_THROWN },
	{ name = WAND, value = SUBTYPE_WEAPON_WAND },
};

local LLS_SUBTYPE_SHIELD_LIST = {
	{ name = ANY, value = nil },
	{ name = BUCKLER, value = SUBTYPE_SHIELD_BUCKLER },
	{ name = SHIELD, value = SUBTYPE_SHIELD_SHIELD },
};

local LLS_SUBTYPE_RECIPE_LIST = {
	{ name = ANY, value = nil },
	{ name = ALCHEMY, value = SUBTYPE_RECIPE_ALCHEMY },
	{ name = BLACKSMITHING, value = SUBTYPE_RECIPE_BLACKSMITHING },
	{ name = COOKING, value = SUBTYPE_RECIPE_COOKING },
	{ name = ENCHANTING, value = SUBTYPE_RECIPE_ENCHANTING },
	{ name = ENGINEERING, value = SUBTYPE_RECIPE_ENGINEERING },
	{ name = LEATHERWORKING, value = SUBTYPE_RECIPE_LEATHERWORKING },
	{ name = TAILORING, value = SUBTYPE_RECIPE_TAILORING },
};

local LLS_SUBTYPE_CONTAINER_LIST = {
	{ name = ANY, value = nil },
	{ name = BAG, value = SUBTYPE_CONTAINER_BAG },
	{ name = AMMO_POUCH, value = SUBTYPE_CONTAINER_AMMO_POUCH },
	{ name = QUIVER, value = SUBTYPE_CONTAINER_QUIVER },
	{ name = ENCHANTING_BAG, value = SUBTYPE_CONTAINER_ENCHANTING_BAG },
	{ name = ENGINEERING_BAG, value = SUBTYPE_CONTAINER_ENGINEERING_BAG },
	{ name = HERB_BAG, value = SUBTYPE_CONTAINER_HERB_BAG },
	{ name = SOUL_BAG, value = SUBTYPE_CONTAINER_SOUL_BAG },
};

local LLS_LOCATION_LIST = {
	{ name = ANY, },
	{ name = NONE, },
	{ name = INVTYPE_CLOAK, },
	{ name = INVTYPE_CHEST, },
	{ name = CROSSBOW, },
	{ name = INVTYPE_FEET, },
	{ name = INVTYPE_FINGER, },
	{ name = INVTYPE_HAND, },
	{ name = INVTYPE_HEAD, },
	{ name = INVTYPE_HOLDABLE, },
	{ name = GUN, },
	{ name = INVTYPE_LEGS, },
	{ name = INVTYPE_WEAPONMAINHAND, },
	{ name = INVTYPE_NECK, },
	{ name = INVTYPE_WEAPONOFFHAND, },
	{ name = INVTYPE_WEAPON, },
	{ name = INVTYPE_RANGED, },
	{ name = INVTYPE_BODY, },
	{ name = INVTYPE_SHOULDER, },
	{ name = INVTYPE_TABARD, },
	{ name = THROWN, },
	{ name = INVTYPE_TRINKET, },
	{ name = INVTYPE_2HWEAPON, },
	{ name = INVTYPE_WAIST, },
	{ name = WAND, },
	{ name = INVTYPE_WRIST, },
};

local LLS_EXTRA_LIST = {
	{ name = ALL, },
	{ name = ONLY, },
	{ name = NONE, },
};

local LLO_RARITY_LIST = {
	{ name = NONE },
	{ name = "|cff9d9d9d"..POOR },
	{ name = "|cffffffff"..COMMON },
	{ name = "|cff1eff00"..UNCOMMON },
	{ name = "|cff0070dd"..RARE },
	{ name = "|cffa335ee"..EPIC },
	{ name = "|cffff8000"..LEGENDARY },
};

local lMagicCharacters = { };
lMagicCharacters["^"] = 1;
lMagicCharacters["$"] = 1;
lMagicCharacters["("] = 1;
lMagicCharacters[")"] = 1;
lMagicCharacters["%"] = 1;
lMagicCharacters["."] = 1;
lMagicCharacters["["] = 1;
lMagicCharacters["]"] = 1;
lMagicCharacters["*"] = 1;
lMagicCharacters["+"] = 1;
lMagicCharacters["-"] = 1;
lMagicCharacters["?"] = 1;

local lBankBagIDs = { BANK_CONTAINER, 5, 6, 7, 8, 9, 10, };
	
-- Item types that can't have enchants or "of" stat bonuses
local lNoBonuses = {
	[""] = 2,
	["INVTYPE_TRINKET"] = 2,
	["INVTYPE_AMMO"] = 2,
	["INVTYPE_THROWN"] = 2,
	["INVTYPE_BAG"] = 2,
	["INVTYPE_TABARD"] = 2,
	["INVTYPE_BODY"] = 2,
	["INVTYPE_HOLDABLE"] = 1,
	["INVTYPE_FINGER"] = 1,
	["INVTYPE_WAIST"] = 1,
}

local lRarityFilter = {
	ff40ffc0 = -1,
	ff9d9d9d = 0,
	ffffffff = 1,
	ff1eff00 = 2,
	ff0070dd = 3,
	ffa335ee = 4,
	ffff8000 = 5,
};

--------------------------------------------------------------------------------------------------
-- Global LootLink variables
--------------------------------------------------------------------------------------------------

LOOTLINK_VERSION = 350;	-- version 3.50

LOOTLINK_ITEM_HEIGHT = 16;
LOOTLINK_ITEMS_SHOWN = 22;

UIPanelWindows["LootLinkFrame"] =		{ area = "left",	pushable = 11,	whileDead = 1 };
UIPanelWindows["LootLinkSearchFrame"] =		{ area = "center",	pushable = 0,	whileDead = 1 };
UIPanelWindows["LootLinkOptionsFrame"] =	{ area = "center",	pushable = 0,	whileDead = 1 };

--------------------------------------------------------------------------------------------------
-- Internal functions
--------------------------------------------------------------------------------------------------

--
-- Functions with no other functional dependencies
--

local function LootLink_MakeIntFromHexString(str)
	local remain = str;
	local amount = 0;
	while( remain ~= "" ) do
		amount = amount * 16;
		local byteVal = string.byte(strupper(strsub(remain, 1, 1)));
		if( byteVal >= string.byte("0") and byteVal <= string.byte("9") ) then
			amount = amount + (byteVal - string.byte("0"));
		elseif( byteVal >= string.byte("A") and byteVal <= string.byte("F") ) then
			amount = amount + 10 + (byteVal - string.byte("A"));
		end
		remain = strsub(remain, 2);
	end
	return amount;
end

local function LootLink_GetRGBFromHexColor(hexColor)
	local red = LootLink_MakeIntFromHexString(strsub(hexColor, 3, 4)) / 255;
	local green = LootLink_MakeIntFromHexString(strsub(hexColor, 5, 6)) / 255;
	local blue = LootLink_MakeIntFromHexString(strsub(hexColor, 7, 8)) / 255;
	return red, green, blue;
end

local function LootLink_MouseIsOver(frame)
	local x, y = GetCursorPosition();
	
	if( not frame ) then
		return nil;
	end
	
	x = x / frame:GetEffectiveScale();
	y = y / frame:GetEffectiveScale();

	local left = frame:GetLeft();
	local right = frame:GetRight();
	local top = frame:GetTop();
	local bottom = frame:GetBottom();
	
	if( not left or not right or not top or not bottom ) then
		return nil;
	end
	
	if( (x > left and x < right) and (y > bottom and y < top) ) then
		return 1;
	else
		return nil;
	end
end

local function LootLink_GetDataVersion()
	if( not LootLinkState or not LootLinkState.DataVersion ) then
		return 0;
	end
	return LootLinkState.DataVersion;
end

local function LootLink_SetDataVersion(version)
	if( not LootLinkState ) then
		LootLinkState = { };
	end
	if( not LootLinkState.DataVersion or LootLinkState.DataVersion < version ) then
		LootLinkState.DataVersion = version;
	end
end

local function LootLink_AddServer(name)
	if( not LootLinkState ) then
		LootLinkState = { };
	end
	
	if( not LootLinkState.Servers ) then
		LootLinkState.Servers = 0;
		LootLinkState.ServerNamesToIndices = { };
	end
	
	if( not LootLinkState.ServerNamesToIndices[name] ) then
		LootLinkState.ServerNamesToIndices[name] = LootLinkState.Servers;
		LootLinkState.Servers = LootLinkState.Servers + 1;
	end
	
	return LootLinkState.ServerNamesToIndices[name];
end

local function LootLink_ConvertServerFormat(item)
	if( item.servers ) then
		local i, v;
		local list;
		for i, v in item.servers do
			if( not list ) then
				list = TEXT(i);
			else
				list = list.." "..i;
			end
		end
		item.s = list;
		item.servers = nil;
	end
end

local function LootLink_CheckItemServerRaw(item, serverIndex)
	if( not item.s ) then
		return nil;
	end
	
	local server;
	for server in string.gfind(item.s, "(%d+)") do
		if( tonumber(server) == serverIndex ) then
			return 1;
		end
	end

	return nil;
end

local function LootLink_AddItemServer(item, serverIndex)
	if( not item.s ) then
		item.s = TEXT(serverIndex);
	elseif( not LootLink_CheckItemServerRaw(item, serverIndex) ) then
		item.s = item.s.." "..serverIndex;
	end
end

local function LootLink_EscapeString(string)
	return string.gsub(string, "\"", "\\\"");
end

local function LootLink_UnescapeString(string)
	return string.gsub(string, "\\\"", "\"");
end

local function LootLink_EscapePattern(string)
	local result = "";
	local remain = string;
	local char;
	
	while( remain ~= "" ) do
		char = strsub(remain, 1, 1);
		if( lMagicCharacters[char] ) then
			result = result.."%"..char;
		else
			result = result..char;
		end
		remain = strsub(remain, 2);
	end
	
	return result;
end

local function LootLink_CheckNumeric(string)
	local remain = string;
	local hasNumber;
	local hasPeriod;
	local char;
	
	while( remain ~= "" ) do
		char = strsub(remain, 1, 1);
		if( char >= "0" and char <= "9" ) then
			hasNumber = 1;
		elseif( char == "." and not hasPeriod ) then
			hasPeriod = 1;
		else
			return nil;
		end
		remain = strsub(remain, 2);
	end
	
	return hasNumber;
end

local function LootLink_NameFromLink(link)
	local name;
	if( not link ) then
		return nil;
	end
	for name in string.gfind(link, "|c%x+|Hitem:%d+:%d+:%d+:%d+|h%[(.-)%]|h|r") do
		return name;
	end
	return nil;
end

function LootLink_MoneyToggle(force)
	if( lOriginal_GameTooltip_ClearMoney_Temp ) then
		GameTooltip_ClearMoney = lOriginal_GameTooltip_ClearMoney_Temp;
		lOriginal_GameTooltip_ClearMoney_Temp = nil;
	elseif ( not force ) then
		lOriginal_GameTooltip_ClearMoney_Temp = GameTooltip_ClearMoney;
		GameTooltip_ClearMoney = LootLink_GameTooltip_ClearMoney_Temp;
	else
		DEFAULT_CHAT_FRAME:AddMessage( "LootLink_MoneyToggle just tried to break?" );
	end
end

local function LootLink_MatchType(left, right)
	local lt = LocationTypes[left];
	local _type;
	local subtype;
	
	if( lt ) then
		local subtypes;
		
		-- Check for weapon override of base type
		if( WeaponSubtypes[right] ) then
			_type = TYPE_WEAPON;
			subtypes = WeaponSubtypes;
		else
			_type = lt.type;
			subtypes = lt.subtypes;
		end
		
		if( subtypes ) then
			subtype = subtypes[right];
		else
			subtype = lt.subtype;
		end
		return nil, lt.i, _type, subtype;
	end
	
	return 1, nil, TYPE_MISC, nil;
end

local function LootLink_HideTooltipMoney()
	LootLinkTooltipMoneyFrame:SetPoint("LEFT", "LootLinkTooltipTextLeft1", "LEFT", 0, 0);
	LootLinkTooltipMoneyFrame:Hide();
end

local function LootLink_SetTooltipMoney(frame, count, money, stack)
	if( count and count > 1 ) then
		money = money * count;
		frame:AddLine(format(LOOTLINK_SELL_PRICE_N, count), 1.0, 1.0, 1.0);
	elseif( stack ) then
		frame:AddLine(LOOTLINK_SELL_PRICE_EACH, 1.0, 1.0, 1.0);
	else
		frame:AddLine(LOOTLINK_SELL_PRICE, 1.0, 1.0, 1.0);
	end
	
	local numLines = frame:NumLines();
	local moneyFrame = getglobal(frame:GetName().."MoneyFrame");
	local newLine = frame:GetName().."TextLeft"..numLines;
	
	moneyFrame:SetPoint("LEFT", newLine, "RIGHT", 4, 0);
	moneyFrame:Show();
	MoneyFrame_Update(moneyFrame:GetName(), money);
	frame:SetMinimumWidth(moneyFrame:GetWidth() + getglobal(newLine):GetWidth() - 10);
end

local function LL_SearchData(item, tag)
	if( item.d ) then
		local s, e;
		local value;

		s, e, value = string.find(item.d, tag.."(.-)")
		if( value ) then
			return tonumber(value);
		end
	end
	return nil;
end

--
-- Functions that are dependent on preceding local functions, ordered appropriately
--

local function LootLink_GetName( elem )
	if ( type(elem) == "table" ) then
		return elem[1], elem[2];
	else
		return elem;
	end
end

local function LootLink_GetValue( elem, index, link )
	local name;
	if ( type(elem) == "table" ) then
		index = elem[2];
		elem = elem[1];
	end
	if ( link and ItemLinks[elem] and ItemLinks[elem].i ~= link ) then
		if ( ItemLinks[elem].m ) then
			for k, v in ItemLinks[elem].m do
				if v.i == link then
					index = k;
					break;
				end
			end
			if ( not index ) then
				--DEFAULT_CHAT_FRAME:AddMessage( elem.." "..link.." doesn't appear to exist yet?" );
				return nil;
			end
		end
	end
	if ( index and ItemLinks[elem] and ItemLinks[elem].m ) then
		return ItemLinks[elem].m[index], index;
	elseif ( ItemLinks[elem] ) then
		return ItemLinks[elem];
	else
		return nil;
	end
end

local function LootLink_InitSizes(serverIndex)
	local index;
	local value;

	lItemLinksSizeTotal = 0;
	lItemLinksSizeServer = 0;
	
	for index, value in ItemLinks do
		lItemLinksSizeTotal = lItemLinksSizeTotal + 1;
		if( LootLink_CheckItemServer(value, serverIndex) ) then
			lItemLinksSizeServer = lItemLinksSizeServer + 1;
		end
	end
end

local function LootLink_GetSize(serverIndex)
	if( not serverIndex ) then
		return lItemLinksSizeTotal;
	end
	return lItemLinksSizeServer;
end

local function LootLink_Status()
	DEFAULT_CHAT_FRAME:AddMessage(format(LOOTLINK_STATUS_HEADER, LOOTLINK_VERSION / 100));
	if( LootLinkState ) then
		DEFAULT_CHAT_FRAME:AddMessage(format(LOOTLINK_DATA_VERSION, LootLink_GetSize(), LootLink_GetSize(lServerIndex), lServer, LootLink_GetDataVersion() / 100));
		if( LootLinkState.HideInfo ) then
			DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_INFO_HIDDEN);
		else
			DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_INFO_SHOWN);
		end
		if( LootLinkState.LightMode ) then
			DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_LIGHT_MODE);
		else
			DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_FULL_MODE);
		end
	else
		DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_INFO_SHOWN);
	end
	if( lScanAuction ) then
		DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_SCHEDULED_AUCTION_SCAN);
	end
end

local function LootLink_GetHyperlink(name, index)
	local itemLink = LootLink_GetValue( name, index );
	if( itemLink and itemLink.i --[[and LootLink_CheckItemServer(itemLink, lServerIndex)]] ) then
		-- Remove instance-specific data that we captured from the link we return
		local item = string.gsub(itemLink.i, "(%d+):(%d+):(%d+):(%d+)", "%1:%2:%3:0");
		return "item:"..item;
	end
	return nil;
end

local function LootLink_GetLink(name, index)
	local itemLink = LootLink_GetValue(name, index);
	if( itemLink and itemLink.c and itemLink.i --[[and (IsAltKeyDown() or LootLink_CheckItemServer(itemLink, lServerIndex))]] ) then
		local link = "|c"..itemLink.c.."|H"..LootLink_GetHyperlink(name, index).."|h["..name.."]|h|r";
		return link;
	end
	return nil;
end

local function LootLink_GetSet(set)
	if ( not LootLinkState.sets ) then
		LootLinkState.sets = {}
	end
	local sets = LootLinkState.sets;
	for k, v in sets do
		if ( v == set ) then
			return k;
		end
	end
	tinsert(sets, set);
	return getn(sets);
end

function LootLink_BuildSearchData(name, value, alternate)
	local state, itemLink, loop, index, field, left, right, iStart, iEnd, val1, val2, val3, foundRequires, _type, subtype = STATE_NAME, LootLink_GetHyperlink(name, alternate);
	
	if( not itemLink ) then
		return nil;
	end
	
	-- This is the only place the tooltip hyperlink is set directly, therefore
	-- this should only be called when we know that the tooltip will be valid

	-- Protect money frame while we set hidden tooltip
	LootLink_MoneyToggle();
	LLHiddenTooltip:SetOwner(WorldFrame,"ANCHOR_NONE");
	LLHiddenTooltip:SetHyperlink(itemLink);
	LootLink_MoneyToggle(1);
	
	-- Double check that the data is good
	local realname, link, quality, level, _, _, _, count = GetItemInfo(itemLink);
	if ( not realname ) then
		LootLink_ScheduleItem(name,LootLink_GetValue(name,alternate).i);
		return nil;
	else
		local tooltipname = LLHiddenTooltipTextLeft1:GetText();
		if ( realname ~= tooltipname ) then
			-- if this returns, we're in trouble.
			return nil;
		end
	end

	local savedD = value.d;
	local savedT = value.t;

	value.d = "";
	if( not LootLinkState.LightMode ) then
		value.t = "";
	end
	
	value.c = string.gsub( ITEM_QUALITY_COLORS[quality].hex, "|c(.+)", "%1" );
	
	for index = 1, 30, 1 do
		field = getglobal("LLHiddenTooltipTextLeft"..index);
		if( field and field:IsShown() ) then
			left = field:GetText();
		else
			left = nil;
		end
		
		field = getglobal("LLHiddenTooltipTextRight"..index);
		if( field and field:IsShown() ) then
			right = field:GetText();
		else
			right = nil;
		end
		
		if( left ) then
			if( not LootLinkState.LightMode ) then
				-- cull set data to show 0/# and don't save durability
				left = string.gsub( left, "(.+ %()%d+(%/%d%))", "%10%2" );
				if ( string.sub( left, 1, 10 ) ~= "Durability" ) then
					value.t = value.t..left.."";
				end
			end
		else
			left = "";
		end
		if( right ) then
			if( not LootLinkState.LightMode ) then
				value.t = value.t..right.."";
			end
		else
			right = "";
		end
		
		loop = 1;
		while( loop ) do
			if( state == STATE_NAME ) then
				state = STATE_BOUND;
				loop = nil;
			elseif( state == STATE_BOUND ) then
				iStart, iEnd, val1 = string.find(left, "Binds when (.+)");
				if( val1 ) then
					if( val1 == "equipped" ) then
						value.d = value.d.."bi"..BINDS_EQUIP.."";
					elseif( val1 == "picked up" ) then
						value.d = value.d.."bi"..BINDS_PICKUP.."";
					elseif( val1 == "used" ) then
						value.d = value.d.."bi"..BINDS_USED.."";
					end
					loop = nil;
				else
					value.d = value.d.."bi"..BINDS_DOES_NOT_BIND.."";
				end
				state = STATE_UNIQUE;
			elseif( state == STATE_UNIQUE ) then
				if( string.find(left, "Unique") ) then
					value.d = value.d.."un1";
					loop = nil;
				end
				state = STATE_LOCATION;
			elseif( state == STATE_LOCATION ) then
				local location;
				loop, location, _type, subtype = LootLink_MatchType(left, right);
				if( location ) then
					value.d = value.d.."lo"..location.."";
				end
				if( _type == TYPE_WEAPON ) then
					state = STATE_DAMAGE;
				elseif( _type == TYPE_ARMOR or _type == TYPE_SHIELD ) then
					state = STATE_ARMOR;
				else
					state = STATE_CONTAINER;
				end
			elseif( state == STATE_DAMAGE ) then
				iStart, iEnd, val1, val2 = string.find(left, "(%d+) %- (%d+) Damage");
				if( val1 and val2 ) then
					value.d = value.d.."mi"..val1.."ma"..val2.."";
				end
				iStart, iEnd, val1 = string.find(right, "Speed (.+)");
				if( val1 ) then
					value.d = value.d.."sp"..val1.."";
				end
				state = STATE_DPS;
				loop = nil;
			elseif( state == STATE_DPS ) then
				iStart, iEnd, val1 = string.find(left, "%((.+) damage per second%)");
				if( val1 ) then
					value.d = value.d.."dp"..val1.."";
				end
				state = STATE_REQUIRES;
				loop = nil;
			elseif( state == STATE_ARMOR ) then
				iStart, iEnd, val1 = string.find(left, "(%d+) Armor");
				if( val1 ) then
					value.d = value.d.."ar"..val1.."";
				end
				if( _type == TYPE_SHIELD ) then
					state = STATE_BLOCK;
				else
					state = STATE_REQUIRES;
				end
				loop = nil;
			elseif( state == STATE_BLOCK ) then
				iStart, iEnd, val1 = string.find(left, "(%d+) Block");
				if( val1 ) then
					value.d = value.d.."bl"..val1.."";
				end
				state = STATE_REQUIRES;
				loop = nil;
			elseif( state == STATE_CONTAINER ) then
				iStart, iEnd, val1, val2 = string.find(left, "(%d+) Slot (.+)");
				if( val1 ) then
					_type = TYPE_CONTAINER;
					subtype = ContainerSubtypes[val2];
					value.d = value.d.."sl"..val1.."";
					loop = nil;
				end
				state = STATE_REQUIRES;
			elseif( state == STATE_REQUIRES ) then
				iStart, iEnd, val1 = string.find(left, "Requires (.+)");
				if( val1 ) then
					iStart, iEnd, val2 = string.find(val1, "Level (%d+)");
					if( val2 ) then
						value.d = value.d.."le"..val2.."";
					else
						iStart, iEnd, val2, val3 = string.find(val1, "(.+) %((%d+)%)");
						if( val2 and val3 ) then
							val1 = RecipeSubtypes[val2];
							if( val1 and _type == TYPE_MISC ) then
								_type = TYPE_RECIPE;
								subtype = val1;
								value.d = value.d.."sk"..val3.."";
							end
						end
					end
				else
					state = STATE_SPACE;
				end
				loop = nil;
			elseif( state == STATE_SPACE ) then
				if ( left == tooltipSpace ) then
					state = STATE_SET;
				end
				loop = nil;
			elseif( state == STATE_SET ) then
				if ( left ~= "" ) then
					iStart, iEnd, val1, val2 = string.find(left, "(.+) %(%d+%/(%d+)%)");
					if( val2 ) then
						local set = LootLink_GetSet(val1);
						value.d = value.d.."se"..set.."";
						state = STATE_FINISH;
					end
				end
				loop = nil;
			elseif( state == STATE_FINISH ) then
				loop = nil;
			end
		end
	end
	if( _type ) then
		value.d = value.d.."ty".._type.."";
	end
	if( subtype ) then
		value.d = value.d.."su"..subtype.."";
	end
	
	if ( realname and name ~= realname ) then
		if ( not ItemLinks[realname] ) then
			ItemLinks[realname] = {};
			ItemLinks[realname] = value;
		else
			if ( ItemLinks[realname].i ~= value.i and ItemLinks[realname].m ) then
				local found;
				for k, v in ItemLinks[realname].m do
					if v.i == value.i then
						found = 1;
						break;
					end
				end
				if ( not found ) then
					tinsert( ItemLinks[realname].m, value );
				end
			end
		end
	end
		
	if ( savedD ~= value.d or savedT ~= value.t ) then
		return 2;
	end
	return 1;
end

local function BuildUsabilityData(data)
	local nSkills;
	local iSkill;
	local HeaderData = { };
	local name, header, isExpanded, rank;
	local Collapse = { };
	local nToCollapse = 0;
	local iCollapse;

	data.class = UnitClass("player");
	data.race = UnitRace("player");
	data.level = UnitLevel("player");
	data.skills = { };
	
	-- We need to expand all of the skills, but first want to save off their state
	nSkills = GetNumSkillLines();
	for iSkill = 1, nSkills do
		local name, header, isExpanded, rank = GetSkillLineInfo(iSkill);
		if( header and not isExpanded ) then
			-- Since we don't know the final index for this item yet, we'll store it by name
			HeaderData[name] = 0;
		end
	end
	
	-- Now expand everything and save off our known skills
	ExpandSkillHeader(0);
	nSkills = GetNumSkillLines()
	for iSkill = 1, nSkills do
		local name, header, isExpanded, rank = GetSkillLineInfo(iSkill);
		if( not header ) then
			data.skills[name] = rank;
		elseif( HeaderData[name] ) then
			-- We now know the final index for this header item
			HeaderData[name] = iSkill;
		end
	end
	
	-- Finally, return the skills page to its original state
	for name, iSkill in HeaderData do
		Collapse[nToCollapse + 1] = iSkill;
		nToCollapse = nToCollapse + 1;
	end
	if( nToCollapse > 0 ) then
		table.sort(Collapse);
		for iCollapse = nToCollapse, 1, -1 do
			CollapseSkillHeader(Collapse[iCollapse]);
		end
	end
end

local function LootLink_GetSkillRank(ud, _type, subtype, location)
	local entry = lTypeAndSubtypeToSkill[_type][subtype];
	
	if( type(entry) == "table" ) then
		-- Two-handed vs. One-handed weapon
		if( location ) then
			if( location == 3 ) then
				return ud.skills[entry[1]];
			else
				return ud.skills[entry[0]];
			end
		else
			return nil;
		end
	else
		-- Everything else
		return ud.skills[entry];
	end
end

local function LootLink_SetHyperlink(tooltip, name, link, index)
	-- If the link isn't in the local cache, it may not be valid
	if ( not link ) then
		return nil;
	end
	local value = ItemLinks[name];
	if ( index and value.m ) then
		value = value.m[index];
	end
	if( not GetItemInfo(link) ) then
		-- To avoid disconnects, we'll create our own lame tooltip for these
		if( value ) then
			local extraSkip = 0;
			local lines = 1;
			local usabilityData;
			
			-- Name, in rarity color
			tooltip:SetText("|c"..value.c..name.."|r");
			
			-- Binds on equip, binds on pickup
			local binds = LL_SearchData(value, "bi");
			if( binds == BINDS_EQUIP ) then
				tooltip:AddLine(ITEM_BIND_ON_EQUIP, 1, 1, 1);
				lines = lines + 1;
			elseif( binds == BINDS_PICKUP ) then
				tooltip:AddLine(ITEM_BIND_ON_PICKUP, 1, 1, 1);
				lines = lines + 1;
			elseif( binds == BINDS_USED ) then
				tooltip:AddLine(ITEM_BIND_ON_USE, 1, 1, 1);
				lines = lines + 1;
			end
			
			-- Unique?
			local unique = LL_SearchData(value, "un");
			if( unique ) then
				tooltip:AddLine("Unique", 1, 1, 1);
				lines = lines + 1;
			end
			
			local _type = LL_SearchData(value, "ty");
			local subtype = LL_SearchData(value, "su");

			-- Equip location and type/subtype
			local location = LL_SearchData(value, "lo");
			if( location ) then
				local subtypes;
				local name;
				for i, v in LocationTypes do
					if( v.i == location ) then
						name = i;
						subtypes = v.subtypes;
						break;
					end
				end
				if( name ) then
					tooltip:AddLine(name, 1, 1, 1);
					lines = lines + 1;
					if( subtype ) then
						if( _type == TYPE_WEAPON ) then
							subtypes = WeaponSubtypes;
						end
						if( subtypes ) then
							for i, v in subtypes do
								if( v == subtype ) then
									if( i == name ) then
										local line = getglobal(tooltip:GetName().."TextLeft"..lines);
										
										if( not usabilityData ) then
											usabilityData = { };
											BuildUsabilityData(usabilityData);
										end
										if( not LootLink_GetSkillRank(usabilityData, _type, subtype, location) ) then
											line:SetTextColor(1, 0, 0);
										end
									else
										local line = getglobal(tooltip:GetName().."TextRight"..lines);
										line:SetText(i);

										if( not usabilityData ) then
											usabilityData = { };
											BuildUsabilityData(usabilityData);
										end
										if( LootLink_GetSkillRank(usabilityData, _type, subtype, location) ) then
											line:SetTextColor(1, 1, 1);
										else
											line:SetTextColor(1, 0, 0);
										end
										
										line:Show();
										extraSkip = extraSkip + 1;
										break;
									end
								end
							end
						end
					end
				end
			end
			
			-- Now do type specific data
			if( _type == TYPE_ARMOR ) then
				local armor = LL_SearchData(value, "ar");
				if( armor ) then
					tooltip:AddLine(armor.." Armor", 1, 1, 1);
					lines = lines + 1;
				end
			elseif( _type == TYPE_WEAPON ) then
				local min = LL_SearchData(value, "mi");
				local max = LL_SearchData(value, "ma");
				local speed = LL_SearchData(value, "sp");
				local dps = LL_SearchData(value, "dp");
				
				if( min and max ) then
					tooltip:AddLine(min.." - "..max.." Damage", 1, 1, 1);
					lines = lines + 1;
					if( speed ) then
						local line = getglobal(tooltip:GetName().."TextRight"..lines);
						line:SetText(format("Speed %.2f", tonumber(speed)));
						line:SetTextColor(1, 1, 1);
						line:Show();
						extraSkip = extraSkip + 1;
					end
				end
				if( dps ) then
					tooltip:AddLine("("..dps.." damage per second)", 1, 1, 1);
					lines = lines + 1;
				end
			elseif( _type == TYPE_SHIELD ) then
				local armor = LL_SearchData(value, "ar");
				local block = LL_SearchData(value, "bl");
				if( armor ) then
					tooltip:AddLine(armor.." Armor", 1, 1, 1);
					lines = lines + 1;
				end
				if( block ) then
					tooltip:AddLine(block.." Block", 1, 1, 1);
					lines = lines + 1;
				end
			elseif( _type == TYPE_RECIPE ) then
				local skill = LL_SearchData(value, "sk");
				if( skill and subtype ) then
					for i, v in RecipeSubtypes do
						if( v == subtype ) then
							if( not usabilityData ) then
								usabilityData = { };
								BuildUsabilityData(usabilityData);
							end
							local rank = LootLink_GetSkillRank(usabilityData, _type, subtype, location);
							if( not rank or rank < skill ) then
								tooltip:AddLine("Requires "..i.." ("..skill..")", 1, 0, 0);
							else
								tooltip:AddLine("Requires "..i.." ("..skill..")", 1, 1, 1);
							end
							lines = lines + 1;
							break;
						end
					end
				end
			elseif( _type == TYPE_CONTAINER ) then
				local slots = LL_SearchData(value, "sl");
				if( slots ) then
					local subtype = LL_SearchData(value, "su") or 0;
					local bag = lTypeAndSubtypeToSkill[_type][subtype]; -- repurposed skill table, fix later?
					tooltip:AddLine(slots.." Slot "..bag, 1, 1, 1);
					lines = lines + 1;
				end
			end
			
			local level = LL_SearchData(value, "le");

			-- Now add any extra text data that we have
			if( value.t ) then
				local skip = lines + extraSkip;
				local piece;
				for piece in string.gfind(value.t, "(.-)") do
					if( lines < 29 ) then
						if( skip == 0 ) then
							if( string.find(piece, "Requires Level .*") ) then
								if( level and tonumber(level) > UnitLevel("player") ) then
									tooltip:AddLine(piece, 1, 0, 0, 1, 1);
								else
									tooltip:AddLine(piece, 1, 1, 1, 1, 1);
								end
							elseif( string.find(piece, ":") ) then
								if( string.find(piece, "^Class") ) then
									if( string.find(piece, "^Class.*"..UnitClass("player")) ) then
										tooltip:AddLine(piece, 1, 1, 1, 1, 1);
									else
										tooltip:AddLine(piece, 1, 0, 0, 1, 1);
									end
								else
									tooltip:AddLine(piece, 0, 1, 0, 1, 1);
								end
							elseif( string.find(piece, "\"") or string.find(piece, "%(%d+/%d+%)") ) then
								tooltip:AddLine(piece, 1, 0.8235, 0, 1, 1);
							elseif( string.find(piece, "^  ") ) then
								tooltip:AddLine(piece, 0.5, 0.5, 0.5, 1, 1);
							else
								tooltip:AddLine(piece, 1, 1, 1, 1, 1);
							end
							lines = lines + 1;
						else
							skip = skip - 1;
						end
					end
				end
			end
			
			-- And, after all that, let the user know that we faked this tooltip
			if( lines < 30 ) then
				tooltip:AddLine("|cff40ffc0<"..LOOTLINK_TOOLTIP_GENERATED..">|r");
				lines = lines + 1;
			end
			
			if( lines < 30 and not LootLink_CheckItemServer(value, lServerIndex) ) then
				tooltip:AddLine("|cff40ffc0<"..LOOTLINK_TOOLTIP_SERVER..">|r");
				lines = lines + 1;
			end
			
			-- Finally, show the tooltip, which adjusts its size
			tooltip:Show();
		end
	else
		-- Get the actual tooltip from the cache
		tooltip:SetHyperlink(link);
		
		-- After setting the tooltip, parse its data
		LootLink_ScheduleItem(name, value.i);
		--LootLink_BuildSearchData(name, value, index);
			
		if( not LootLink_ValidateItem(name, link) ) then
			tooltip:AddLine("|cff40ffc0<"..LOOTLINK_TOOLTIP_BAD_ITEM..">|r");
		elseif( not LootLink_CheckItemServerRaw(value, lServerIndex) ) then
			tooltip:AddLine("|cff40ffc0<"..LOOTLINK_TOOLTIP_SERVER..">|r");
			--LootLink_AddItemServer(value, lServerIndex);
			--lItemLinksSizeServer = lItemLinksSizeServer + 1;
			--return 1;
		end
	end
end

local function LootLink_SetTitle()
	local lootLinkTitle = getglobal("LootLinkTitleText");
	local total = LootLink_GetSize(lServerIndex);
	local size;
	
	if( not DisplayIndices ) then
		size = total;
	else
		size = DisplayIndices.onePastEnd - 1;
	end
	if( size < total ) then
		if( size == 1 ) then
			lootLinkTitle:SetText(TEXT(LOOTLINK_TITLE_FORMAT_PARTIAL_SINGULAR));
		else
			lootLinkTitle:SetText(format(TEXT(LOOTLINK_TITLE_FORMAT_PARTIAL_PLURAL), size));
		end
	else
		if( size == 1 ) then
			lootLinkTitle:SetText(TEXT(LOOTLINK_TITLE_FORMAT_SINGULAR));
		else
			lootLinkTitle:SetText(format(TEXT(LOOTLINK_TITLE_FORMAT_PLURAL), size));
		end
	end
end

local function LootLink_MatchesSearch(name, value, ud)
	if( not value ) then
		return nil;
	end
	if( LootLinkState ) then
		if ( LootLinkState.spoof and name ) then
			if ( not LootLink_ValidateItem( name, value.i ) ) then
				return nil;
			end
		end
		if ( LootLinkState.server ) then
			if ( not LootLink_CheckItemServer(value, lServerIndex) ) then
				return nil;
			end
		end
		if ( LootLinkState.rarityfilter ) then
			if ( value.c and lRarityFilter[value.c] < LootLinkState.rarityfilter ) then
				return nil;
			end
		end
		if ( LootLinkState.noname ) then
			if ( ItemLinks[name] and ItemLinks[name].i and ItemLinks[name].i ~= value.i ) then
				return nil;
			end
		else
			if ( LootLinkState.novariants ) then
				if ( string.gsub(value.i, "%d+:%d+:(%d+):%d+", "%1") ~="0" and ItemLinks[name] and ItemLinks[name].i and ItemLinks[name].i ~= value.i ) then
					return nil;
				end
			end
			if ( LootLinkState.enchants ) then
				if ( string.gsub(value.i, "%d+:(%d+):%d+:%d+", "%1") ~= "0" ) then
					return nil;
				end
			end
		end
	end
	if( not LootLinkFrame.SearchParams or not name ) then
		return 1;
	end
	
	if( value.d ) then
		local sp = LootLinkFrame.SearchParams; 
		
		if( sp.server ) then
			if ( not LootLink_CheckItemServer(value, lServerIndex) ) then
				return nil;
			end
		end
		
		if( sp.spoof ) then
			if ( LootLink_ValidateItem( name, value.i ) ) then
				return nil;
			end
		end
		
		if( sp.text ) then
			if( not value.t ) then
				return nil;
			end
			if( not string.find(string.lower(value.t), string.lower(sp.text), 1, sp.plain) ) then
				return nil;
			end
		end
		
		if( sp.name ) then
			if( not string.find(string.lower(name), string.lower(sp.name), 1, sp.plain) ) then
				return nil;
			end
		end
		
		if( sp.rarity ) then
			if( LLS_RARITY_LIST[sp.rarity].value ~= value.c ) then
				return nil;
			end
		end
		
		if( sp.binds ) then
			if( LLS_BINDS_LIST[sp.binds].value ~= LL_SearchData(value, "bi") ) then
				return nil;
			end
		end
		
		if( sp.unique ) then
			if( sp.unique ~= LL_SearchData(value, "un") ) then
				return nil;
			end
		end
		
		if( sp.usable ) then
			local _type = LL_SearchData(value, "ty");
			local subtype = LL_SearchData(value, "su");
			local level = LL_SearchData(value, "le");
			local skill = LL_SearchData(value, "sk");
			
			-- Check for the required skill
			if( _type ) then
				if( subtype ) then
					local rank = LootLink_GetSkillRank(ud, _type, subtype, LL_SearchData(value, "lo"));
					if( not rank or (skill and skill > rank) ) then
						return nil;
					end
				end
			end
			
			-- Check for the required class
			if( value.t and string.find(value.t, "Class") and not string.find(value.t, "Class.*"..UnitClass("player")) ) then
				return nil;
			end
			
			-- Check level requirement
			if( level and level > ud.level ) then
				return nil;
			end
		end
		
		if( sp.location ) then
			local location = LL_SearchData(value, "lo");
		
			if( sp.location == 2 ) then
				if( location ) then
					return nil;
				end
			elseif( sp.location ~= 1 ) then
				if( LocationTypes[LLS_LOCATION_LIST[sp.location].name].i ~= location ) then
					return nil;
				end
			end
		end
		
		if( sp.minLevel ) then
			local level = LL_SearchData(value, "le");
			if( not level or level < sp.minLevel ) then
				return nil;
			end
		end
		
		if( sp.maxLevel ) then
			local level = LL_SearchData(value, "le");
			if( level and level > sp.maxLevel ) then
				return nil;
			end
		end
		
		if( sp.set ) then
			local set = LL_SearchData(value, "se");
			if( not set ) then
				return nil;
			end
		end
		
		if( sp.type ) then
			local _type = LLS_TYPE_LIST[sp.type].value;
			if( _type ~= LL_SearchData(value, "ty") ) then
				return nil;
			end
			if( sp.subtype ) then
				local subtype;
				if( _type == TYPE_ARMOR ) then
					subtype = LLS_SUBTYPE_ARMOR_LIST[sp.subtype].value;
				elseif( _type == TYPE_SHIELD ) then
					subtype = LLS_SUBTYPE_SHIELD_LIST[sp.subtype].value;
				elseif( _type == TYPE_WEAPON ) then
					subtype = LLS_SUBTYPE_WEAPON_LIST[sp.subtype].value;
				elseif( _type == TYPE_RECIPE ) then
					subtype = LLS_SUBTYPE_RECIPE_LIST[sp.subtype].value;
				elseif( _type == TYPE_CONTAINER ) then
					subtype = LLS_SUBTYPE_CONTAINER_LIST[sp.subtype].value;
				end
				if( subtype and subtype ~= LL_SearchData(value, "su") ) then
					return nil;
				end
			end
			
			if( _type == TYPE_WEAPON ) then
				if( sp.minMinDamage ) then
					local damage = LL_SearchData(value, "mi");
					if( not damage or damage < sp.minMinDamage ) then
						return nil;
					end
				end

				if( sp.minMaxDamage ) then
					local damage = LL_SearchData(value, "ma");
					if( not damage or damage < sp.minMaxDamage ) then
						return nil;
					end
				end

				if( sp.maxSpeed ) then
					local speed = LL_SearchData(value, "sp");
					if( not speed or speed > sp.maxSpeed ) then
						return nil;
					end
				end

				if( sp.minDPS ) then
					local DPS = LL_SearchData(value, "dp");
					if( not DPS or DPS < sp.minDPS ) then
						return nil;
					end
				end
			elseif( _type == TYPE_ARMOR or _type == TYPE_SHIELD ) then
				if( sp.minArmor ) then
					local armor = LL_SearchData(value, "ar");
					if( not armor or armor < sp.minArmor ) then
						return nil;
					end
				end
				
				if( _type == TYPE_SHIELD ) then
					if( sp.minBlock ) then
						local block = LL_SearchData(value, "bl");
						if( not block or block < sp.minBlock ) then
							return nil;
						end
					end
				end
			elseif( _type == TYPE_CONTAINER ) then
				if( sp.minSlots ) then
					local slots = LL_SearchData(value, "sl");
					if( not slots or slots < sp.minSlots ) then
						return nil;
					end
				end
			elseif( _type == TYPE_RECIPE ) then
				if( sp.minSkill ) then
					local skill = LL_SearchData(value, "sk");
					if( not skill or skill < sp.minSkill ) then
						return nil;
					end
				end
				
				if( sp.maxSkill ) then
					local skill = LL_SearchData(value, "sk");
					if( not skill or skill > sp.maxSkill ) then
						return nil;
					end
				end
			end
		end
	else
		return nil;
	end

	return 1;
end

local function LootLink_NameComparison(elem1, elem2)
	local v1, i1 = elem1[1], elem1[3];
	local v2, i2 = elem2[1], elem2[3];
	
	if ( v1 == v2 ) then
		if ( i1 and i2 ) then
			return i1 < i2;
		elseif ( i1 ) then
			return false;
		elseif ( i2 ) then
			return true;
		end
	end
	return v1 < v2;
end

local function LootLink_ColorComparison(elem1, elem2)
	local color1;
	local color2;
	local v1 = elem1[2];
	local v2 = elem2[2];
	
	if( v1 and v1.c ) then
		color1 = v1.c;
	else
		color1 = "ffffffff";
	end
	if( v2 and v2.c ) then
		color2 = v2.c;
	else
		color2 = "ffffffff";
	end
	
	if( color1 == color2 ) then
		return LootLink_NameComparison( elem1, elem2 );
	end
	
	if( not lColorSortTable[color1] and not lColorSortTable[color2] ) then
		return color1 < color2;
	end
	
	if( lColorSortTable[color1] ) then
		if( lColorSortTable[color2] ) then
			return lColorSortTable[color1] < lColorSortTable[color2];
		end
		return nil;
	end
	return 1;
end

local function LootLink_GenericComparison(elem1, elem2, v1, v2)
	if( v1 == v2 ) then
		return LootLink_NameComparison( elem1, elem2 );
	end
	if( not v1 ) then
		return 1;
	end
	if( not v2 ) then
		return nil;
	end
	if( tonumber(v1) and tonumber(v2) ) then
		return tonumber(v1) < tonumber(v2);
	end
	return v1 < v2;
end

local function LootLink_BindsComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(ItemLinks[elem1], "bi");
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(ItemLinks[elem2], "bi");
	end
	
	return LootLink_GenericComparison(elem1, elem2, v1, v2);
end

local function LootLink_UniqueComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(value1, "un");
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(value2, "un");
	end
	
	return LootLink_GenericComparison(elem1, elem2, v1, v2);
end

local function LootLink_LocationComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(value1, "lo");
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(value2, "lo");
	end
	
	return LootLink_GenericComparison(elem1, elem2, v1, v2);
end

local function LootLink_TypeComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(value1, "ty");
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(value2, "ty");
	end
	
	return LootLink_GenericComparison(elem1, elem2, v1, v2);
end

local function LootLink_SubtypeComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(value1, "su");
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(value2, "su");
	end
	
	return LootLink_GenericComparison(elem1, elem2, v1, v2);
end

local function LootLink_MinDamageComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(value1, "mi");
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(value2, "mi");
	end
	
	return LootLink_GenericComparison(elem1, elem2, v1, v2);
end

local function LootLink_MaxDamageComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(value1, "ma");
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(value2, "ma");
	end
	
	return LootLink_GenericComparison(elem1, elem2, v1, v2);
end

local function LootLink_SpeedComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(value1, "sp");
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(value2, "sp");
	end
	
	return LootLink_GenericComparison(elem1, elem2, v1, v2);
end

local function LootLink_DPSComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(value1, "dp");
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(value2, "dp");
	end
	
	return LootLink_GenericComparison(elem1, elem2, v1, v2);
end

local function LootLink_ArmorComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(value1, "ar");
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(value2, "ar");
	end
	
	return LootLink_GenericComparison(elem1, elem2, v1, v2);
end

local function LootLink_BlockComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(value1, "bl");
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(value2, "bl");
	end
	
	return LootLink_GenericComparison(elem1, elem2, v1, v2);
end

local function LootLink_SlotsComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(value1, "sl");
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(value2, "sl");
	end
	
	return LootLink_GenericComparison(elem1, elem2, v1, v2);
end

local function LootLink_SkillComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(value1, "sk");
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(value2, "sk");
	end
	
	return LootLink_GenericComparison(elem1, elem2, v1, v2);
end

local function LootLink_LevelComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(value1, "le");
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(value2, "le");
	end
	
	return LootLink_GenericComparison(elem1, elem2, v1, v2);
end

-- Ugly, but functional
local function LootLink_SetComparison(elem1, elem2)
	local v1, v2;
	
	local value1 = elem1[2];
	local value2 = elem2[2];
	
	local sets = LootLinkState.sets;
	
	if ( not sets ) then
		return LootLink_NameComparison(elem1, elem2);
	end
	
	if( value1 and value1.d ) then
		v1 = LL_SearchData(value1, "se");
		v1 = sets[v1];
	end
	if( value2 and value2.d ) then
		v2 = LL_SearchData(value2, "se");
		v2 = sets[v2];
	end
	
	if ( not v1 and not v2 ) then
		return LootLink_NameComparison(elem1, elem2);
	elseif ( not v1 ) then
		return nil;
	elseif ( not v2 ) then
		return 1;
	else
		if ( v1 == v2 ) then
			return LootLink_NameComparison(elem1, elem2);
		else
			return v1 < v2;
		end
	end
end

local function LootLink_Sort()
	if( LOOTLINK_DROPDOWN_LIST[UIDropDownMenu_GetSelectedID(LootLinkFrameDropDown)].sortType ) then
		local sortType = LOOTLINK_DROPDOWN_LIST[UIDropDownMenu_GetSelectedID(LootLinkFrameDropDown)].sortType;
		if( sortType == "name" ) then
			table.sort(DisplayIndices, LootLink_NameComparison);
		elseif( sortType == "rarity" ) then
			table.sort(DisplayIndices, LootLink_ColorComparison);
		elseif( sortType == "binds" ) then
			table.sort(DisplayIndices, LootLink_BindsComparison);
		elseif( sortType == "unique" ) then
			table.sort(DisplayIndices, LootLink_UniqueComparison);
		elseif( sortType == "location" ) then
			table.sort(DisplayIndices, LootLink_LocationComparison);
		elseif( sortType == "type" ) then
			table.sort(DisplayIndices, LootLink_TypeComparison);
		elseif( sortType == "subtype" ) then
			table.sort(DisplayIndices, LootLink_SubtypeComparison);
		elseif( sortType == "minDamage" ) then
			table.sort(DisplayIndices, LootLink_MinDamageComparison);
		elseif( sortType == "maxDamage" ) then
			table.sort(DisplayIndices, LootLink_MaxDamageComparison);
		elseif( sortType == "speed" ) then
			table.sort(DisplayIndices, LootLink_SpeedComparison);
		elseif( sortType == "DPS" ) then
			table.sort(DisplayIndices, LootLink_DPSComparison);
		elseif( sortType == "armor" ) then
			table.sort(DisplayIndices, LootLink_ArmorComparison);
		elseif( sortType == "block" ) then
			table.sort(DisplayIndices, LootLink_BlockComparison);
		elseif( sortType == "slots" ) then
			table.sort(DisplayIndices, LootLink_SlotsComparison);
		elseif( sortType == "skill" ) then
			table.sort(DisplayIndices, LootLink_SkillComparison);
		elseif( sortType == "level" ) then
			table.sort(DisplayIndices, LootLink_LevelComparison);
		elseif( sortType == "set" ) then
			table.sort(DisplayIndices, LootLink_SetComparison);
		end
	end
end

local function LootLink_BuildDisplayIndices()
	local iNew = 1;
	local index;
	local value;
	local UsabilityData;
	
	if( LootLinkFrame.SearchParams and LootLinkFrame.SearchParams.usable ) then
		UsabilityData = { };
		BuildUsabilityData(UsabilityData);
	end
	
	DisplayIndices = { };
	for index, value in ItemLinks do
		if( LootLink_MatchesSearch(index, value, UsabilityData) ) then
			DisplayIndices[iNew] = { index, value, nil };
			iNew = iNew + 1;
		end
		if( value.m ) then
			for i, altvalue in value.m do
				if( LootLink_MatchesSearch(index, altvalue, UsabilityData) ) then
					DisplayIndices[iNew] = { index, altvalue, i };
					iNew = iNew + 1;
				end
			end
		end	
	end
	DisplayIndices.onePastEnd = iNew;
	LootLink_SetDataVersion(100); -- version 1.00
	LootLink_Sort();
	LootLink_SetTitle();
	LootLinkQuickSearch_Clear();
end

local function LootLink_Reset()
	ItemLinks = { };

	LootLink_SetDataVersion(110);	-- version 1.10

	LootLink_InitSizes(lServerIndex);
	
	if( DisplayIndices ) then
		LootLink_BuildDisplayIndices();
		LootLink_Update();
	end
end

local function LootLink_MakeHome()
	local index;
	local value;

	LootLink_SetDataVersion(110);	-- version 1.10
	
	for index, value in ItemLinks do
		if( not value._ ) then
			-- If this item predates multiple server support, mark is as seen on this server
			LootLink_AddItemServer(value, lServerIndex);
		else
			-- Otherwise just wipe the flag since it only applies to pre-1.10 data
			value._ = nil;
		end
	end

	LootLink_InitSizes(lServerIndex);
end

local function LootLink_LightMode()
	local index;
	local value;

	for index, value in ItemLinks do
		value.t = nil;
	end
	LootLinkState.LightMode = 1;
end

--
-- Uncategorized functions; will sort later
--

local function LootLinkFrameDropDown_Initialize()
	local info;
	for i = 1, getn(LOOTLINK_DROPDOWN_LIST), 1 do
		info = { };
		info.text = LOOTLINK_DROPDOWN_LIST[i].name;
		info.func = LootLinkFrameDropDownButton_OnClick;
		UIDropDownMenu_AddButton(info);
	end
end

local function LLS_RarityDropDown_Initialize()
	local info;
	for i = 1, getn(LLS_RARITY_LIST), 1 do
		info = { };
		info.text = LLS_RARITY_LIST[i].name;
		info.func = LLS_RarityDropDown_OnClick;
		if( LLS_RARITY_LIST[i].value ) then
			info.textR = LLS_RARITY_LIST[i].r;
			info.textG = LLS_RARITY_LIST[i].g;
			info.textB = LLS_RARITY_LIST[i].b;
		end
		UIDropDownMenu_AddButton(info);
	end
end

local function LLO_RarityDropDown_Initialize()
	local info;
	for i = 1, getn(LLO_RARITY_LIST), 1 do
		info = { };
		info.text = LLO_RARITY_LIST[i].name;
		info.func = LLO_RarityDropDown_OnClick;
		UIDropDownMenu_AddButton(info);
	end
end

local function LLS_BindsDropDown_Initialize()
	local info;
	for i = 1, getn(LLS_BINDS_LIST), 1 do
		info = { };
		info.text = LLS_BINDS_LIST[i].name;
		info.func = LLS_BindsDropDown_OnClick;
		UIDropDownMenu_AddButton(info);
	end
end

local function LLS_LocationDropDown_Initialize()
	local info;
	for i = 1, getn(LLS_LOCATION_LIST), 1 do
		info = { };
		info.text = LLS_LOCATION_LIST[i].name;
		info.func = LLS_LocationDropDown_OnClick;
		UIDropDownMenu_AddButton(info);
	end
end

local function LLS_TypeDropDown_Initialize()
	local info;
	for i = 1, getn(LLS_TYPE_LIST), 1 do
		info = { };
		info.text = LLS_TYPE_LIST[i].name;
		info.func = LLS_TypeDropDown_OnClick;
		UIDropDownMenu_AddButton(info);
	end
end

local function LLS_SubtypeDropDownArmor_Initialize()
	local info;
	for i = 1, getn(LLS_SUBTYPE_ARMOR_LIST), 1 do
		info = { };
		info.text = LLS_SUBTYPE_ARMOR_LIST[i].name;
		info.func = LLS_SubtypeDropDown_OnClick;
		UIDropDownMenu_AddButton(info);
	end
end

local function LLS_SubtypeDropDownShield_Initialize()
	local info;
	for i = 1, getn(LLS_SUBTYPE_SHIELD_LIST), 1 do
		info = { };
		info.text = LLS_SUBTYPE_SHIELD_LIST[i].name;
		info.func = LLS_SubtypeDropDown_OnClick;
		UIDropDownMenu_AddButton(info);
	end
end

local function LLS_SubtypeDropDownWeapon_Initialize()
	local info;
	for i = 1, getn(LLS_SUBTYPE_WEAPON_LIST), 1 do
		info = { };
		info.text = LLS_SUBTYPE_WEAPON_LIST[i].name;
		info.func = LLS_SubtypeDropDown_OnClick;
		UIDropDownMenu_AddButton(info);
	end
end

local function LLS_SubtypeDropDownRecipe_Initialize()
	local info;
	for i = 1, getn(LLS_SUBTYPE_RECIPE_LIST), 1 do
		info = { };
		info.text = LLS_SUBTYPE_RECIPE_LIST[i].name;
		info.func = LLS_SubtypeDropDown_OnClick;
		UIDropDownMenu_AddButton(info);
	end
end

local function LLS_SubtypeDropDownContainer_Initialize()
	local info;
	for i = 1, getn(LLS_SUBTYPE_CONTAINER_LIST), 1 do
		info = { };
		info.text = LLS_SUBTYPE_CONTAINER_LIST[i].name;
		info.func = LLS_SubtypeDropDown_OnClick;
		UIDropDownMenu_AddButton(info);
	end
end

local function LootLink_UIDropDownMenu_SetSelectedID(frame, id, names)
	UIDropDownMenu_SetSelectedID(frame, id);
	if( not frame ) then
		frame = this;
	end
	UIDropDownMenu_SetText(names[id].name, frame);
end

local function LootLink_SetupTypeUI(iType, iSubtype)
	local _type = LLS_TYPE_LIST[iType].value;
	
	if( not iSubtype ) then
		iSubtype = 1;
	end

	-- Hide all of the variable labels and fields to start
	getglobal("LLS_SubtypeLabel"):Hide();
	getglobal("LLS_SubtypeDropDown"):Hide();
	getglobal("LLS_MinimumArmorLabel"):Hide();
	getglobal("LLS_MinimumBlockLabel"):Hide();
	getglobal("LLS_MinimumDamageLabel"):Hide();
	getglobal("LLS_MaximumDamageLabel"):Hide();
	getglobal("LLS_MaximumSpeedLabel"):Hide();
	getglobal("LLS_MinimumDPSLabel"):Hide();
	getglobal("LLS_MinimumSlotsLabel"):Hide();
	getglobal("LLS_MinimumSkillLabel"):Hide();
	getglobal("LLS_MaximumSkillLabel"):Hide();
	getglobal("LLS_MinimumArmorEditBox"):Hide();
	getglobal("LLS_MinimumBlockEditBox"):Hide();
	getglobal("LLS_MinimumDamageEditBox"):Hide();
	getglobal("LLS_MaximumDamageEditBox"):Hide();
	getglobal("LLS_MaximumSpeedEditBox"):Hide();
	getglobal("LLS_MinimumDPSEditBox"):Hide();
	getglobal("LLS_MinimumSlotsEditBox"):Hide();
	getglobal("LLS_MinimumSkillEditBox"):Hide();
	getglobal("LLS_MaximumSkillEditBox"):Hide();
	
	if( _type ~= TYPE_OTHER ) then
		local label = getglobal("LLS_SubtypeLabel");
		local dropdown = getglobal("LLS_SubtypeDropDown");
		local initfunc;
		
		-- Show the dropdown and its label
		label:Show();
		dropdown:Show();
		
		if( _type == TYPE_ARMOR ) then
			label:SetText(LLS_SUBTYPE_ARMOR);
			initfunc = LLS_SubtypeDropDownArmor_Initialize;
			
			getglobal("LLS_MinimumArmorLabel"):Show();
			getglobal("LLS_MinimumArmorEditBox"):Show();
		elseif( _type == TYPE_SHIELD ) then
			label:SetText(LLS_SUBTYPE_SHIELD);
			initfunc = LLS_SubtypeDropDownShield_Initialize;

			getglobal("LLS_MinimumArmorLabel"):Show();
			getglobal("LLS_MinimumBlockLabel"):Show();
			getglobal("LLS_MinimumArmorEditBox"):Show();
			getglobal("LLS_MinimumBlockEditBox"):Show();
		elseif( _type == TYPE_WEAPON ) then
			label:SetText(LLS_SUBTYPE_WEAPON);
			initfunc = LLS_SubtypeDropDownWeapon_Initialize;
			
			getglobal("LLS_MinimumDamageLabel"):Show();
			getglobal("LLS_MaximumDamageLabel"):Show();
			getglobal("LLS_MaximumSpeedLabel"):Show();
			getglobal("LLS_MinimumDPSLabel"):Show();
			getglobal("LLS_MinimumDamageEditBox"):Show();
			getglobal("LLS_MaximumDamageEditBox"):Show();
			getglobal("LLS_MaximumSpeedEditBox"):Show();
			getglobal("LLS_MinimumDPSEditBox"):Show();
		elseif( _type == TYPE_CONTAINER ) then
			label:SetText(LLS_SUBTYPE_CONTAINER);
			initfunc = LLS_SubtypeDropDownContainer_Initialize;

			getglobal("LLS_MinimumSlotsLabel"):Show();
			getglobal("LLS_MinimumSlotsEditBox"):Show();
		else
			label:SetText(LLS_SUBTYPE_RECIPE);
			initfunc = LLS_SubtypeDropDownRecipe_Initialize;

			getglobal("LLS_MinimumSkillLabel"):Show();
			getglobal("LLS_MaximumSkillLabel"):Show();
			getglobal("LLS_MinimumSkillEditBox"):Show();
			getglobal("LLS_MaximumSkillEditBox"):Show();
		end
		
		UIDropDownMenu_Initialize(dropdown, initfunc);
		UIDropDownMenu_SetSelectedID(LLS_SubtypeDropDown, iSubtype);
	end
	
	LLS_TextEditBox:SetFocus();
end

local function LootLink_InspectSlot(unit, id)
	local link = GetInventoryItemLink(unit, id);
	if( link ) then
		local name = LootLink_ProcessLinks(link, 1);
		if( name and ItemLinks[name] ) then
			local count = GetInventoryItemCount(unit, id);
			if( count > 1 ) then
				ItemLinks[name].x = 1;
			end
			LootLink_Event_InspectSlot(name, count, ItemLinks[name], unit, id);
		end
	end
end

function LootLink_Inspect(who)
	if ( not UnitIsPlayer(who) ) then return; end
	if ( who == "mouseover" ) then
		if ( not LLInspect_List ) then
			LLInspect_List = {};
		end
		local name = UnitName(who);
		local time = GetTime();
		local lasttime = LLInspect_List[name];
		if ( lasttime and time - lasttime < 120 ) then
			return nil;
		end
		if ( not lasttime ) then
			LLInspect_List.total = (LLInspect_List.total or 0) + 1;
		end
		if ( LLInspect_List.total and LLInspect_List.total > 250 ) then
			local list = 0;
			for k, v in LLInspect_List do
				list = list + 1;
				v = nil;
				if ( list == 100 ) then
					break;
				end
			end
		end
				
		LLInspect_List[name] = time;
	end	
			
	local index;
	
	for index = 1, getn(INVENTORY_SLOT_LIST), 1 do
		LootLink_InspectSlot(who, INVENTORY_SLOT_LIST[index].id)
	end
end

local function LootLink_ScanInventory()
	local bagid;
	local size;
	local slotid;
	local link;
	
	for bagid = 0, 4, 1 do
		size = GetContainerNumSlots(bagid);
		if( size ) then
			for slotid = size, 1, -1 do
				link = GetContainerItemLink(bagid, slotid);
				if( link ) then
					local name = LootLink_ProcessLinks(link, 1);
					if( name and ItemLinks[name] ) then
						local texture, count, locked, quality, readable = GetContainerItemInfo(bagid, slotid);
						if( count > 1 ) then
							ItemLinks[name].x = 1;
						end
						LootLink_Event_ScanInventory(name, count, ItemLinks[name], bagid, slotid);
					end
				end
			end
		end
	end
end

local function LootLink_ScanSellPrices()
	local bagid;
	local size;
	local slotid;
	local link;
	
	LootLink_MoneyToggle();
	for bagid = 0, 4, 1 do
		lBagID = bagid;
		size = GetContainerNumSlots(bagid);
		if( size ) then
			for slotid = size, 1, -1 do
				lSlotID = slotid;
				LLHiddenTooltip:SetBagItem(bagid, slotid);
			end
		end
	end
	
	lBagID = nil;
	lSlotID = nil;
	
	LootLink_MoneyToggle(1);
end

local function LootLink_StartAuctionScan()
	-- Start with the first page
	lCurrentAuctionPage = nil;

	-- Hook the functions that we need for the scan
	if( not lOriginal_CanSendAuctionQuery ) then
		lOriginal_CanSendAuctionQuery = CanSendAuctionQuery;
		CanSendAuctionQuery = LootLink_CanSendAuctionQuery;
	end
	if( not lOriginal_AuctionFrameBrowse_OnEvent ) then
		lOriginal_AuctionFrameBrowse_OnEvent = AuctionFrameBrowse_OnEvent;
		AuctionFrameBrowse_OnEvent = LootLink_AuctionFrameBrowse_OnEvent;
	end
	
	LootLink_Event_StartAuctionScan();
end

local function LootLink_StopAuctionScan()
	LootLink_Event_StopAuctionScan();
	
	-- Unhook the scanning functions
	if( lOriginal_CanSendAuctionQuery ) then
		CanSendAuctionQuery = lOriginal_CanSendAuctionQuery;
		lOriginal_CanSendAuctionQuery = nil;
	end
	if( lOriginal_AuctionFrameBrowse_OnEvent ) then
		AuctionFrameBrowse_OnEvent = lOriginal_AuctionFrameBrowse_OnEvent;
		lOriginal_AuctionFrameBrowse_OnEvent = nil;
	end
end

local function LootLink_AuctionNextQuery()
	if( lCurrentAuctionPage ) then
		local numBatchAuctions, totalAuctions = GetNumAuctionItems("list");
		local maxPages = floor(totalAuctions / NUM_AUCTION_ITEMS_PER_PAGE);
		
		if( lCurrentAuctionPage < maxPages ) then
			lCurrentAuctionPage = lCurrentAuctionPage + 1;
			BrowseNoResultsText:SetText(format(LOOTLINK_AUCTION_PAGE_N, lCurrentAuctionPage + 1, maxPages + 1));
		else
			lScanAuction = nil;
			LootLink_StopAuctionScan();
			if( totalAuctions > 0 ) then
				BrowseNoResultsText:SetText(LOOTLINK_AUCTION_SCAN_DONE);
				LootLink_Event_FinishedAuctionScan();
			end
			return;
		end
	else
		lCurrentAuctionPage = 0;
		BrowseNoResultsText:SetText(LOOTLINK_AUCTION_SCAN_START);
	end
	QueryAuctionItems("", "", "", nil, nil, nil, lCurrentAuctionPage, nil, nil);
	LootLink_Event_AuctionQuery(lCurrentAuctionPage);
end

local function LootLink_ScanAuction()
	local numBatchAuctions, totalAuctions = GetNumAuctionItems("list");
	local auctionid;
	local link;

	if( numBatchAuctions > 0 ) then
		for auctionid = 1, numBatchAuctions do
			link = GetAuctionItemLink("list", auctionid);
			if( link ) then
				local name = LootLink_ProcessLinks(link, 1);
				if( name and ItemLinks[name] ) then
					local name_, texture, count, quality, canUse, level, minBid, minIncrement, buyoutPrice, bidAmount, highBidder, owner = GetAuctionItemInfo("list", auctionid);
					if( count > 1 ) then
						ItemLinks[name].x = 1;
					end
					LootLink_Event_ScanAuction(name, count, ItemLinks[name], lCurrentAuctionPage, auctionid);
				end
			end
		end
	end
end

local function LootLink_ScanBank()
	local index;
	local bagid;
	local size;
	local slotid;
	local link;
	
	for index, bagid in lBankBagIDs do
		size = GetContainerNumSlots(bagid);
		if( size ) then
			for slotid = size, 1, -1 do
				link = GetContainerItemLink(bagid, slotid);
				if( link ) then
					local name = LootLink_ProcessLinks(link, 1);
					if( name and ItemLinks[name] ) then
						local texture, count, locked, quality, readable = GetContainerItemInfo(bagid, slotid);
						if( count > 1 ) then
							ItemLinks[name].x = 1;
						end
						LootLink_Event_ScanBank(name, count, ItemLinks[name], bagid, slotid);
					end
				end
			end
		end
	end
end

local function LootLink_ScanSellPrice(price)
	local link = GetContainerItemLink(lBagID, lSlotID);
	local texture, itemCount, locked, quality, readable = GetContainerItemInfo(lBagID, lSlotID);
	local name;
	
	if( itemCount and itemCount > 1 ) then
		price = price / itemCount;
	end
	
	name = LootLink_NameFromLink(link);
	if( name and ItemLinks[name] ) then
		ItemLinks[name].p = price;
		if( itemCount and itemCount > 1 ) then
			ItemLinks[name].x = 1;
		end
	end
end

local function LootLink_CheckTooltipInfo()
	if( GameTooltip:IsVisible() ) then
		-- If we've already added our information or money is already showing, no need to do it again
		if( GameTooltip.llDone or GameTooltipMoneyFrame:IsVisible() ) then
			return nil;
		end
	
		-- Don't add information to tooltips generated while the mouse is over the minimap
		if( LootLink_MouseIsOver(MinimapCluster) ) then
			return nil;
		end
	
		local field = getglobal("GameTooltipTextLeft1");
		if( field and field:IsShown() ) then
			local name = field:GetText();
			if( name ) then
				if( ItemLinks[name] ) then
					LootLink_AddTooltipInfo(name, GameTooltip);
				end
				-- Whether or not we had information for this item, there's no need to check it again
				return nil;
			end
		end
	end
	
	return 1;
end

local function LootLink_CheckVersionReminder()
	local index;
	local value;
	local version;
	
	version = LootLink_GetDataVersion();
	for index, value in LOOTLINK_DATA_UPGRADE_HELP do
		if( version < value.version ) then
			DEFAULT_CHAT_FRAME:AddMessage(value.text);
		end
	end
end

local function LootLink_UpgradeData()
	local index;
	local item;
	
	for index, item in ItemLinks do
		LootLink_ConvertServerFormat(item);
		if( item.item ) then
			item.i = item.item;
			item.item = nil;
		end
		if( item.color ) then
			item.c = item.color;
			item.color = nil;
		end
		if( item.price ) then
			item.p = item.price;
			item.price = nil;
		end
		if( item.stack ) then
			item.x = item.stack;
			item.stack = nil;
		end
		
		if( item.SearchData ) then
			local data = "";

			if( item.SearchData.type ) then
				data = data.."ty"..item.SearchData.type.."";
			end
			if( item.SearchData.location ) then
				data = data.."lo"..LocationTypes[item.SearchData.location].i.."";
			end
			if( item.SearchData.subtype ) then
				data = data.."su"..item.SearchData.subtype.."";
			end
			if( item.SearchData.binds ) then
				data = data.."bi"..item.SearchData.binds.."";
			end
			if( item.SearchData.level ) then
				data = data.."le"..item.SearchData.level.."";
			end
			if( item.SearchData.armor ) then
				data = data.."ar"..item.SearchData.armor.."";
			end
			if( item.SearchData.minDamage ) then
				data = data.."mi"..item.SearchData.minDamage.."";
			end
			if( item.SearchData.maxDamage ) then
				data = data.."ma"..item.SearchData.maxDamage.."";
			end
			if( item.SearchData.speed ) then
				data = data.."sp"..item.SearchData.speed.."";
			end
			if( item.SearchData.DPS ) then
				data = data.."dp"..item.SearchData.DPS.."";
			end
			if( item.SearchData.unique ) then
				data = data.."un"..item.SearchData.unique.."";
			end
			if( item.SearchData.block ) then
				data = data.."bl"..item.SearchData.block.."";
			end
			if( item.SearchData.slots ) then
				data = data.."sl"..item.SearchData.slots.."";
			end
			if( item.SearchData.skill ) then
				data = data.."sk"..item.SearchData.skill.."";
			end
			
			if( item.SearchData.text ) then
				item.t = item.SearchData.text;
			end
			
			item.d = data;
			item.SearchData = nil;
		end
	end
end

local function LootLink_VariablesLoaded()
	local index;
	local value;

	if( not LootLinkState ) then
		LootLinkState = { };
	elseif ( LootLinkState.options ) then
		for k, v in LootLinkState.options do
			LootLinkState[k] = v;
		end
		LootLinkState.options = nil;
	end
	
	if( not ItemLinks ) then
		LootLink_Reset();
	end
	
	lServer = GetCVar("realmName");
	lServerIndex = LootLink_AddServer(lServer);
	
	LootLink_UpgradeData();
	
	LootLink_InitSizes(lServerIndex);
	
	-- Display and move minimap button --
	if ( not LootLinkState.hideminimap ) then
		LootLinkMinimapButton:Show();
		LootLink_Minimap_Position();
	end
	
	if ( LootLinkState.mouseover ) then	
		this:RegisterEvent("UPDATE_MOUSEOVER_UNIT");
	end
	if ( LootLinkState.sortType ) then
		LootLink_UIDropDownMenu_SetSelectedID(LootLinkFrameDropDown, LootLinkState.sortType, LOOTLINK_DROPDOWN_LIST);
	end
end

--------------------------------------------------------------------------------------------------
-- OnFoo functions
--------------------------------------------------------------------------------------------------
function LootLink_OnLoad()
	local index;
	local value;

	for index, value in ChatMessageTypes do
		this:RegisterEvent(value);
	end
	
	for index = 1, getn(INVENTORY_SLOT_LIST), 1 do
		INVENTORY_SLOT_LIST[index].id = GetInventorySlotInfo(INVENTORY_SLOT_LIST[index].name);
	end
	
	this:RegisterEvent("PLAYER_TARGET_CHANGED");
	this:RegisterEvent("PLAYER_ENTERING_WORLD");
	this:RegisterEvent("PLAYER_LEAVING_WORLD");
	this:RegisterEvent("BANKFRAME_OPENED");
	this:RegisterEvent("VARIABLES_LOADED");
	this:RegisterEvent("AUCTION_HOUSE_SHOW");
	this:RegisterEvent("AUCTION_HOUSE_CLOSE");
	this:RegisterEvent("AUCTION_ITEM_LIST_UPDATE");
	this:RegisterEvent("MERCHANT_SHOW");
	--this:RegisterEvent("PLAYER_LOGOUT");
	this:RegisterEvent("MEMORY_EXHAUSTED");
	this:RegisterEvent("MEMORY_RECOVERED");

	-- Register our slash command
	SLASH_LOOTLINK1 = "/lootlink";
	SLASH_LOOTLINK2 = "/ll";
	SlashCmdList["LOOTLINK"] = function(msg)
		LootLink_SlashCommandHandler(msg);
	end
	
	-- Hook the GameTooltip:SetOwner function
	lOriginal_GameTooltip_SetOwner = GameTooltip.SetOwner;
	GameTooltip.SetOwner = LootLink_GameTooltip_SetOwner;
	
	-- Hook the ContainerFrameItemButton_OnEnter, ContainerFrame_Update and GameTooltip:SetLootItem functions
	lOriginal_ContainerFrameItemButton_OnEnter = ContainerFrameItemButton_OnEnter;
	ContainerFrameItemButton_OnEnter = LootLink_ContainerFrameItemButton_OnEnter;
	lOriginal_ContainerFrame_Update = ContainerFrame_Update;
	ContainerFrame_Update = LootLink_ContainerFrame_Update;
	lOriginal_GameTooltip_SetLootItem = GameTooltip.SetLootItem;
	GameTooltip.SetLootItem = LootLink_GameTooltip_SetLootItem;
	
	-- Hook GameTooltip_OnHide and GameTooltip_ClearMoney
	lOriginal_GameTooltip_OnHide = GameTooltip_OnHide;
	GameTooltip_OnHide = LootLink_GameTooltip_OnHide;
	lOriginal_GameTooltip_ClearMoney = GameTooltip_ClearMoney;
	GameTooltip_ClearMoney = LootLink_GameTooltip_ClearMoney;
	
	-- Hook the shopping tooltip functions
	lOriginal_ShoppingTooltip1_SetMerchantCompareItem = ShoppingTooltip1.SetMerchantCompareItem;
	ShoppingTooltip1.SetMerchantCompareItem = LootLink_ShoppingTooltip1_SetMerchantCompareItem;
	lOriginal_ShoppingTooltip2_SetMerchantCompareItem = ShoppingTooltip2.SetMerchantCompareItem;
	ShoppingTooltip2.SetMerchantCompareItem = LootLink_ShoppingTooltip2_SetMerchantCompareItem;

	lOriginal_ShoppingTooltip1_SetAuctionCompareItem = ShoppingTooltip1.SetAuctionCompareItem;
	ShoppingTooltip1.SetAuctionCompareItem = LootLink_ShoppingTooltip1_SetAuctionCompareItem;
	lOriginal_ShoppingTooltip2_SetAuctionCompareItem = ShoppingTooltip2.SetAuctionCompareItem;
	ShoppingTooltip2.SetAuctionCompareItem = LootLink_ShoppingTooltip2_SetAuctionCompareItem;
	
	-- Hook SetItemRef
	lOriginal_SetItemRef = SetItemRef;
	SetItemRef = LootLink_SetItemRef;
	
	-- Hook our hidden tooltip's OnTooltipAddMoney
	lOriginal_OnTooltipAddMoney = LLHiddenTooltip:GetScript("OnTooltipAddMoney");
	LLHiddenTooltip:SetScript("OnTooltipAddMoney", LootLink_OnTooltipAddMoney);
	
	-- Hook chat edit box
	lOriginal_ChatEdit_OnTabPressed = ChatEdit_OnTabPressed;
	ChatEdit_OnTabPressed = LootLink_ChatEdit_OnTabPressed;
	lOriginal_ChatEdit_OnTextChanged = ChatEdit_OnTextChanged;
	ChatEdit_OnTextChanged = LootLink_ChatEdit_OnTextChanged;
	
	-- Hook CloseWindows
	lOriginal_CloseWindows = CloseWindows;
	CloseWindows = LootLink_CloseWindows;

	if( DEFAULT_CHAT_FRAME ) then
		DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_LOADED);
	end
	UIErrorsFrame:AddMessage(LOOTLINK_LOADED, 1.0, 1.0, 1.0, 1.0, UIERRORS_HOLD_TIME);
end

function LootLink_Minimap_OnEnter()
	GameTooltip:SetOwner(this, "ANCHOR_BOTTOMLEFT");
	GameTooltip:AddLine(LOOTLINK_TITLE);
	if ( not LootLinkState.lockminimap ) then
		GameTooltip:AddLine("|cffaaaaaa "..LOOTLINK_MINIMAP_DRAG);
	end
	if ( LootLinkFrame.process ) then
		GameTooltip:AddLine("|cffaaaaaa "..getglobal("LLP_"..LootLinkFrame.process.."_LABEL"));
		if ( LootLink_Process:IsVisible() ) then
			GameTooltip:AddLine("|cffaaaaaa "..LOOTLINK_MINIMAP_PROCESS_SHOW);
		else
			GameTooltip:AddLine("|cffaaaaaa "..LOOTLINK_MINIMAP_PROCESS_HIDE);
		end
	end
	GameTooltip:Show();
end

function LootLink_Minimap_Position(x,y)
	if ( x or y ) then
		if ( x ) then if ( x < 0 ) then x = x + 360; end LootLinkState.x = x; end
		if ( y ) then LootLinkState.y = y; end
	end
	x, y = LootLinkState.x, LootLinkState.y

	if ( LootLinkOptionsFrame:IsVisible() ) then
		LLO_MinimapPos:SetValue( x or LLO_MINIMAP_X );
		LLO_MinimapOffset:SetValue( y or LLO_MINIMAP_Y );
	end
	LootLinkMinimapButton:SetPoint("TOPLEFT","Minimap","TOPLEFT",53-((80+(y or LLO_MINIMAP_Y))*cos(x or LLO_MINIMAP_X)),((80+(y or LLO_MINIMAP_Y))*sin(x or LLO_MINIMAP_X))-55);
end

function LootLink_Minimap_DragStart()
	if ( LootLinkState.lockminimap ) then return; end
	this:SetScript("OnUpdate", LootLink_Minimap_DragUpdate);
end
function LootLink_Minimap_DragStop()
	LootLinkMinimapButton:UnlockHighlight()
	this:SetScript("OnUpdate", nil);
end
function LootLink_Minimap_DragUpdate()
	-- Thanks to Gello for making this a ton shorter
	LootLinkMinimapButton:LockHighlight();
	local curX, curY = GetCursorPosition();
	local mapX, mapY = Minimap:GetCenter();
	local x, y;
	if ( IsShiftKeyDown() ) then
		y = math.pow( math.pow(curY - mapY * Minimap:GetEffectiveScale(), 2) + math.pow(mapX * Minimap:GetEffectiveScale() - curX, 2), 0.5) - 70;
		y = min( max( y, -30 ), 30 );
	end
	x = math.deg(math.atan2( curY - mapY * Minimap:GetEffectiveScale(), mapX * Minimap:GetEffectiveScale() - curX ));
	LootLink_Minimap_Position(x,y);
end

function LootLink_CanSendAuctionQuery()
	local value = lOriginal_CanSendAuctionQuery();
	if( value ) then
		LootLink_AuctionNextQuery();
		return nil;
	end
	return value;
end

function LootLink_AuctionFrameBrowse_OnEvent()
	-- Intentionally empty; don't allow the auction UI to update while we're scanning
end

function LootLink_GameTooltip_SetOwner(this, owner, anchor)
	lOriginal_GameTooltip_SetOwner(this, owner, anchor);
	lGameToolTipOwner = owner;
end

function LootLink_GameTooltip_ClearMoney()
	lOriginal_GameTooltip_ClearMoney();
	if( not lSuppressInfoAdd ) then
		lCheckTooltip = LootLink_CheckTooltipInfo();
	end
end

function LootLink_GameTooltip_ClearMoney_Temp()
	-- Intentionally empty; don't clear money while we use our hidden tooltip
end

function LootLink_ContainerFrameItemButton_OnEnter()
	LootLink_AutoInfoOff();
	lOriginal_ContainerFrameItemButton_OnEnter();
	if( not InRepairMode() and not MerchantFrame:IsVisible() ) then
		local frameID = this:GetParent():GetID();
		local buttonID = this:GetID();
		local link = GetContainerItemLink(frameID, buttonID);
		local name = LootLink_NameFromLink(link);
		
		if( name and ItemLinks[name] ) then
			local texture, itemCount, locked, quality, readable = GetContainerItemInfo(frameID, buttonID);
			LootLink_AddTooltipInfo(name, GameTooltip, itemCount);
			GameTooltip:Show();
		end
	end
	LootLink_AutoInfoOn();
end

function LootLink_ContainerFrame_Update(frame)
	LootLink_AutoInfoOff();
	lOriginal_ContainerFrame_Update(frame);
	if( not InRepairMode() and not MerchantFrame:IsVisible() and GameTooltip:IsVisible() ) then
		local frameID = frame:GetID();
		local frameName = frame:GetName();
		local iButton;
		for iButton = 1, frame.size do
			local button = getglobal(frameName.."Item"..iButton);
			if( GameTooltip:IsOwned(button) ) then
				local buttonID = button:GetID();
				local link = GetContainerItemLink(frameID, buttonID);
				local name = LootLink_NameFromLink(link);
				
				if( name and ItemLinks[name] ) then
					local texture, itemCount, locked, quality, readable = GetContainerItemInfo(frameID, buttonID);
					LootLink_AddTooltipInfo(name, GameTooltip, itemCount);
					GameTooltip:Show();
				end
			end
		end
	end
	LootLink_AutoInfoOn();
end

function LootLink_GameTooltip_OnHide()
	lOriginal_GameTooltip_OnHide();
	GameTooltip.llDone = nil;
	lCheckTooltip = nil;
end

function LootLink_GameTooltip_SetLootItem(this, slot)
	LootLink_AutoInfoOff();
	lOriginal_GameTooltip_SetLootItem(this, slot);
	
	local link = GetLootSlotLink(slot);
	local name = LootLink_NameFromLink(link);
	if( name and ItemLinks[name] and ItemLinks[name].p ) then
		local texture, item, quantity, quality = GetLootSlotInfo(slot);
		LootLink_AddTooltipInfo(name, GameTooltip, quantity);
	end
	LootLink_AutoInfoOn();
end

function LootLink_ShoppingTooltip1_SetMerchantCompareItem(this, id, num)
	LootLink_MoneyToggle();
	local retval = lOriginal_ShoppingTooltip1_SetMerchantCompareItem(this, id, num);
	LootLink_MoneyToggle(1);
	return retval;
end

function LootLink_ShoppingTooltip2_SetMerchantCompareItem(this, id, num)
	LootLink_MoneyToggle();
	local retval = lOriginal_ShoppingTooltip2_SetMerchantCompareItem(this, id, num);
	LootLink_MoneyToggle(1);
	return retval;
end

function LootLink_ShoppingTooltip1_SetAuctionCompareItem(this, _type, index, num)
	LootLink_MoneyToggle();
	local retval = lOriginal_ShoppingTooltip1_SetAuctionCompareItem(this, _type, index, num);
	LootLink_MoneyToggle(1);
	return retval;
end

function LootLink_ShoppingTooltip2_SetAuctionCompareItem(this, _type, index, num)
	LootLink_MoneyToggle();
	local retval = lOriginal_ShoppingTooltip2_SetAuctionCompareItem(this, _type, index, num);
	LootLink_MoneyToggle(1);
	return retval;
end

function LootLink_SetItemRef(link, text, button)
	lOriginal_SetItemRef(link, text, button);

	-- If this is a tooltip-creating link, add our information to it
	if( strsub(link, 1, 6) ~= "Player" ) then
		ItemRefTooltipMoneyFrame:SetPoint("LEFT", "ItemRefTooltipTextLeft1", "LEFT", 0, 0);
		ItemRefTooltipMoneyFrame:Hide();

		if( ItemRefTooltip:IsVisible() ) then
			local field = getglobal("ItemRefTooltipTextLeft1");
			if( field and field:IsShown() ) then
				local name = field:GetText();
				if( name and ItemLinks[name] ) then
					LootLink_AddTooltipInfo(name, ItemRefTooltip);
				end
			end
		end
	end
end

function LootLink_OnTooltipAddMoney()
	lOriginal_OnTooltipAddMoney();
	if( lBagID and lSlotID ) then
		LootLink_ScanSellPrice(arg1);
	end
end

function LootLink_OnEvent()
	if( event == "PLAYER_TARGET_CHANGED" ) then
		if( UnitIsUnit("target", "player") ) then
			return;
		elseif( UnitIsPlayer("target") ) then
			LootLink_Inspect("target");
		end
	elseif( event == "UPDATE_MOUSEOVER_UNIT" ) then
		LootLink_Inspect("mouseover");
	elseif( event == "PLAYER_ENTERING_WORLD" ) then
		this:RegisterEvent("UNIT_INVENTORY_CHANGED");
		-- Now that we're in the world, we want to watch for inventory changes
		this:RegisterEvent("UNIT_INVENTORY_CHANGED");

		-- Check to see if the user needs to upgrade their database
		LootLink_CheckVersionReminder();
		
		-- Do initial scan of inventory and equipped items
		LootLink_ScanInventory();
		LootLink_Inspect("player");
	elseif( event == "PLAYER_LEAVING_WORLD" ) then
		-- When we leave the world, we don't need to watch for inventory changes,
		-- especially since zoning will cause one UNIT_INVENTORY_CHANGED event for
		-- each item in the player's inventory and that they have equipped
		this:UnregisterEvent("UNIT_INVENTORY_CHANGED");
	elseif( event == "UNIT_INVENTORY_CHANGED" ) then
		if( arg1 == "player" ) then
			LootLink_ScanInventory();
			LootLink_Inspect("player");
		end
	elseif( event == "BANKFRAME_OPENED" ) then
		LootLink_ScanBank();
	elseif( event == "VARIABLES_LOADED" ) then
		LootLink_VariablesLoaded();
	elseif( event == "AUCTION_HOUSE_SHOW" ) then
		if( lScanAuction ) then
			LootLink_StartAuctionScan();
		end
	elseif( event == "AUCTION_HOUSE_CLOSED" ) then
		LootLink_StopAuctionScan();
	elseif( event == "AUCTION_ITEM_LIST_UPDATE" ) then
		LootLink_ScanAuction();
	elseif( event == "MERCHANT_SHOW" ) then
		LootLink_ScanSellPrices();
	--elseif( event == "PLAYER_LOGOUT" ) then
		--LootLink_ClearDelayed();
	elseif( event == "MEMORY_EXHAUSTED" ) then
		LLP_OUTOFMEM = 1;
	elseif( event == "MEMORY_RECOVERED" ) then
		LLP_OUTOFMEM = nil;
	else
		local name = LootLink_ProcessLinks(arg1, event == "CHAT_MSG_LOOT");
		if( name and ItemLinks[name] ) then
			LootLink_Event_ScanChat(name, ItemLinks[name], arg1);
		end
	end
end

function LootLink_RemoveItem(name, link, displayindex, index)
	local value = ItemLinks[name];
	if ( not value ) then
		return;
	end
	local found;
	if ( link and string.sub(link, 1, 5) == "item:" ) then
		link = string.sub( link, 6 );
	end
	if ( displayindex and not link and not index ) then
		link = DisplayIndices[displayindex][2].i;
	end
	if ( not link and not index ) then
		index = 0;
	end
	if ( link or index ) then
		if ( (index and index == 0) or (value.i and value.i == link) ) then
			found = 1;
			if ( value.m and value.m[1] ) then
				for k, v in value.m[1] do
					value[k] = v;
				end
				tremove( value.m, 1 );

				if ( getn(value.m) == 0 ) then
					value.m = nil;
				end
			else
				ItemLinks[name] = nil;
				di = 0;
			end
		else
			if ( value.m ) then
				for i=1, getn(value.m) do
					if ( (index and index == i) or (value.m[i].i == link) ) then
						found = 1;
						tremove( value.m, i );
						break;
					end
				end
			end
		end
		
		if ( found ) then
			LootLink_SortAlternates( name );
		
			if ( DisplayIndices and LootLinkFrame:IsVisible() ) then
				local dName, start;
				local i = 1;
				if ( DisplayIndices[i] ) then
					dName = DisplayIndices[i][1];
				end
				while dName do
					if dName == name then
						if not start then
							start = i;
						end
					elseif start then
						break;
					end
					i = i+1;
					if ( DisplayIndices[i] ) then
						dName = DisplayIndices[i][1];
					else
						dName = nil;
					end
				end
				if ( start and i ) then
					tremove(DisplayIndices, i-1);
					DisplayIndices.onePastEnd = DisplayIndices.onePastEnd - 1;
					if ( displayindex ) then
						LootLink_Update();
						LootLink_SetTitle();
					end
				end
			else
				DisplayIndices = nil;
			end
			return 1;
		end
	end
end

function LootLinkItemButton_OnClick(button)
	local name = this:GetText();
	local index = this.i;
	if( button == "LeftButton" ) then
		local data = DisplayIndices[this:GetID()+FauxScrollFrame_GetOffset(LootLinkListScrollFrame)];
		if( IsShiftKeyDown() and ChatFrameEditBox:IsVisible() ) then
			if ( not LootLink_CheckItemServer(data[2], lServerIndex) ) then
				local dialog = StaticPopup_Show("LOOTLINK_LINK_CONFIRM", LOOTLINK_STATICPOPUP_SERVER, LOOTLINK_STATICPOPUP_LINKITEM);
				if ( dialog ) then
					dialog.data = data;
				end
			elseif ( not GetItemInfo("item:"..data[2].i) ) then
				local dialog = StaticPopup_Show("LOOTLINK_LINK_CONFIRM", LOOTLINK_STATICPOPUP_INVALID, LOOTLINK_STATICPOPUP_LINKITEM);
				if ( dialog ) then
					dialog.data = data;
				end
			else			
				ChatFrameEditBox:Insert(LootLink_GetLink(name, data[3]));
			end
		end
		if( IsControlKeyDown() ) then
			if ( not LootLink_CheckItemServer(data[2], lServerIndex) ) then
				local dialog = StaticPopup_Show("LOOTLINK_DRESSUP_CONFIRM", LOOTLINK_STATICPOPUP_SERVER, LOOTLINK_STATICPOPUP_DRESSUP);
				if ( dialog ) then
					dialog.data = data;
				end
			elseif ( not GetItemInfo("item:"..data[2].i) ) then
				local dialog = StaticPopup_Show("LOOTLINK_DRESSUP_CONFIRM", LOOTLINK_STATICPOPUP_INVALID, LOOTLINK_STATICPOPUP_DRESSUP);
				if ( dialog ) then
					dialog.data = data;
				end
			else			
				DressUpItemLink(LootLink_GetLink(name, data[3]));
			end
		end
	elseif( button == "RightButton" ) then
		if( IsShiftKeyDown() and IsControlKeyDown() ) then
			local data = DisplayIndices[this:GetID()+FauxScrollFrame_GetOffset(LootLinkListScrollFrame)];
			local dialog = StaticPopup_Show("LOOTLINK_DELETEITEM_CONFIRM", data[2].c, name);
			if ( dialog ) then
				dialog.data = data;
			end
			--if ( LootLink_RemoveItem(name, nil, this:GetID()+FauxScrollFrame_GetOffset(LootLinkListScrollFrame)) ) then
			--	LootLink_SortAlternates(name);
			--end
		end
	end
	--@todo Telo: add a method to correct the color of items
end

function LootLink_OnShow()
	PlaySound("igMainMenuOpen");
	LootLink_Update();
end

function LootLink_OnHide()
	PlaySound("igMainMenuClose");
end

function LootLink_ClearDelayed()
	if ( LootLinkFrame.schedule ) then
		for k, v in LootLinkFrame.schedule do
			local value, index = LootLink_GetValue( v[1], nil, v[2] );
			if ( v and not v.t ) then
				if ( LootLink_RemoveItem(v[1], v[2]) ) then
					LootLink_SortAlternates(v[1]);
				end
			end
		end
	end
	LootLinkFrame.schedule = nil;
end

function LootLink_ScheduleItem(name,link,time)
	if ( not LootLinkFrame.schedule ) then
		LootLinkFrame.schedule = {};
	end
	for k, v in LootLinkFrame.schedule do
		if ( v[1] == name and v[2] == link ) then
			return;
		end
	end
	LLHiddenTooltip:SetHyperlink("item:"..link);
	tinsert( LootLinkFrame.schedule, {name,link,(time or LOOTLINK_SCHEDULE_DELAY)} );
end
	
function LootLink_OnUpdate(elapsed)
	if( lCheckTooltip ) then
		lCheckTooltip = LootLink_CheckTooltipInfo();
	end
	if ( LootLinkFrame.process ) then
		LootLinkProcess_Run();
	end
	local list = LootLinkFrame.schedule;
	if ( list ) then
		for k, v in list do
			v[3] = v[3] - elapsed;
			if ( v[3] < 0 ) then
				local value, index = LootLink_GetValue( v[1], nil, v[2] );
				if ( not value ) then
					-- Item was remove from ItemLinks table, stop trying to check for it.
					tremove( list, k );
					return;	
				elseif ( LootLink_BuildSearchData( v[1], value, index ) ) then
					tremove( list, k );
					return;
				else
					-- Still don't have information from server.
					if ( v[4] and v[4] == 60 ) then
						-- we've tried long enough, fail this item;
						if ( LootLink_RemoveItem(v[1], v[2]) ) then
							LootLink_SortAlternates(name);
						end
						tremove( list, k );
						return;
					end
					v[4] = (v[4] or 0) + 1;
					v[3] = LOOTLINK_SCHEDULE_DELAY;
				end
			end
		end
		if ( getn(list) == 0 ) then
			list = nil;
		end
	end
end

function LootLinkItemButton_OnEnter()
	local link = LootLink_GetHyperlink(this:GetText(), this.i);
	if( link ) then
		LootLinkFrame.TooltipButton = this:GetID();
		LootLinkTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT");
		if ( LootLink_SetHyperlink(LootLinkTooltip, this:GetText(), link, this.i) ) then
			LootLink_Update();
		end
		LootLink_AddTooltipTexture(link);
		LootLink_AddTooltipInfo(this:GetText(), nil, nil, this.i);
	end
end

function LootLinkItemButton_OnLeave()
	if( LootLinkFrame.TooltipButton ) then
		LootLinkFrame.TooltipButton = nil;
		HideUIPanel(LootLinkTooltip);
	end
	LootLink_AddTooltipTexture();
end

function LootLinkFrameDropDown_OnLoad()
	UIDropDownMenu_Initialize(LootLinkFrameDropDown, LootLinkFrameDropDown_Initialize);
	LootLink_UIDropDownMenu_SetSelectedID(LootLinkFrameDropDown, 1, LOOTLINK_DROPDOWN_LIST);
	UIDropDownMenu_SetWidth(80);
	UIDropDownMenu_SetButtonWidth(24);
	UIDropDownMenu_JustifyText("LEFT", LootLinkFrameDropDown)
end

function LootLinkFrameDropDownButton_OnClick()
	local oldID = UIDropDownMenu_GetSelectedID(LootLinkFrameDropDown);
	UIDropDownMenu_SetSelectedID(LootLinkFrameDropDown, this:GetID());
	if( oldID ~= this:GetID() ) then
		LootLink_Refresh();
	end
	LootLinkState.sortType = this:GetID();
end

function LLS_RarityDropDown_OnLoad()
	UIDropDownMenu_SetWidth(90);
	UIDropDownMenu_SetButtonWidth(24);
	UIDropDownMenu_JustifyText("LEFT", LLS_RarityDropDown);
end

function LLS_RarityDropDown_OnClick()
	UIDropDownMenu_SetSelectedID(LLS_RarityDropDown, this:GetID());
end

function LLS_BindsDropDown_OnLoad()
	UIDropDownMenu_SetWidth(90);
	UIDropDownMenu_SetButtonWidth(24);
	UIDropDownMenu_JustifyText("LEFT", LLS_BindsDropDown);
end

function LLS_BindsDropDown_OnClick()
	UIDropDownMenu_SetSelectedID(LLS_BindsDropDown, this:GetID());
end

function LLS_LocationDropDown_OnLoad()
	UIDropDownMenu_SetWidth(90);
	UIDropDownMenu_SetButtonWidth(24);
	UIDropDownMenu_JustifyText("LEFT", LLS_LocationDropDown);
end

function LLS_LocationDropDown_OnClick()
	UIDropDownMenu_SetSelectedID(LLS_LocationDropDown, this:GetID());
end

function LLS_TypeDropDown_OnLoad()
	UIDropDownMenu_SetWidth(90);
	UIDropDownMenu_SetButtonWidth(24);
	UIDropDownMenu_JustifyText("LEFT", LLS_TypeDropDown);
end

function LLS_TypeDropDown_OnClick()
	local oldID = UIDropDownMenu_GetSelectedID(LLS_TypeDropDown);
	UIDropDownMenu_SetSelectedID(LLS_TypeDropDown, this:GetID());
	if( oldID ~= this:GetID() ) then
		LootLink_SetupTypeUI(this:GetID(), 1);
	end
end

function LLS_SubtypeDropDown_OnLoad()
	UIDropDownMenu_SetWidth(90);
	UIDropDownMenu_SetButtonWidth(24);
	UIDropDownMenu_JustifyText("LEFT", LLS_SubtypeDropDown);
end

function LLS_SubtypeDropDown_OnClick()
	UIDropDownMenu_SetSelectedID(LLS_SubtypeDropDown, this:GetID());
end

function LLO_RarityDropDown_OnLoad()
	UIDropDownMenu_SetWidth(90);
	UIDropDownMenu_SetButtonWidth(24);
	UIDropDownMenu_JustifyText("LEFT", LLO_RarityDropDown);
end

function LLO_RarityDropDown_OnClick()
	UIDropDownMenu_SetSelectedID(LLO_RarityDropDown, this:GetID());
end

--------------------------------------------------------------------------------------------------
-- Callback functions
--------------------------------------------------------------------------------------------------
function ToggleLootLink()
	if( LootLinkFrame:IsVisible() ) then
		HideUIPanel(LootLinkFrame);
	else
		ShowUIPanel(LootLinkFrame);
	end
end

function LootLink_SlashCommandHandler(msg)
	local reset;
	local makehome;
	local light;
	local aborted;

	if( not lDisableVersionReminder ) then
		LootLink_CheckVersionReminder();
	end
	if( not msg or msg == "" ) then
		ToggleLootLink();
	else
		local command = string.lower(msg);
		if( command == LOOTLINK_HELP ) then
			local index = 0;
			local value = getglobal("LOOTLINK_HELP_TEXT"..index);
			while( value ) do
				DEFAULT_CHAT_FRAME:AddMessage(value);
				index = index + 1;
				value = getglobal("LOOTLINK_HELP_TEXT"..index);
			end
		elseif( command == LOOTLINK_STATUS ) then
			LootLink_Status();
		elseif( command == LOOTLINK_AUCTION or command == LOOTLINK_SCAN ) then
			if( AuctionFrame and AuctionFrame:IsVisible() ) then
				local iButton;
				local button;

				-- Hide the UI from any current results, show the no results text so we can use it
				BrowseNoResultsText:Show();
				for iButton = 1, NUM_BROWSE_TO_DISPLAY do
					button = getglobal("BrowseButton"..iButton);
					button:Hide();
				end
				BrowsePrevPageButton:Hide();
				BrowseNextPageButton:Hide();
				BrowseSearchCountText:Hide();

				LootLink_StartAuctionScan();
			else
				lScanAuction = 1;
				LootLink_Status();
			end
		elseif( command == LOOTLINK_SHOWINFO ) then
			LootLinkState.HideInfo = nil;
			LootLink_Status();
		elseif( command == LOOTLINK_HIDEINFO ) then
			LootLinkState.HideInfo = 1;
			LootLink_Status();
		elseif( command == LOOTLINK_FULLMODE ) then
			LootLinkState.LightMode = nil;
			LootLink_Status();
		else
			local iStart;
			local iEnd;
			local args;
			
			iStart, iEnd, command, args = string.find(command, "^(%w+)%s*(.*)$");
	
			if( command == LOOTLINK_RESET ) then
				if( lResetNeedsConfirm ) then
					if( args == LOOTLINK_CONFIRM ) then
						LootLink_Reset();
						lResetNeedsConfirm = nil;
						lDisableVersionReminder = nil
						DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_RESET_DONE);
					end
				else
					reset = 1;
					lResetNeedsConfirm = 1;
					lDisableVersionReminder = 1;
				end
			elseif( LootLink_GetDataVersion() < 110 and command == LOOTLINK_MAKEHOME ) then
				if( lMakeHomeNeedsConfirm ) then
					if( args == LOOTLINK_CONFIRM ) then
						LootLink_MakeHome();
						lMakeHomeNeedsConfirm = nil;
						lDisableVersionReminder = nil;
						DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_MAKEHOME_DONE);
					end
				else
					makehome = 1;
					lMakeHomeNeedsConfirm = 1;
					lDisableVersionReminder = 1;
				end
			elseif( command == LOOTLINK_LIGHTMODE ) then
				if( lLightModeNeedsConfirm ) then
					if( args == LOOTLINK_CONFIRM ) then
						LootLink_LightMode();
						lLightModeNeedsConfirm = nil;
						DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_LIGHTMODE_DONE);
					end
				else
					light = 1;
					lLightModeNeedsConfirm = 1;
				end
			end
		end
	end
	
	if( not reset ) then
		if( lResetNeedsConfirm ) then
			aborted = 1;
			lResetNeedsConfirm = nil;
			DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_RESET_ABORTED);
		end
	end
	
	if( not makehome ) then
		if( lMakeHomeNeedsConfirm ) then
			aborted = 1;
			lMakeHomeNeedsConfirm = nil;
			DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_MAKEHOME_ABORTED);
		end
	end
	
	if( not light ) then
		if( lLightModeNeedsConfirm ) then
			lLightModeNeedsConfirm = nil;
			DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_LIGHTMODE_ABORTED);
		end
	end
	
	if( reset ) then
		DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_RESET_NEEDS_CONFIRM);
	end
	
	if( makehome ) then
		DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_MAKEHOME_NEEDS_CONFIRM);
	end
	
	if( light ) then
		DEFAULT_CHAT_FRAME:AddMessage(LOOTLINK_LIGHTMODE_NEEDS_CONFIRM);
	end
	
	if( aborted and not reset and not makehome ) then
		lDisableVersionReminder = nil
	end
end

function LootLink_Update()
	local iItem;
	
	if( not DisplayIndices ) then
		LootLink_BuildDisplayIndices();
	end
	FauxScrollFrame_Update(LootLinkListScrollFrame, DisplayIndices.onePastEnd - 1, LOOTLINK_ITEMS_SHOWN, LOOTLINK_ITEM_HEIGHT, nil, nil, nil, LootLinkHighlightFrame, 293, 316);
	LootLinkHighlightFrame:Hide();
	for iItem = 1, LOOTLINK_ITEMS_SHOWN, 1 do
		local itemIndex = iItem + FauxScrollFrame_GetOffset(LootLinkListScrollFrame);
		local lootLinkItem = getglobal("LootLinkItem"..iItem);
		
		if( itemIndex < DisplayIndices.onePastEnd ) then
			local value = DisplayIndices[itemIndex][2]; --LootLink_GetValue(DisplayIndices[itemIndex]);
			if ( not value ) then
				LootLink_Refresh();
				return;
			end
			if ( value ) then		
				local color = { };
				local name = DisplayIndices[itemIndex][1]; --LootLink_GetName(DisplayIndices[itemIndex]);
				local index = DisplayIndices[itemIndex][3];
				local extra = getglobal(lootLinkItem:GetName().."Type");
				lootLinkItem.i = nil;
				local text = getglobal(lootLinkItem:GetName().."Text");
				local indexed = getglobal(lootLinkItem:GetName().."Indexed");
				text:SetPoint( "LEFT",  20, 0 );
				text:SetWidth(275);
				getglobal(lootLinkItem:GetName().."Indexed"):Hide();
				if ( index and index > 0 ) then
					lootLinkItem.i = index;
					if ( DisplayIndices[itemIndex-1] and DisplayIndices[itemIndex-1][1] == name ) then
						local _,_, enchant, bonus = string.find(value.i, "%d+:(%d+):(%d+):%d+");
						bonus = tonumber(bonus);
						enchant = tonumber(enchant);
						if ( bonus > 0 or enchant > 0 ) then
							text:SetPoint( "LEFT",  30, 0 );
							text:SetWidth(245);
							indexed:Show();
						end
						if ( enchant > 0 ) then
							indexed:SetVertexColor(0,1,0);
						else
							indexed:SetVertexColor(1,1,1);
						end
					end
				end
				local link = "item:"..value.i;
				local realname
				if ( link ) then
					realname = GetItemInfo( link );
				end
				local onserver = LootLink_CheckItemServer(value, lServerIndex);
				extra:SetVertexColor(1.0,1.0,1.0)
				SetDesaturation(extra,0);
				extra:SetAlpha(1.0);
				if ( not onserver ) then
					extra:SetTexture("Interface\\GossipFrame\\TaxiGossipIcon");
					extra:Show();
				elseif ( not realname ) then
					extra:SetTexture("Interface\\GossipFrame\\ActiveQuestIcon");
					extra:SetAlpha(0.5);
					SetDesaturation(extra,1);
					extra:Show();
				elseif ( not LootLink_ValidateItem(name, link) ) then
					extra:SetTexture("Interface\\GossipFrame\\AvailableQuestIcon");
					extra:SetVertexColor(1.0,0,0)
					extra:Show();
				else
					extra:Hide();
				end

				lootLinkItem:SetText(name);
				if( value.c ) then
					color.r, color.g, color.b = LootLink_GetRGBFromHexColor(value.c);
					lootLinkItem:SetTextColor(color.r, color.g, color.b);
					lootLinkItem.r = color.r;
					lootLinkItem.g = color.g;
					lootLinkItem.b = color.b;
				else
					lootLinkItem.r = 0;
					lootLinkItem.g = 0;
					lootLinkItem.b = 0;
				end
				if ( LootLinkFrame.qs and LootLinkFrame.qs == itemIndex ) then
					if ( color ) then
						LootLinkHighlight:SetVertexColor(color.r, color.g, color.b);
					else
						LootLinkHighlight:SetVertexColor(0.5, 0.5, 0.5);
					end
					lootLinkItem:SetTextColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b);
					LootLinkHighlightFrame:SetPoint("TOPLEFT", "LootLinkItem"..iItem, "TOPLEFT", 0, 0);
					LootLinkHighlightFrame:Show();
				end
				lootLinkItem:Show();

				if( LootLinkFrame.TooltipButton and LootLinkFrame.TooltipButton == iItem ) then
					if( value.i ) then
						local link = LootLink_GetHyperlink(name, index);				
						LootLinkTooltip:SetOwner(lootLinkItem, "ANCHOR_BOTTOMRIGHT");
						LootLink_SetHyperlink(LootLinkTooltip, name, link, index);
						LootLink_AddTooltipInfo(name);
						LootLink_AddTooltipTexture(link);
						LootLinkTooltip:Show();
					else
						LootLinkItemButton_OnLeave();
					end
				end
			end
		else
			lootLinkItem:Hide();
		end
	end
end

function LootLink_Search()
	LootLinkSearchFrame:Show();
end

function LootLink_Refresh()
	FauxScrollFrame_SetOffset(LootLinkListScrollFrame, 0);
	getglobal("LootLinkListScrollFrameScrollBar"):SetValue(0);
	LootLink_BuildDisplayIndices();
	LootLink_Update();
end

function LootLink_DoSearch()
	LootLink_Refresh();
end

function LootLinkSearch_LoadValues()
	local sp = LootLinkFrame.SearchParams;
	local field;
	
	if( LootLinkState.LightMode ) then
		getglobal("LLS_TextDisabled"):Show();
		getglobal("LLS_TextEditBox"):Hide();
		getglobal("LLS_NameEditBox"):SetFocus();
	else
		getglobal("LLS_TextDisabled"):Hide();
		field = getglobal("LLS_TextEditBox");
		field:Show();
		field:SetFocus();
		if( sp and sp.text ) then
			field:SetText(sp.text);
		else
			field:SetText("");
		end
	end
	
	field = getglobal("LLS_NameEditBox");
	if( sp and sp.name ) then
		field:SetText(sp.name);
	else
		field:SetText("");
	end
	
	UIDropDownMenu_Initialize(LLS_RarityDropDown, LLS_RarityDropDown_Initialize);
	if( sp and sp.rarity ) then
		LootLink_UIDropDownMenu_SetSelectedID(LLS_RarityDropDown, sp.rarity, LLS_RARITY_LIST);
	else
		LootLink_UIDropDownMenu_SetSelectedID(LLS_RarityDropDown, 1, LLS_RARITY_LIST);
	end
	
	if( sp and sp.server ) then
		getglobal("LLS_ServerCheckButton"):SetChecked(1);
	else
		getglobal("LLS_ServerCheckButton"):SetChecked(0);
	end
	
	UIDropDownMenu_Initialize(LLS_BindsDropDown, LLS_BindsDropDown_Initialize);
	if( sp and sp.binds ) then
		LootLink_UIDropDownMenu_SetSelectedID(LLS_BindsDropDown, sp.binds, LLS_BINDS_LIST);
	else
		LootLink_UIDropDownMenu_SetSelectedID(LLS_BindsDropDown, 1, LLS_BINDS_LIST);
	end
	
	if( sp and sp.unique ) then
		getglobal("LLS_UniqueCheckButton"):SetChecked(1);
	else
		getglobal("LLS_UniqueCheckButton"):SetChecked(0);
	end
	
	UIDropDownMenu_Initialize(LLS_LocationDropDown, LLS_LocationDropDown_Initialize);
	if( sp and sp.location ) then
		LootLink_UIDropDownMenu_SetSelectedID(LLS_LocationDropDown, sp.location, LLS_LOCATION_LIST);
	else
		LootLink_UIDropDownMenu_SetSelectedID(LLS_LocationDropDown, 1, LLS_LOCATION_LIST);
	end
	
	if( LootLinkState and LootLinkState.spoof ) then
		LLS_SpoofCheckButton:Hide();
	else
		LLS_SpoofCheckButton:Show();
		if( sp and sp.spoof ) then
			getglobal("LLS_SpoofCheckButton"):SetChecked(1);
		else
			getglobal("LLS_SpoofCheckButton"):SetChecked(0);
		end
	end
	
	if( sp and sp.usable ) then
		getglobal("LLS_UsableCheckButton"):SetChecked(1);
	else
		getglobal("LLS_UsableCheckButton"):SetChecked(0);
	end
	
	field = getglobal("LLS_MinimumLevelEditBox");
	if( sp and sp.minLevel ) then
		field:SetText(sp.minLevel);
	else
		field:SetText("");
	end

	field = getglobal("LLS_MaximumLevelEditBox");
	if( sp and sp.maxLevel ) then
		field:SetText(sp.maxLevel);
	else
		field:SetText("");
	end
	
	UIDropDownMenu_Initialize(LLS_TypeDropDown, LLS_TypeDropDown_Initialize);
	if( sp and sp.type ) then
		LootLink_UIDropDownMenu_SetSelectedID(LLS_TypeDropDown, sp.type, LLS_TYPE_LIST);
	else
		LootLink_UIDropDownMenu_SetSelectedID(LLS_TypeDropDown, 1, LLS_TYPE_LIST);
	end
	
	if( sp and sp.type ) then
		LootLink_SetupTypeUI(sp.type, sp.subtype);
	else
		LootLink_SetupTypeUI(1, 1);
	end

	field = getglobal("LLS_MinimumDamageEditBox");
	if( sp and sp.minMinDamage ) then
		field:SetText(sp.minMinDamage);
	else
		field:SetText("");
	end
	
	field = getglobal("LLS_MaximumDamageEditBox");
	if( sp and sp.minMaxDamage ) then
		field:SetText(sp.minMaxDamage);
	else
		field:SetText("");
	end
	
	field = getglobal("LLS_MaximumSpeedEditBox");
	if( sp and sp.maxSpeed ) then
		field:SetText(sp.maxSpeed);
	else
		field:SetText("");
	end
	
	field = getglobal("LLS_MinimumDPSEditBox");
	if( sp and sp.minDPS ) then
		field:SetText(sp.minDPS);
	else
		field:SetText("");
	end
	
	field = getglobal("LLS_MinimumArmorEditBox");
	if( sp and sp.minArmor ) then
		field:SetText(sp.minArmor);
	else
		field:SetText("");
	end
	
	field = getglobal("LLS_MinimumBlockEditBox");
	if( sp and sp.minBlock ) then
		field:SetText(sp.minBlock);
	else
		field:SetText("");
	end
	
	field = getglobal("LLS_MinimumSlotsEditBox");
	if( sp and sp.minSlots ) then
		field:SetText(sp.minSlots);
	else
		field:SetText("");
	end
	
	field = getglobal("LLS_MinimumSkillEditBox");
	if( sp and sp.minSkill ) then
		field:SetText(sp.minSkill);
	else
		field:SetText("");
	end
	
	field = getglobal("LLS_MaximumSkillEditBox");
	if( sp and sp.maxSkill ) then
		field:SetText(sp.maxSkill);
	else
		field:SetText("");
	end
	
	--[[if( sp and sp.set ) then
		getglobal("LLS_SetCheckButton"):SetChecked(1);
	else
		getglobal("LLS_SetCheckButton"):SetChecked(0);
	end]]
end

function LootLinkSearch_SaveValues()
	local sp;
	local interesting;
	local field;
	local value;
	
	LootLinkFrame.SearchParams = { };
	sp = LootLinkFrame.SearchParams;
	
	field = getglobal("LLS_TextEditBox");
	value = field:GetText();
	if( value and value ~= "" ) then
		sp.text = value;
		interesting = 1;
	end
	
	field = getglobal("LLS_NameEditBox");
	value = field:GetText();
	if( value and value ~= "" ) then
		sp.name = value;
		interesting = 1;
	end
	
	value = UIDropDownMenu_GetSelectedID(LLS_RarityDropDown);
	if( value and value ~= 1 ) then
		sp.rarity = value;
		interesting = 1;
	end
	
	field = getglobal("LLS_ServerCheckButton");
	value = field:GetChecked();
	if( value ) then
		sp.server = value;
		interesting = 1;
	end
	
	value = UIDropDownMenu_GetSelectedID(LLS_BindsDropDown);
	if( value and value ~= 1 ) then
		sp.binds = value;
		interesting = 1;
	end
	
	field = getglobal("LLS_UniqueCheckButton");
	value = field:GetChecked();
	if( value ) then
		sp.unique = value;
		interesting = 1;
	end
	
	value = UIDropDownMenu_GetSelectedID(LLS_LocationDropDown);
	if( value and value ~= 1 ) then
		sp.location = value;
		interesting = 1;
	end
	
	field = getglobal("LLS_UsableCheckButton");
	value = field:GetChecked();
	if( value ) then
		sp.usable = value;
		interesting = 1;
	end
	
	field = getglobal("LLS_MinimumLevelEditBox");
	value = field:GetText();
	if( value and LootLink_CheckNumeric(value) ) then
		sp.minLevel = tonumber(value);
		interesting = 1;
	end
	
	field = getglobal("LLS_MaximumLevelEditBox");
	value = field:GetText();
	if( value and LootLink_CheckNumeric(value) ) then
		sp.maxLevel = tonumber(value);
		interesting = 1;
	end

	value = UIDropDownMenu_GetSelectedID(LLS_TypeDropDown);
	if( value and value ~= 1 ) then
		sp.type = value;
		interesting = 1;
	end
	
	field = getglobal("LLS_SpoofCheckButton");
	value = field:GetChecked();
	if( value ) then
		sp.spoof = value;
		interesting = 1;
	end
	
	value = UIDropDownMenu_GetSelectedID(LLS_SubtypeDropDown);
	if( value and value ~= 1 ) then
		sp.subtype = value;
		if( sp.type and sp.type ~= 1 ) then
			interesting = 1;
		end
	end

	field = getglobal("LLS_MinimumDamageEditBox");
	value = field:GetText();
	if( value and LootLink_CheckNumeric(value) ) then
		sp.minMinDamage = tonumber(value);
		interesting = 1;
	end
	
	field = getglobal("LLS_MaximumDamageEditBox");
	value = field:GetText();
	if( value and LootLink_CheckNumeric(value) ) then
		sp.minMaxDamage = tonumber(value);
		interesting = 1;
	end
	
	field = getglobal("LLS_MaximumSpeedEditBox");
	value = field:GetText();
	if( value and LootLink_CheckNumeric(value) ) then
		sp.maxSpeed = tonumber(value);
		interesting = 1;
	end
	
	field = getglobal("LLS_MinimumDPSEditBox");
	value = field:GetText();
	if( value and LootLink_CheckNumeric(value) ) then
		sp.minDPS = tonumber(value);
		interesting = 1;
	end
	
	field = getglobal("LLS_MinimumArmorEditBox");
	value = field:GetText();
	if( value and LootLink_CheckNumeric(value) ) then
		sp.minArmor = tonumber(value);
		interesting = 1;
	end
	
	field = getglobal("LLS_MinimumBlockEditBox");
	value = field:GetText();
	if( value and LootLink_CheckNumeric(value) ) then
		sp.minBlock = tonumber(value);
		interesting = 1;
	end

	field = getglobal("LLS_MinimumSlotsEditBox");
	value = field:GetText();
	if( value and LootLink_CheckNumeric(value) ) then
		sp.minSlots = tonumber(value);
		interesting = 1;
	end

	field = getglobal("LLS_MinimumSkillEditBox");
	value = field:GetText();
	if( value and LootLink_CheckNumeric(value) ) then
		sp.minSkill = tonumber(value);
		interesting = 1;
	end

	field = getglobal("LLS_MaximumSkillEditBox");
	value = field:GetText();
	if( value and LootLink_CheckNumeric(value) ) then
		sp.maxSkill = tonumber(value);
		interesting = 1;
	end
	
	--[[value = getglobal("LLS_SetCheckButton"):GetChecked();
	if ( value ) then
		sp.set = value;
		interesting = 1;
	end]]

	-- Only save search params if we had interesting data on the page	
	if( not interesting ) then
		LootLinkFrame.SearchParams = nil;
	else
		if( IsControlKeyDown() ) then
			sp.plain = nil;
		else
			sp.plain = 1;
		end
	end
end

function LootLinkSearchFrame_SaveSearchParams()
	LootLinkSearchFrame.OldSearchParams = LootLinkFrame.SearchParams;
end

function LootLinkSearchFrame_RestoreSearchParams()
	LootLinkFrame.SearchParams = LootLinkSearchFrame.OldSearchParams;
end

function LootLinkSearchFrame_ChangeFocus()
	local frames = {"LLS_TextEditBox",
		"LLS_NameEditBox",
		"LLS_MinimumLevelEditBox",
		"LLS_MaximumLevelEditBox",
		"LLS_MinimumSlotsEditBox",
		"LLS_MinimumDamageEditBox",
		"LLS_MaximumDamageEditBox",
		"LLS_MaximumSpeedEditBox",
		"LLS_MinimumDPSEditBox",
		"LLS_MinimumArmorEditBox",
		"LLS_MinimumBlockEditBox",
		"LLS_MinimumSkillEditBox",
		"LLS_MaximumSkillEditBox",
	}
	
	local name = this:GetName();
	for i=1, 13 do
		if ( frames[i] == name ) then
			if ( IsShiftKeyDown() ) then
				i = i-1;
			else
				i = i+1;
			end
			while frames[i] do
				local nextframe = getglobal(frames[i]);
				if ( nextframe:IsVisible() ) then
					nextframe:SetFocus();
					return;
				end
				if ( IsShiftKeyDown() ) then
					i = i-1;
				else
					i = i+1;
				end
			end
			return;
		end
	end
end

function LootLinkSearchFrame_Cancel()
	PlaySound("gsTitleOptionExit");
	LootLinkSearchFrame:Hide();
	LootLinkSearchFrame_RestoreSearchParams();
end

function LootLinkSearchFrame_Okay()
	PlaySound("gsTitleOptionOK");
	LootLinkSearchFrame:Hide();
	LootLinkSearch_SaveValues();
	LootLink_DoSearch();
end

function LootLinkQuickSearch_Search(Next)
	if ( not Next ) then
		Next = 0;
	end
	local text = LootLinkFrameQuickSearch:GetText();
	local length = string.len( text );
	local last = LootLinkFrameQuickSearch.last;
	local function DoScroll(i)
		local offset = FauxScrollFrame_GetOffset(LootLinkListScrollFrame);
		if ( i < offset or i > offset + LOOTLINK_ITEMS_SHOWN ) then
			if ( i > DisplayIndices.onePastEnd - LOOTLINK_ITEMS_SHOWN ) then
				i = DisplayIndices.onePastEnd - LOOTLINK_ITEMS_SHOWN;
			end
			i = i-1;
			local val = (i * 16) - 0.5
			LootLinkListScrollFrameScrollBar:SetValue(val);
			LootLinkListScrollFrame.offset = i;
		end
	end
	local function DoSearch(index, offset)
		if ( not offset ) then
			offset = getn(DisplayIndices);
		end
		for i=index+Next, offset do
			local name = LootLink_GetName(DisplayIndices[i]);
			if ( name and string.find( string.lower(name), string.lower(text), 1, 1 ) ) then
				LootLinkFrame.qs = i;
				DoScroll(i);
				return 1;
			end
		end
	end
	if ( text and text ~= "" ) then
		if ( not last or length < last ) then
			if ( not DoSearch(LootLinkFrame.qs) and not DoSearch(1, LootLinkFrame.qs+1) ) then
				LootLinkFrameQuickSearch.last = length;
				LootLinkFrameQuickSearch:SetTextColor(1,0,0);
				LootLinkFrame.qs = 0;
			else
				LootLinkFrameQuickSearch:SetTextColor(1,1,1);
				LootLinkFrameQuickSearch.last = nil;
			end
		end
	else
		LootLinkFrame.qs = 0;
		LootLinkFrameQuickSearch:SetTextColor(1,1,1);
	end
	LootLink_Update();
end

function LootLinkQuickSearch_FullSearch()
	local name = this:GetText();
	if ( name ) then
		if ( name == "" ) then
			name = nil;
		end
		if ( name and not LootLinkFrame.SearchParams ) then
			LootLinkFrame.SearchParams = { };
		end
		if ( LootLinkFrame.SearchParams ) then
			LootLinkFrame.SearchParams.name = name;
		end
	end
	LootLink_DoSearch();
	LootLinkFrameQuickSearch:SetText(name or "");
end

function LootLinkQuickSearch_Clear()
	LootLinkFrameQuickSearch:SetText("");
	LootLinkFrameQuickSearch.last = nil;
end

LootLinkOptionsList = {
	hideminimap	= { frame = "LLO_HideMinimap", flip = 1 },
	lockminimap	= { frame = "LLO_LockMinimap" },
	hideicon	= { frame = "LLO_IconCheckButton", flip = 1 },
	HideInfo	= { frame = "LLO_ExtraInfoCheckButton", flip = 1 },
	mouseover	= { frame = "LLO_MouseoverCheckButton" },
	typelinks	= { frame = "LLO_TypeLinksCheckButton" },
	autotypelinks	= { frame = "LLO_AutoTypeLinksCheckButton", dependancy = "typelinks" },
	
	server		= { frame = "LLO_ServerCheckButton", refresh = 1 },
	spoof		= { frame = "LLO_SpoofCheckButton", refresh = 1 },
	nosame		= { frame = "LLO_SameNameCheckButton", flip = 1, refresh = 1 },
	novariants	= { frame = "LLO_VariantCheckButton", flip = 1, dependancy = "nosame", refresh = 1 },
	enchants	= { frame = "LLO_EnchantsCheckButton", flip = 1, dependancy = "nosame", refresh = 1 },

	x		= { frame = "LLO_MinimapPos", slider = LLO_MINIMAP_X},
	y		= { frame = "LLO_MinimapOffset", slider = LLO_MINIMAP_Y },

	rarityfilter	= { frame = "LLO_RarityDropDown", dropdown = {LLO_RarityDropDown_Initialize, 2, LLO_RARITY_LIST}, refresh = 1 },
	};
	
function LootLinkOptions_LoadValues()
	local op = LootLinkState;
	
	for k, v in LootLinkOptionsList do
		local frame = getglobal( v.frame );
		if ( frame ) then
			if ( v.dropdown ) then
				UIDropDownMenu_Initialize(frame, v.dropdown[1]);
				LootLink_UIDropDownMenu_SetSelectedID(frame, 1, v.dropdown[3]);
			end
			local value = op[k];
			if value then
				if ( v.slider ) then
					frame:SetValue( value );
					LootLinkOptionsFrame[k] = value;
				elseif ( v.dropdown ) then
					LootLink_UIDropDownMenu_SetSelectedID(frame, value+v.dropdown[2], v.dropdown[3]);
				end
			elseif ( v.slider ) then
				frame:SetValue( v.slider );
				LootLinkOptionsFrame[k] = nil;
			end
			if ( not v.slider and not v.dropdown ) then
				if ( v.flip ) then
					frame:SetChecked(1-(value or 0))
				else
					frame:SetChecked(value)
				end
			end
			if ( v.dependancy ) then
				if ( getglobal(LootLinkOptionsList[v.dependancy].frame):GetChecked() ) then
					frame:Enable();
					getglobal(frame:GetName().."Text"):SetTextColor(1, 0.82, 0);
				else
					frame:Disable();
					getglobal(frame:GetName().."Text"):SetTextColor(0.3, 0.3, 0.3);
				end
			end
		else
			ChatFrame1:AddMessage(k.." no frame?");
		end
	end
	
	LootLinkOptionsFrame.needsave = 1;
end

function LootLinkOptions_SaveValues()
	local op = LootLinkState;
	local refresh;
	
	for k, v in LootLinkOptionsList do
		local frame = getglobal( v.frame );
		if ( frame ) then
			local value = op[k];
			if ( v.dropdown ) then
				value = UIDropDownMenu_GetSelectedID(frame);
				if ( value == 1 ) then
					value = nil;
				else
					value = value - (v.dropdown[2] or 0);
				end
			elseif ( not v.slider ) then
				local checked = frame:GetChecked();
				if ( v.flip ) then
					if ( checked ) then
						checked = nil
					else
						checked = 1;
					end
				end
				value = checked;		
			end
			if ( v.refresh and op[k] ~= value ) then
				refresh = 1;
			end
			op[k] = value;
		else
			ChatFrame1:AddMessage(k.." no frame?");
		end
	end
	
	if ( refresh ) then
		LootLink_Refresh();
	end
	LootLinkOptionsFrame.needsave = nil;
end

function LootLinkOptionsFrame_Defaults()
	local op = LootLinkState;
	for k, v in LootLinkOptionsList do
		op[k] = nil;
	end
	LootLinkOptions_LoadValues();
end

function LootLinkOptionsFrame_Cancel()
	if ( LootLinkOptionsFrame.needsave ) then
		LootLinkOptionsFrame.needsave = nil;
		if ( LootLinkOptionsFrame.x or LootLinkOptionsFrame.y ) then
			LootLink_Minimap_Position( LootLinkOptionsFrame.x, LootLinkOptionsFrame.y );
		end
	end
	PlaySound("gsTitleOptionExit");
	LootLinkOptionsFrame:Hide();
end

function LootLinkOptionsFrame_Okay()
	PlaySound("gsTitleOptionOK");
	LootLinkOptions_SaveValues();
	LootLinkOptionsFrame:Hide();
end

function LootLink_CloseWindows(ignoreCenter)
	if ( LootLinkOptionsFrame:IsVisible() ) then
		LootLinkOptionsFrame_Cancel();
		return 1;
	end
	if ( LootLinkSearchFrame:IsVisible() ) then
		LootLinkSearchFrame_Cancel();
		return 1;
	end
	return lOriginal_CloseWindows(ignoreCenter);
end

function LootLink_ValidateItem(name, link, fixlink)
	-- Basic validation of enchants and stat bonuses
	-- todo : more complex evaluation of the stat bonuses taking in to account item level and base stats
	if ( not link ) then return nil; 
	elseif ( string.sub(link, 1, 5) ~= "item:" ) then
		link = "item:"..link;
	end
	local realname, ilink, quality, level, sType, sSubType, count, equip = GetItemInfo(link);
	if ( realname ) then
		if ( realname ~= name ) then
			-- quickly kill anything that's really wrong.
			if ( not fixlink ) then	return nil; end
			name = realname;
		end
		
		local _,_, itemid, enchant, stat = string.find( link, "(%d+):(%d+):(%d+):%d+" );
		
		if ( tonumber(stat) > 0 ) then
			if ( quality ~= 2 and quality ~= 3 and itemid ~= "20039" ) then -- special case for Dark Iron Boots, grr.
				-- Nothing but Uncommon and Rare items have stat bonuses
				if ( not fixlink ) then	return nil; end
				stat = 0;
			elseif( lNoBonuses[equip] == 2 ) then
				-- Nothing that can't be equiped can't have stat bonuses
				if ( not fixlink ) then	return nil; end
				stat = 0;
			end
		end
		if ( tonumber(enchant) > 0 ) then
			if ( lNoBonuses[equip] ) then
				-- Nothing that isn't armor or weapons can have enchants
				if ( not fixlink ) then	return nil; end
				enchant = 0;
			end
		end
		
		return name, itemid..":"..enchant..":"..stat..":0";
	else
		if ( not fixlink ) then return 1; end
		return name, string.sub(link,6);
	end
end


--------------------------------------------------------------------------------------------------
-- Clean and Purge functions
--------------------------------------------------------------------------------------------------

local LLPtodo;
local LLPvars;

function LootLink_CheckDuplicate(name)
	local links = {};
	local value = ItemLinks[name];
	local dups = 0;
	if ( value and value.m ) then
		links[value.i] = 1;
		local i = 1;
		--while value and value.m and value.m[i] do
		for i=getn(value.m), 1, -1 do
			if ( links[value.m[i].i] ) then
				dups = dups + 1;
				LootLink_RemoveItem(name, nil, nil, i);
			else
				links[value.m[i].i] = 1;
			end
		end
	end
	return dups;
end

function LootLink_Process_Cancel()
	LootLink_Process:Hide();
	LootLink_Process:SetScript("OnUpdate", nil);
	LootLink_Process.fade = nil;
	LootLinkFrame.process = nil;
	LootLink_Process_ToggleButtons(1);
	
	if ( LLPtodo ) then
		local text = getglobal("LLP_"..LLPvars.functype.."_CANCEL");
		text = string.gsub( text, "%%r", LLPvars.total-LLPvars.done );
		text = string.gsub( text, "%%u", LLPvars.updated );
		text = string.gsub( text, "%%t", LLPvars.total );
		text = string.gsub( text, "%%f", LLPvars.failed );
		DEFAULT_CHAT_FRAME:AddMessage(text);

		LootLink_Process_Time:SetText("");

		LLPvars = nil;
		LLPtodo = nil;
	end
end

function LootLink_Process_Memory()
	LootLink_Process_Cancel();
	DEFAULT_CHAT_FRAME:AddMessage(LLP_ABORT1);
	DEFAULT_CHAT_FRAME:AddMessage(LLP_ABORT2);
end

function LootLink_Process_ToggleButtons(on)
	if ( on ) then
		LLO_Purge:Enable();
		LLO_Fix:Enable();
	else
		LLO_Purge:Disable();
		LLO_Fix:Disable();
	end
end

function LootLinkProcess_PURGE()
	local todo = LLPtodo[LLPvars.done+1];
	
	local name, link = todo[1], todo[2];	
	local _,_,itemid,enchantid,suffixid=string.find(link, "(%d+):(%d+):(%d+):%d+");
	
	LootLink_SortAlternates(name);
	local op = LootLinkState;
	local value = ItemLinks[name];
	
	if ( value and value.m ) then
		if ( op.nosame or (op.novariants and tonumber(suffixid) > 0) ) then
			LLPvars.updated = LLPvars.updated + getn(value.m);
			value.m = nil;
		end
		if ( op.enchants ) then
			if ( tonumber(enchantid) > 0 ) then
				if ( LootLink_RemoveItem(name, link) ) then
					return 1;
				end
			end
		end
	end
	value = LootLink_GetValue(name, link);
	if ( not value ) then
		return 2;
	end
	if ( value.c and op.rarityfilter ) then
		local rarity = lRarityFilter[value.c];
		if ( rarity and rarity < op.rarityfilter ) then
			if ( LootLink_RemoveItem(name, link) ) then
				return 1;
			end
		end
	end
	if ( LootLinkState.spoof ) then
		local valid = LootLink_ValidateItem(name, link)
		if ( not valid ) then
			if ( LootLink_RemoveItem(name, link) ) then
				return 1;
			end
		elseif ( valid == 1 ) then
			return 2;
		end
	end
end

function LootLinkProcess_FIXCACHE()
	local todo = LLPtodo[LLPvars.done+1];
	local name, link, index = todo[1], todo[2], todo[3];
	local realname = GetItemInfo("item:"..link);
	local value = LootLink_GetValue( name, nil, link );
	if ( not value ) then return; end
	value.i = string.gsub(link,"(%d+:%d+:%d+:)%d+","%10");
	if ( realname ) then
		if ( LootLink_BuildSearchData( name, value, index ) == 2 ) then
			return 1;
		end
	elseif ( not value.t or value.t == "" ) then
		value.t = name.."Bad Search Data";
		return 1;
	else
		local changes;
		if ( value.t ) then
			local t = value.t;
			local d = value.d;
			local s, e, val = string.find(t, "^(.-)");
			if ( not val or not string.find(name, val) ) then
				return 2;
			end
			if ( string.find(t, "%/") ) then
				-- cull out durability data.
				t = string.gsub(t, "Durability %d+ %/ %d+", "");
				-- check set data.
				s,e,val = string.find(t, ".+(.-) %(%d%/%d+%).+");
				if ( val ) then
					t = string.gsub(t, "(.+ %()%d+(%/%d+%).+)", "%10%2");
					local newset = LootLink_GetSet(val);
					local setnum = LL_SearchData(value, "se");
					if ( d ) then
						if ( not setnum ) then
							d = d.."se"..newset.."";
							changes = 1;
						elseif( newset ~= setnum ) then
							d = string.gsub(d, "(.+se)%d+(.+)", "%1"..newset.."%2");
							changes = 1;
						end
					end
				end
			end
			
			if ( value.t ~= t ) then
				changes = 1;
			end
			value.t = t;
			value.d = d;
		end
		if ( changes ) then
			return 1;
		end
		return 2;
	end
end

function LootLinkProcess_Process(func)
	if ( not LLPvars ) then
		LootLink_Process_ToggleButtons();
		
		LLPvars = {};
		
		LLPvars.func = getglobal( "LootLinkProcess_"..func );
		
		if ( not LLPvars.func ) then
			LootLink_Process_ToggleButtons(1);
			preprocess = nil;
			return;
		end
		
		HideUIPanel(LootLinkFrame);
		
		LootLink_Process_Header:SetText(getglobal("LLP_"..func.."_LABEL") );
		LootLink_Process_Num:SetText("Preprocessing");
		LootLink_Process_Bar:SetMinMaxValues(0,1);
		LootLink_Process_Bar:SetValue(0);
		
		LLPvars.functype = func;
		LLPvars.elapsed = 0;
		LLPvars.done = 0;
		LLPvars.updated = 0;
		LLPvars.failed = 0;
		LLPvars.wait = 0;
		LLPvars.total = 0;
		
		LootLink_Process:SetAlpha( 1 );
		LootLink_Process:Show();
		
		LootLinkFrame.process = func;
	end
end

function LootLinkProcess_Run()
	local thisprocess = 0;
	local func = LLPvars.func;
	
	if ( LLP_OUTOFMEM ) then
		LootLink_Process_Memory();
	end
	
	if ( LLPvars.wait ) then
		LLPvars.wait = LLPvars.wait - arg1;
		if ( LLPvars.wait <= 0 ) then
			LLPvars.wait = nil;
		end
		return;
	end
	
	if ( not LLPtodo ) then
		local normal, mult = 0,0;
		LLPtodo = {};
		for k, v in ItemLinks do
			if ( strlen(k) == 0 ) then
				ItemLinks[k] = nil;
				LLPvars.updated = LLPvars.updated + 1;
			elseif ( v.i ) then
				normal = normal + 1;
				tinsert( LLPtodo, {k, v.i} );
				if ( GetItemInfo("item:"..v.i) ) then
					LLHiddenTooltip:SetHyperlink("item:"..v.i);
				end
				if ( v.m ) then
					for i=1, getn(v.m) do
						if v.m[i].i then
							mult = mult + 1;
							tinsert( LLPtodo, {k, v.m[i].i, i} );
							if ( GetItemInfo("item:"..v.m[i].i) ) then
								LLHiddenTooltip:SetHyperlink("item:"..v.m[i].i);
							end
						else
							LootLink_RemoveItem(k, i);
							LLPvars.updated = LLPvars.updated + 1;
						end
					end
				end
			else
				LootLink_RemoveItem(k);
				LLPvars.updated = LLPvars.updated + 1;
			end
		end
		LLPvars.total = normal + mult;
		LootLink_Process_Bar:SetValue(0)
		LootLink_Process_Bar:SetMinMaxValues(0, LLPvars.total)
		LLPvars.wait = 2;
		return;
	end
	
	local todo = LLPtodo;
	
	LLPvars.elapsed = LLPvars.elapsed + arg1;
	
	while todo and todo[LLPvars.done+1] do
		local ret = func();
		if ( ret ) then
			if ( ret == 1 ) then
				LLPvars.updated = LLPvars.updated + 1;
			else
				LLPvars.failed = LLPvars.failed + 1;
			end
		end
		
		LLPvars.updated = LLPvars.updated + LootLink_CheckDuplicate(todo[LLPvars.done+1][1]);
		LootLink_SortAlternates(todo[LLPvars.done+1][1]);
		
		thisprocess = thisprocess + 1;
		LLPvars.done = LLPvars.done + 1;
		
		if ( thisprocess == 50 ) then
			thisprocess = 0;
			LootLink_Process_Bar:SetValue( LLPvars.done );
			LootLink_Process_Num:SetText( LLPvars.done .. "/".. LLPvars.total );
			local time = (LLPvars.elapsed / LLPvars.done ) * (LLPvars.total - LLPvars.done);
			local s = mod( time, 60 );
			local m = (time-s) / 60;
			s = floor(s)
			if ( strlen(s) == 1 ) then
				s = "0"..s;
			end
			LootLink_Process_Time:SetText( "Estimated "..m..":"..s.." remaining" );
			
			if ( DisplayIndices and LootLinkFrame:IsVisible() ) then
				LootLink_Update();
				LootLink_SetTitle();
			end
			
			return;
		end
	end
	
	if ( todo and not todo[LLPvars.done+1] ) then
		local text = getglobal("LLP_"..LLPvars.functype.."_END");
		text = string.gsub( text, "%%u", LLPvars.updated );
		text = string.gsub( text, "%%t", LLPvars.total );
		text = string.gsub( text, "%%f", LLPvars.failed );
		DEFAULT_CHAT_FRAME:AddMessage(text);
		LootLink_Process_Time:SetText("");
		LootLink_Process_Num:SetText("Finished");
		LootLink_Process_Bar:SetValue(LLPvars.total);

		LootLink_Process_ToggleButtons(1);
		
		LLPvars = nil;
		LLPtodo = nil;
		LootLinkFrame.process = nil;
		
		LootLink_Process.fade = 2;
		LootLink_Process:SetScript("OnUpdate", LootLink_Process_Fade);
	end
end

function LootLink_Process_Fade()
	if ( LootLink_Process.fade ) then
		LootLink_Process.fade = LootLink_Process.fade - (arg1 / 2);
		if ( LootLink_Process.fade < 1 ) then
			this:SetAlpha( LootLink_Process.fade );
			if ( LootLink_Process.fade <= 0 ) then
				this:Hide();
				LootLink_Process.fade = nil;
				LootLink_Process:SetScript("OnUpdate", nil);
			end
		end
	end
end

--------------------------------------------------------------------------------------------------
-- External functions
--------------------------------------------------------------------------------------------------

-- Looks for and deconstructs links contained in a text string; if trusted, existing information will be verified
-- Trusted sources are everything except chat links; since these can be spoofed, we want to allow them to be replaced
function LootLink_ProcessLinks(text, trusted)
	local enchants, spoof, nosame, rarityfilter, lastName = LootLinkState.enchants, LootLinkState.spoof, LootLinkState.nosame, LootLinkState.rarityfilter;
	if( text ) then
		for color, item, name in string.gfind(text, "|c(%x+)|Hitem:(%d+:%d+:%d+:%d+)|h%[(.-)%]|h|r") do
			if ( item ~= "0:0:0:0" and ( not rarityfilter or lRarityFilter[color] > rarityfilter ) and LootLink_CheckSame(name, item) ) then
				local fixedname, fixeditem;
				if ( not trusted ) then
					if ( spoof ) then
						name, item = LootLink_ValidateItem(name, item, 1);
					else
						fixedname, fixeditem = LootLink_ValidateItem(name, item, 1);
						if ( fixedname and fixedname ~= name ) then
							LootLink_AddItem(fixedname, fixeditem, color, trusted);
						end
					end
				end
				local enchant;
				if ( not enchants and not nosame ) then
					enchant = string.gsub(item, "^(%d+):(%d+):(%d+):%d+$", "%1:%2:%3:0");
				end
				item = string.gsub(item, "^(%d+):%d+:(%d+):%d+$", "%1:0:%2:0");
				LootLink_AddItem(name, item, color, trusted);
				if ( enchant and enchant ~= item ) then
					LootLink_AddItem(name, enchant, color, trusted);
				end
				LootLink_SortAlternates(name);
				lastName = name;
			end
		end
	end	
	return lastName;
end

function LootLink_CheckSame(name, item)
	local _,_,itemid,suffixid=string.find(item, "(%d+):%d+:(%d+):%d+");
	local value = ItemLinks[name];
	if ( value and (LootLinkState.nosame or LootLinkState.novariants) ) then
		-- Assumes no same name items are both different itemid and have suffixids
		if ( value.i and value.i ~= item ) then
			for id, suf in string.gfind( value.i, "(%d+):%d+:(%d+):") do
				if ( nosame and tonumber(id) > tonumber(itemid) ) then
					return;
				elseif ( tonumber(suf) > tonumber(suffixid) ) then
					return;
				end
			end
		end
	end
	return 1;
end

-- old filter function, keeping for posterity
--[[function LootLink_FilterCheck(name, item, color)
	if ( item == "0:0:0:0" ) then
		return;
	end
	local op = LootLinkState;
	if ( op.rarityfilter ) then
		local rarity = lRarityFilter[color];
		if ( rarity and rarity < op.rarityfilter ) then
			return;
		end
	end		
	local _,_,itemid,enchantid,suffixid=string.find(item, "(%d+):(%d+):(%d+):%d+");
	if ( not tonumber(enchantid) ) then
		--ChatFrame1:AddMessage((name or "??").." no enchant id "..(item or "???"));
		return;
	end
	local value = ItemLinks[name];
	if ( value and (op.nosame or op.novariants) ) then
		-- Assumes no same name items are both different itemid and have suffixids
		if ( value.i and value.i ~= item ) then
			for id, suf in string.gfind( value.i, "(%d+):%d+:(%d+):") do
				if ( op.nosame and tonumber(id) > tonumber(itemid) ) then
					return;
				elseif ( tonumber(suf) > tonumber(suffixid) ) then
					return;
				end
			end
		end
	end
	return 1;
end]]

-- Add items to the database
function LootLink_AddItem(name, link, color, trusted)
	local nosame, value, index = LootLinkState.nosame;
	if ( name and link ) then
		if ( not ItemLinks[name] ) then
			ItemLinks[name] = { };
			lItemLinksSizeTotal = lItemLinksSizeTotal + 1;
			if( LootLink_GetDataVersion() < 110 ) then
				-- Set a flag to indicate that this item is new and should be skipped on a makehome
				ItemLinks[name]._ = 1;
			end
		end
		value = ItemLinks[name];
		if ( not nosame and LootLinkState.novariants ) then
			for suffixid in string.gfind(link, "%d+:%d+:(%d+):%d+") do
				if ( tonumber(suffixid) > 0 ) then
					nosame = 1;
				end
			end
		end
		if ( not nosame and value.i and value.i ~= link ) then
			if ( value.m and getn(value.m) > 0 ) then
				for k, v in value.m do
					if ( v.i == link ) then
						if ( not trusted ) then
							return nil;
						end
						index = k;
						break;
					end
				end
			end
			if ( index ) then
				value = value.m[index];
			else
				if ( not value.m ) then
					value.m = {};
				end
				tinsert( value.m, {} );
				index = getn(value.m);
				value = value.m[index];
				lItemLinksSizeTotal = lItemLinksSizeTotal + 1;
			end
		end
		
		value.i = link;
		value.c = color;
		
		LootLink_BuildSearchData(name, value, index);
		if( not LootLink_CheckItemServerRaw(value, lServerIndex) ) then
			LootLink_AddItemServer(value, lServerIndex);
		end
		return value;
	end
end

-- Sort same name items by ids
function LootLink_SortAlternates(name)
	if ( not name ) then return; end
	local value = ItemLinks[name];
	local newval;
	local values = {};
	local itemval = {};

	-- cleaned up by Adrine
	function sortByLink(e1, e2)
		if ( not e1 ) then return true;	elseif ( not e2 ) then return false; end
		local _,_,a,b,c = string.find(e1.i, "(%d+):(%d+):(%d+)");
		local _,_,d,e,f = string.find(e2.i, "(%d+):(%d+):(%d+)");

		if a ~= d then return tonumber(a) > tonumber(d); end
		if c ~= f then return tonumber(c) > tonumber(f); end
		if b ~= e then return tonumber(b) < tonumber(e); end
		return false;
	end
			
	if value and value.m then
		if ( getn(value.m) == 0 ) then
			value.m = nil;
			return nil;
		end
		for k, v in value do
			if ( k ~= "m" ) then
				itemval[k] = v;
			end
		end
		tinsert( values, itemval );
		for k, v in value.m do
			tinsert( values, v );
		end
		
		table.sort(values, sortByLink);
		
		newval = values[1];
		newval.m = {};
		for i=2, getn(values) do
			tinsert( newval.m, values[i] );
		end
	else
		return nil;
	end
	ItemLinks[name] = {};
	ItemLinks[name] = newval;
end

-- Adds icon of the item if it's valid
function LootLink_AddTooltipTexture(link)
	if ( link and not LootLinkState.hideicon) then
		local _, _, _, _, _, _, _, _, itemTexture = GetItemInfo(link);
		if ( itemTexture ) then
			local iItem = LootLinkFrame.TooltipButton or 0;
			LootLinkTooltipIconTexture:SetTexture( itemTexture );
			LootLinkTooltipIcon:ClearAllPoints();
			LootLinkTooltipIcon:SetPoint("BOTTOMLEFT", getglobal("LootLinkItem"..iItem), "BOTTOMRIGHT");
			LootLinkTooltipIcon:Show();
			return;
		end
	end
	LootLinkTooltipIcon:Hide();
end			

-- Adds extra tooltip information for the item with the given name
function LootLink_AddTooltipInfo(name, tooltip, quantity, index)
	if( not tooltip ) then
		tooltip = LootLinkTooltip;
	end
	if( not quantity ) then
		quantity = 1;
	end
	if( tooltip == LootLinkTooltip ) then
		LootLink_HideTooltipMoney();
	end
	if( not LootLinkState.HideInfo and ItemLinks[name] ) then
		if( tooltip == GameTooltip ) then
			GameTooltip.llDone = 1;
		end
		if( ItemLinks[name].p ) then
			LootLink_SetTooltipMoney(tooltip, quantity, ItemLinks[name].p, ItemLinks[name].x);
		end
		LootLink_AddExtraTooltipInfo(tooltip, name, quantity, ItemLinks[name]);
		tooltip:Show();
	end
end

-- This will set up a tooltip with item information for the given name if it's known
function LootLink_SetTooltip(tooltip, name, quantity)
	local link;
	
	if( tooltip and name ) then
		link = LootLink_GetHyperlink(name);
		if( link ) then
			LootLink_SetHyperlink(tooltip, name, link);
			if( quantity ) then
				quantity = tonumber(quantity);
			else
				quantity = 1;
			end
			if( quantity > 0 ) then
				LootLink_AddTooltipInfo(name, tooltip, quantity);
			end
		end
	end
end

-- 
function LootLink_ChatEdit_OnTabPressed()
	
	lOriginal_ChatEdit_OnTabPressed();
end

function LootLink_ChatEdit_OnTextChanged()
	local text = this:GetText();
	if (string.sub(text, 1, 7) ~= "/script" or string.sub(text, 1, 5) ~= "/dump") then
		--text = string.gsub(text, "([|]?[h]?)%[(.-)%]([|]?[h]?)", FLT_LinkifyName);
		--end
	end
	lOriginal_ChatEdit_OnTextChanged();
end

-- Calling this will allow LootLink to automatically add information to tooltips when needed
function LootLink_AutoInfoOn()
	lSuppressInfoAdd = nil;
end

-- Calling this will prevent LootLink from automatically adding information to tooltips
function LootLink_AutoInfoOff()
	lSuppressInfoAdd = 1;
end

-- Use this function to get the current server name from LootLink's perspective
function LootLink_GetCurrentServerName()
	return lServer;
end

-- Use this function to get the current server index
function LootLink_GetCurrentServerIndex()
	return lServerIndex;
end

-- Use this function to map a server name to the server index for the ItemLinks[name].servers array
function LootLink_GetServerIndex(name)
	if( not LootLinkState or not LootLinkState.ServerNamesToIndices ) then
		return nil;
	end
	return LootLinkState.ServerNamesToIndices[name];
end

-- Use this function to check whether an ItemLinks[name] entry is valid for a given server index
function LootLink_CheckItemServer(item, serverIndex)
	-- If we haven't converted and this item predates multiple server support, count it as valid
	if( LootLink_GetDataVersion() < 110 and not item._ ) then
		return 1;
	end
	return LootLink_CheckItemServerRaw(item, serverIndex);
end

-- Used for debugging changes in item data and tooltip format
function LootLink_Validate()
	for index, value in ItemLinks do
		if( not value.d ) then
			DEFAULT_CHAT_FRAME:AddMessage(index.." has no search data");
--		else
--			if( not LL_SearchData(value, "su") ) then
--				DEFAULT_CHAT_FRAME:AddMessage(index.." has no subtype");
--			end
		end
	end
end

--------------------------------------------------------------------------------------------------
-- Hookable callback functions
--------------------------------------------------------------------------------------------------

-- Hook this function to add any extra information you like to the tooltip
function LootLink_AddExtraTooltipInfo(tooltip, name, quantity, item)
	-- tooltip: the current tooltip frame
	-- name: the name of the item
	-- quantity: the number of items, if known, else 1
	-- item: ItemLinks[name]; LootLink's data for this item
end

-- Hook this function to be called whenever an equipment slot is successfully inspected
function LootLink_Event_InspectSlot(name, count, item, unit, slotid)
	-- name: the name of the item
	-- count: the number of items, if known, else 1
	-- item: ItemLinks[name]; LootLink's data for this item
	-- unit: "target", "player", etc.
	-- slotid: the id of the slot inspected
end

-- Hook this function to be called whenever an inventory slot is successfully inspected
function LootLink_Event_ScanInventory(name, count, item, bagid, slotid)
	-- name: the name of the item
	-- count: the number of items, if known, else 1
	-- item: ItemLinks[name]; LootLink's data for this item
	-- bagid: the id of the bag containing the item
	-- slotid: the id of the slot inspected
end

-- Hook this function to be called whenever a bank slot is successfully inspected
function LootLink_Event_ScanBank(name, count, item, bagid, slotid)
	-- name: the name of the item
	-- count: the number of items, if known, else 1
	-- item: ItemLinks[name]; LootLink's data for this item
	-- bagid: the id of the bag containing the item
	-- slotid: the id of the slot inspected
end

-- Hook this function to be called whenever an auction entry is successfully inspected
function LootLink_Event_ScanAuction(name, count, item, auctionpage, auctionid)
	-- name: the name of the item
	-- count: the number of items, if known, else 1
	-- item: ItemLinks[name]; LootLink's data for this item
	-- auctionpage: the page number this item was found on
	-- auctionid: the id of the inspected item
end

-- Hook this function to be called whenever a chat message is successfully inspected
function LootLink_Event_ScanChat(name, item, text)
	-- name: the name of the last item in the chat message
	-- item: ItemLinks[name]; LootLink's data for this item
	-- text: the inspected chat message
end

-- Hook this function to be called whenever LootLink starts an auction scan
function LootLink_Event_StartAuctionScan()
end

-- Hook this function to be called whenever LootLink stops an auction scan
function LootLink_Event_StopAuctionScan()
end

-- Hook this function to be called whenever LootLink completes a full auction scan
function LootLink_Event_FinishedAuctionScan()
end

-- Hook this function to be called whenever LootLink sends a new auction query
function LootLink_Event_AuctionQuery(auctionpage)
	-- auctionpage: the page number for the query that was just sent
end


--------------------------------------------------------------------------------------------------
-- Static Popup Dialogs
--------------------------------------------------------------------------------------------------

StaticPopupDialogs["LOOTLINK_DELETEITEM_CONFIRM"] = {
	text = TEXT(LOOTLINK_STATICPOPUP_DELETE_ITEM_CONFIRM),
	button1 = TEXT(YES),
	button2 = TEXT(NO),
	OnAccept = function(data)
		if ( not data ) then return; end
		if ( LootLink_RemoveItem(data[1], data[2].i, data[3] ) ) then
			LootLink_Update();
			LootLink_SetTitle();
		end
	end,
	showAlert = 1,
	timeout = 0,
	exclusive = 1,
	whileDead = 1,
	hideOnEscape = 1,
	};

StaticPopupDialogs["LOOTLINK_LITEMODE_CONFIRM"] = {
	text = TEXT(LOOTLINK_STATICPOPUP_LITE_MODE_CONFIRM),
	button1 = TEXT(OKAY),
	button2 = TEXT(CANCEL),
	OnCancel = function()
		LLO_LiteModeCheckButton:SetChecked();
	end,
	showAlert = 1,
	timeout = 0,
	exclusive = 1,
	whileDead = 1,
	hideOnEscape = 1,
	};

StaticPopupDialogs["LOOTLINK_LINK_CONFIRM"] = {
	text = TEXT(LOOTLINK_STATICPOPUP_ITEM_CONFIRM),
	button1 = TEXT(OKAY),
	button2 = TEXT(CANCEL),
	OnAccept = function(data)
		ChatFrameEditBox:Insert(LootLink_GetLink(data[1], data[3]));
	end,
	showAlert = 1,
	timeout = 0,
	exclusive = 1,
	whileDead = 1,
	hideOnEscape = 1,
	};

StaticPopupDialogs["LOOTLINK_DRESSUP_CONFIRM"] = {
	text = TEXT(LOOTLINK_STATICPOPUP_ITEM_CONFIRM),
	button1 = TEXT(OKAY),
	button2 = TEXT(CANCEL),
	OnAccept = function(data)
		DressUpItemLink(LootLink_GetLink(data[1], data[3]));
	end,
	showAlert = 1,
	timeout = 0,
	exclusive = 1,
	whileDead = 1,
	hideOnEscape = 1,
	};
	

--------------------------------------------------------------------------------------------------
-- Type links - borrowed from Gazmik Fizzwidget's Linkerator
--------------------------------------------------------------------------------------------------

function LootLink_TypeLinks_LinkifyName(head, text, tail)
	if (head ~= "|h" and tail ~= "|h") then -- only linkify things text that isn't linked already
		local link = LootLink_GetLink(text);
		if (link) then return link; end
	end
	return head.."["..text.."]"..tail;
end

function LootLink_TypeLinks_ParseChatMessage(text)
	return string.gsub(text, "([|]?[h]?)%[(.-)%]([|]?[h]?)", LootLink_TypeLinks_LinkifyName);
end

-- thank you ckknight
function LootLink_TypeLinks_AutoComplete(text)
	local _,_,body,name = string.find( text, "(.*%[)(%S[^%[^%]]+)$" );
	if ( name ) then
		local textlen, namelen, found = strlen(text), strlen(name);
		if ( this.lastLength and this.lastItem and textlen > this.lastLength ) then
			if string.find(strlower(this.lastItem), strlower(name)) then
				this.lastLength = textlen;
				this:SetText(body..this.lastItem);
				this:HighlightText(textlen);
				return 1;
			end
		end
		this.lastLength = textlen;
		for k, v in ItemLinks do
			if ( strlen(k) >= namelen and strlower(strsub(k,1,namelen)) == strlower(name) ) then
				found = 1;
				if ( (not this.itemComplete or not this.itemComplete[k]) ) then
					if ( not this.itemComplete ) then
						this.itemComplete = {};
					end
					this.ignoreTextChange = 1;
					this:SetText(body..k);
					this:HighlightText(textlen);
					this.lastItem = k;
					this.itemComplete[k] = 1;
					return 1;
				end
			end
		end
		if ( found ) then
			this.lastLength = nil;
			this.lastItem = nil;
			this.itemComplete = nil;
			return LootLink_TypeLinks_AutoComplete(text);
		end
	end
	this.lastItem = nil;
	this.itemComplete = nil;
end

-- Hooks

lOrig_ChatEdit_OnTextChanged = ChatEdit_OnTextChanged;
function ChatEdit_OnTextChanged()
	if ( LootLinkState.typelinks ) then
		local text = this:GetText();
		if (string.find(text, "^/script") or string.find(text, "^/dump")) then
			-- don't parse
		else
			local newtext = LootLink_TypeLinks_ParseChatMessage(text);
			if ( text ~= newtext ) then
				this:SetText(newtext);
				return;
			end
		end
	end
	lOrig_ChatEdit_OnTextChanged(this);
end

lOrig_ChatEdit_OnChar = ChatFrameEditBox:GetScript("OnChar");
function ChatEdit_OnChar()
	if ( LootLinkState.typelinks and LootLinkState.autotypelinks ) then
		local text = this:GetText();
		if ( string.find(text, "^/script") or string.find(text, "^/dump")) then
		else
			LootLink_TypeLinks_AutoComplete(text);
		end
	elseif ( lOrig_ChatEdit_OnChar ) then
		lOrig_ChatEdit_OnChar(this);
	end
end
ChatFrameEditBox:SetScript("OnChar", ChatEdit_OnChar);

lOrig_ChatEdit_OnTabPressed = ChatEdit_OnTabPressed;
function ChatEdit_OnTabPressed()
	this.lastLength = nil;
	if ( LootLinkState.typelinks and LootLinkState.autotypelinks and this.lastItem ) then
		this:Insert("");
	else
		lOrig_ChatEdit_OnTabPressed();
	end
end

lOrig_ChatEdit_OnEnterPressed = ChatEdit_OnEnterPressed;
function ChatEdit_OnEnterPressed()
	if ( LootLinkState.typelinks and LootLinkState.autotypelinks and this.itemComplete ) then
		this:SetText(this:GetText().."]");
	else
		lOrig_ChatEdit_OnEnterPressed();
	end
	this.itemComplete = nil;
end
