gOutfitter_Settings = nil;

local Outfitter_cInitializationEvent = "PLAYER_ENTERING_WORLD";

local BANKED_FONT_COLOR = {r = 0.25, g = 0.2, b = 1.0};
local BANKED_FONT_COLOR_CODE = "|cff4033ff";
local OUTFIT_MESSAGE_COLOR = {r = 0.2, g = 0.75, b = 0.3};

local Outfitter_cSlotNames =
{
	-- First priority goes to armor
	
	"HeadSlot",
	"ShoulderSlot",
	"ChestSlot",
	"WristSlot",
	"HandsSlot",
	"WaistSlot",
	"LegsSlot",
	"FeetSlot",
	
	-- Second priority goes to weapons
	
	"MainHandSlot",
	"SecondaryHandSlot",
	"RangedSlot",
	"AmmoSlot",
	
	-- Last priority goes to items with no durability
	
	"BackSlot",
	"NeckSlot",
	"ShirtSlot",
	"TabardSlot",
	"Finger0Slot",
	"Finger1Slot",
	"Trinket0Slot",
	"Trinket1Slot",
};

local Outfitter_cSlotDisplayNames =
{
	HeadSlot = HEADSLOT,
	NeckSlot = NECKSLOT,
	ShoulderSlot = SHOULDERSLOT,
	BackSlot = BACKSLOT,
	ChestSlot = CHESTSLOT,
	ShirtSlot = SHIRTSLOT,
	TabardSlot = TABARDSLOT,
	WristSlot = WRISTSLOT,
	HandsSlot = HANDSSLOT,
	WaistSlot = WAISTSLOT,
	LegsSlot = LEGSSLOT,
	FeetSlot = FEETSLOT,
	Finger0Slot = Outfitter_cFinger0SlotName,
	Finger1Slot = Outfitter_cFinger1SlotName,
	Trinket0Slot = Outfitter_cTrinket0SlotName,
	Trinket1Slot = Outfitter_cTrinket1SlotName,
	MainHandSlot = MAINHANDSLOT,
	SecondaryHandSlot = SECONDARYHANDSLOT,
	RangedSlot = RANGEDSLOT,
	AmmoSlot = AMMOSLOT,
};

local Outfitter_cInvTypeToSlotName =
{
	INVTYPE_2HWEAPON = {SlotName = "MainHandSlot", MetaSlotName = "TwoHandSlot"},
	INVTYPE_BAG = {SlotName = "Bag"},
	INVTYPE_BODY = {SlotName = "ShirtSlot"},
	INVTYPE_CHEST = {SlotName = "ChestSlot"},
	INVTYPE_CLOAK = {SlotName = "BackSlot"},
	INVTYPE_FEET = {SlotName = "FeetSlot"},
	INVTYPE_FINGER = {SlotName = "Finger0Slot"},
	INVTYPE_HAND = {SlotName = "HandsSlot"},
	INVTYPE_HEAD = {SlotName = "HeadSlot"},
	INVTYPE_HOLDABLE = {SlotName = "SecondaryHandSlot"},
	INVTYPE_LEGS = {SlotName = "LegsSlot"},
	INVTYPE_NECK = {SlotName = "NeckSlot"},
	INVTYPE_RANGED = {SlotName = "RangedSlot"},
	INVTYPE_ROBE = {SlotName = "ChestSlot"},
	INVTYPE_SHIELD = {SlotName = "SecondaryHandSlot"},
	INVTYPE_SHOULDER = {SlotName = "ShoulderSlot"},
	INVTYPE_TABARD = {SlotName = "TabardSlot"},
	INVTYPE_TRINKET = {SlotName = "Trinket0Slot"},
	INVTYPE_WAIST = {SlotName = "WaistSlot"},
	INVTYPE_WEAPON = {SlotName = "MainHandSlot", MetaSlotName = "Weapon0Slot"},
	INVTYPE_WEAPONMAINHAND = {SlotName = "MainHandSlot"},
	INVTYPE_WEAPONOFFHAND = {SlotName = "SecondaryHandSlot"},
	INVTYPE_WRIST = {SlotName = "WristSlot"},
	INVTYPE_RANGEDRIGHT = {SlotName = "RangedSlot"},
	INVTYPE_AMMO = {SlotName = "AmmoSlot"},
	INVTYPE_THROWN = {SlotName = "RangedSlot"},
	INVTYPE_RELIC = {SlotName = "RangedSlot"},
};

local Outfitter_cHalfAlternateStatSlot =
{
	Trinket0Slot = "Trinket1Slot",
	Finger0Slot = "Finger1Slot",
	Weapon0Slot = "Weapon1Slot",
};

local Outfitter_cFullAlternateStatSlot =
{
	Trinket0Slot = "Trinket1Slot",
	Trinket1Slot = "Trinket0Slot",
	Finger0Slot = "Finger1Slot",
	Finger1Slot = "Finger0Slot",
	Weapon0Slot = "Weapon1Slot",
	Weapon1Slot = "Weapon0Slot",
};

local gOutfitter_cCategoryOrder =
{
	"Complete",
	"Partial",
	"Accessory",
	"Special"
};

local gOutfitter_Collapsed = {};
local gOutfitter_BankFrameOpened = false;

local Outfitter_cItemAliases =
{
	[18608] = 18609,	-- Benediction -> Anathema
	[18609] = 18608,	-- Anathema -> Benediction
	[17223] = 17074,	-- Thunderstrike -> Shadowstrike
	[17074] = 17223,	-- Shadowstrike -> Thunderstrike
};

local Outfitter_cSpecialtyBags =
{
	[21340] = {Name = "Soul Pouch", Type = "ShardBag"},
	[21341] = {Name = "Felcloth Bag", Type = "ShardBag"},
	[21342] = {Name = "Core Felcloth Bag", Type = "ShardBag"},
	[22243] = {Name = "Small Soul Pouch", Type = "ShardBag"},
	[22244] = {Name = "Box of Souls", Type = "ShardBag"},
	
	[2102] = {Name = "Small Ammo Pouch", Type = "AmmoPouch"},
	[7279] = {Name = "Small Leather Ammo Pouch", Type = "AmmoPouch"},
	[8218] = {Name = "Thick Leather Ammo Pouch", Type = "AmmoPouch"},
	[7372] = {Name = "Heavy Leather Ammo Pouch", Type = "AmmoPouch"},
	[3574] = {Name = "Hunting Ammo Sack", Type = "AmmoPouch"},
	[3604] = {Name = "Bandolier of the Night Watch", Type = "AmmoPouch"},
	[5441] = {Name = "Small Shot Pouch", Type = "AmmoPouch"},
	[2663] = {Name = "Ribbly's Bandolier", Type = "AmmoPouch"},
	[19320] = {Name = "Gnoll Skin Bandolier", Type = "AmmoPouch"},

	[19319] = {Name = "Harpy Hide Quiver", Type = "Quiver"},
	[7371] = {Name = "Heavy Quiver", Type = "Quiver"},
	[3573] = {Name = "Hunting Quiver", Type = "Quiver"},
	[7278] = {Name = "Light Leather Quiver", Type = "Quiver"},
	[2101] = {Name = "Light Quiver", Type = "Quiver"},
	[11362] = {Name = "Medium Quiver", Type = "Quiver"},
	[8217] = {Name = "Quickdraw Quiver", Type = "Quiver"},
	[3605] = {Name = "Quiver of the Night Watch", Type = "Quiver"},
	[2662] = {Name = "Ribbly's Quiver", Type = "Quiver"},
	[5439] = {Name = "Small Quiver", Type = "Quiver"},
	[18714] = {Name = "Ancient Sinew Wrapped Lamina", Type = "Quiver"},
	
	[22246] = {Name = "Enchanted Mageweave Pouch", Type = "Enchant"},
	[22248] = {Name = "Enchanted Runecloth Bag", Type = "Enchant"},
	[22249] = {Name = "Big Bag of Enchantment", Type = "Enchant"},
	
	[22250] = {Name = "Herb Pouch", Type = "Herb"},
	[22251] = {Name = "Cenarian Herb Bag", Type = "Herb"},
	[22252] = {Name = "Satchel of Cenarious", Type = "Herb"},
};

local Outfitter_cFishingPoles =
{
	{Code = 19970, SubCode = 0}, -- Outfitter_cArcaniteFishingPole
	{Code = 19022, SubCode = 0}, -- Outfitter_cNatPaglesFishingPole
	{Code = 12224, SubCode = 0}, -- Outfitter_cBlumpFishingPole
	{Code = 6367, SubCode = 0}, -- Outfitter_cBigIronFishingPole
	{Code = 6365, SubCode = 0}, -- Outfitter_cStrongFishingPole
	{Code = 6256, SubCode = 0}, -- Outfitter_cFishingPole
};

local Outfitter_cRidingItems =
{
	{Code = 11122, SubCode = 0}, -- Outfitter_cCarrotOnAStick
};

local Outfitter_cArgentDawnTrinkets = 
{
	{Code = 13209, SubCode = 0}, -- Outfitter_cSealOfTheDawn
	{Code = 19812, SubCode = 0}, -- Outfitter_cRuneOfTheDawn
	{Code = 12846, SubCode = 0}, -- Outfitter_cArgentDawnCommission
};

local Outfitter_cStatIDItems =
{
	Fishing = Outfitter_cFishingPoles,
	Riding = Outfitter_cRidingItems,
	ArgentDawn = Outfitter_cArgentDawnTrinkets,
};

local Outfitter_cIgnoredUnusedItems = 
{
	[2901] = "Mining Pick",
	[5956] = "Blacksmith hammer",
	[6219] = "Arclight Spanner",
	[7005] = "Skinning Knife",
	[7297] = "Morbent's Bane",
	[10696] = "Enchanted Azsharite Felbane Sword",
	[10697] = "Enchanted Azsharite Felbane Dagger",
	[10698] = "Enchanted Azsharite Felbane Staff",
	[20406] = "Twilight Cultist Mantle",
	[20407] = "Twilight Cultist Robe",
	[20408] = "Twilight Cultist Cowl",
};

local Outfitter_cSmartOutfits =
{
	{Name = Outfitter_cFishingOutfit, StatID = "Fishing", IsAccessory = true},
	{Name = Outfitter_cHerbalismOutfit, StatID = "Herbalism", IsAccessory = true},
	{Name = Outfitter_cMiningOutfit, StatID = "Mining", IsAccessory = true},
	{Name = Outfitter_cSkinningOutfit, StatID = "Skinning", IsAccessory = true},
	{Name = Outfitter_cFireResistOutfit, StatID = "FireResist"},
	{Name = Outfitter_cNatureResistOutfit, StatID = "NatureResist"},
	{Name = Outfitter_cShadowResistOutfit, StatID = "ShadowResist"},
	{Name = Outfitter_cArcaneResistOutfit, StatID = "ArcaneResist"},
	{Name = Outfitter_cFrostResistOutfit, StatID = "FrostResist"},
};

local Outfitter_cStatCategoryInfo =
{
	{Category = "Stat", Name = Outfitter_cStatsCategory},
	{Category = "Melee", Name = Outfitter_cMeleeCategory},
	{Category = "Spell", Name = Outfitter_cSpellsCategory},
	{Category = "Regen", Name = Outfitter_cRegenCategory},
	{Category = "Resist", Name = Outfitter_cResistCategory},
	{Category = "Trade", Name = Outfitter_cTradeCategory},
};

local Outfitter_cItemStatInfo =
{
	{ID = "Agility", Name = Outfitter_cAgilityStatName, Category = "Stat"},
	{ID = "Armor", Name = Outfitter_cArmorStatName, Category = "Stat"},
	{ID = "Defense", Name = Outfitter_cDefenseStatName, Category = "Stat"},
	{ID = "Intellect", Name = Outfitter_cIntellectStatName, Category = "Stat"},
	{ID = "Spirit", Name = Outfitter_cSpiritStatName, Category = "Stat"},
	{ID = "Stamina", Name = Outfitter_cStaminaStatName, Category = "Stat"},
	{ID = "Strength", Name = Outfitter_cStrengthStatName, Category = "Stat"},
	
	{ID = "ManaRegen", Name = Outfitter_cManaRegenStatName, Category = "Regen"},
	{ID = "HealthRegen", Name = Outfitter_cHealthRegenStatName, Category = "Regen"},
	
	{ID = "SpellCrit", Name = Outfitter_cSpellCritStatName, Category = "Spell"},
	{ID = "SpellHit", Name = Outfitter_cSpellHitStatName, Category = "Spell"},
	{ID = "SpellDmg", Name = Outfitter_cSpellDmgStatName, Category = "Spell"},
	{ID = "FrostDmg", Name = Outfitter_cFrostDmgStatName, Category = "Spell"},
	{ID = "FireDmg", Name = Outfitter_cFireDmgStatName, Category = "Spell"},
	{ID = "ArcaneDmg", Name = Outfitter_cArcaneDmgStatName, Category = "Spell"},
	{ID = "ShadowDmg", Name = Outfitter_cShadowDmgStatName, Category = "Spell"},
	{ID = "NatureDmg", Name = Outfitter_cNatureDmgStatName, Category = "Spell"},
	{ID = "Healing", Name = Outfitter_cHealingStatName, Category = "Spell"},
	
	{ID = "MeleeCrit", Name = Outfitter_cMeleeCritStatName, Category = "Melee"},
	{ID = "MeleeHit", Name = Outfitter_cMeleeHitStatName, Category = "Melee"},
	{ID = "MeleeDmg", Name = Outfitter_cMeleeDmgStatName, Category = "Melee"},
	{ID = "Dodge", Name = Outfitter_cDodgeStatName, Category = "Melee"},
	{ID = "Attack", Name = Outfitter_cAttackStatName, Category = "Melee"},
	
	{ID = "ArcaneResist", Name = Outfitter_cArcaneResistStatName, Category = "Resist"},
	{ID = "FireResist", Name = Outfitter_cFireResistStatName, Category = "Resist"},
	{ID = "FrostResist", Name = Outfitter_cFrostResistStatName, Category = "Resist"},
	{ID = "NatureResist", Name = Outfitter_cNatureResistStatName, Category = "Resist"},
	{ID = "ShadowResist", Name = Outfitter_cShadowResistStatName, Category = "Resist"},
	
	{ID = "Fishing", Name = Outfitter_cFishingStatName, Category = "Trade"},
	{ID = "Herbalism", Name = Outfitter_cHerbalismStatName, Category = "Trade"},
	{ID = "Mining", Name = Outfitter_cMiningStatName, Category = "Trade"},
	{ID = "Skinning", Name = Outfitter_cSkinningStatName, Category = "Trade"},
};

local Outfitter_cNormalizedClassName =
{
	[Outfitter_cDruidClassName] = "Druid",
	[Outfitter_cHunterClassName] = "Hunter",
	[Outfitter_cMageClassName] = "Mage",
	[Outfitter_cPaladinClassName] = "Paladin",
	[Outfitter_cPriestClassName] = "Priest",
	[Outfitter_cRogueClassName] = "Rogue",
	[Outfitter_cShamanClassName] = "Shaman",
	[Outfitter_cWarlockClassName] = "Warlock",
	[Outfitter_cWarriorClassName] = "Warrior",
};

local Outfitter_cClassSpecialOutfits =
{
	Warrior =
	{
		{Name = Outfitter_cWarriorBattleStance, SpecialID = "Battle"},
		{Name = Outfitter_cWarriorDefensiveStance, SpecialID = "Defensive"},
		{Name = Outfitter_cWarriorBerserkerStance, SpecialID = "Berserker"},
	},
	
	Druid =
	{
		{Name = Outfitter_cDruidBearForm, SpecialID = "Bear"},
		{Name = Outfitter_cDruidCatForm, SpecialID = "Cat"},
		{Name = Outfitter_cDruidAquaticForm, SpecialID = "Aquatic"},
		{Name = Outfitter_cDruidTravelForm, SpecialID = "Travel"},
		{Name = Outfitter_cDruidMoonkinForm, SpecialID = "Moonkin"},
	},
	
	Priest =
	{
		{Name = Outfitter_cPriestShadowform, SpecialID = "Shadowform"},
	},
	
	Rogue =
	{
		{Name = Outfitter_cRogueStealth, SpecialID = "Stealth"},
	},
	
	Shaman =
	{
		{Name = Outfitter_cShamanGhostWolf, SpecialID = "GhostWolf"},
	},
	
	Hunter =
	{
		{Name = Outfitter_cHunterMonkey, SpecialID = "Monkey"},
		{Name = Outfitter_cHunterHawk, SpecialID = "Hawk"},
		{Name = Outfitter_cHunterCheetah, SpecialID = "Cheetah"},
		{Name = Outfitter_cHunterPack, SpecialID = "Pack"},
		{Name = Outfitter_cHunterBeast, SpecialID = "Beast"},
		{Name = Outfitter_cHunterWild, SpecialID = "Wild"},
	},
	
	Mage =
	{
		{Name = Outfitter_cMageEvocate, SpecialID = "Evocate"},
	},
};

local	gOutfitter_SpellNameSpecialID =
{
	[Outfitter_cAspectOfTheCheetah] = "Cheetah",
	[Outfitter_cAspectOfThePack] = "Pack",
	[Outfitter_cAspectOfTheBeast] = "Beast",
	[Outfitter_cAspectOfTheWild] = "Wild",
	[Outfitter_cEvocate] = "Evocate",
};

local	gOutfitter_AuraIconSpecialID =
{
	["INV_Misc_Fork&Knife"] = "Dining",
	["Spell_Shadow_Shadowform"] = "Shadowform",
	["Spell_Nature_SpiritWolf"] = "GhostWolf",
	["Ability_Rogue_FeignDeath"] = "Feigning",
	["Ability_Hunter_AspectOfTheMonkey"] = "Monkey",
	["Spell_Nature_RavenForm"] = "Hawk",
};

local Outfitter_cSpecialOutfitDescriptions =
{
	ArgentDawn = Outfitter_cArgentDawnOutfitDescription,
	Riding = Outfitter_cRidingOutfitDescription,
	Dining = Outfitter_cDiningOutfitDescription,
	Battleground = Outfitter_cBattlegroundOutfitDescription,
	AB = Outfitter_cArathiBasinOutfitDescription,
	AV = Outfitter_cAlteracValleyOutfitDescription,
	WSG = Outfitter_cWarsongGulchOutfitDescription,
	BR = Outfitter_cBloodRingOutfitDescription,
	SGV = Outfitter_cSunnygladeValleyOutfitDescription,
	City = Outfitter_cCityOutfitDescription,
};

-- Note that zone special outfits will be worn in the order
-- the are listed here, with later outfits being worn over
-- earlier outfits (when they're being applied at the same time)
-- This allows BG-specific outfits to take priority of the generic
-- BG outfit

local Outfitter_cZoneSpecialIDs =
{
	"ArgentDawn",
	"City",
	"Battleground",
	"AV",
	"AB",
	"WSG",
	"BR",
	"Instance",
};

local Outfitter_cZoneSpecialIDMap =
{
	[Outfitter_cWesternPlaguelands] = {"ArgentDawn"},
	[Outfitter_cEasternPlaguelands] = {"ArgentDawn"},
	[Outfitter_cStratholme] = {"ArgentDawn"},
	[Outfitter_cScholomance] = {"ArgentDawn"},
	[Outfitter_cNaxxramas] = {"ArgentDawn"},
	[Outfitter_cAlteracValley] = {"Battleground", "AV"},
	[Outfitter_cArathiBasin] = {"Battleground", "AB"},
	[Outfitter_cWarsongGulch] = {"Battleground", "WSG"},
	[Outfitter_cBloodRing] = {"Battleground", "BR"},
	[Outfitter_cSunnygladeValley] = {"Battleground", "SGV"},
	[Outfitter_cIronforge] = {"City"},
	[Outfitter_cCityOfIronforge] = {"City"},
	[Outfitter_cDarnassus] = {"City"},
	[Outfitter_cStormwind] = {"City"},
	[Outfitter_cOrgrimmar] = {"City"},
	[Outfitter_cThunderBluff] = {"City"},
	[Outfitter_cUndercity] = {"City"},
	[Outfitter_cAQ20] = {"Instance"},
	[Outfitter_cAQ40] = {"Instance"},
	[Outfitter_cZG] = {"Instance"},
	[Outfitter_cES] = {"Instance"},
};

local gOutfitter_StatDistribution =
{
	DRUID =
	{
		Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 20}},
		Stamina = {Health = {Coeff = 10}},
		Intellect = {Mana = {Coeff = 15}, SpellCrit = {Coeff = 1 / 30}},
		Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
		Strength = {BlockAmount = {Coeff = 1 / 22}},
	},
	
	HUNTER =
	{
		Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 26.5}, MeleeCrit = {Coeff = 1 / 53}},
		Stamina = {Health = {Coeff = 10}},
		Intellect = {Mana = {Coeff = 15}},
		Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
		Strength = {BlockAmount = {Coeff = 1 / 22}},
	},
	
	MAGE =
	{
		Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 20}, MeleeCrit = {Coeff = 1 / 20}},
		Stamina = {Health = {Coeff = 10}},
		Intellect = {Mana = {Coeff = 15}, SpellCrit = {Coeff = 1.0 / 59.5}},
		Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
		Strength = {BlockAmount = {Coeff = 1 / 22}},
	},
	
	PALADIN =
	{
		Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 20}, MeleeCrit = {Coeff = 1 / 20}},
		Stamina = {Health = {Coeff = 10}},
		Intellect = {Mana = {Coeff = 15}, SpellCrit = {Coeff = 1.0 / 20}},
		Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
		Strength = {BlockAmount = {Coeff = 1 / 22}},
	},
	
	PRIEST =
	{
		Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 20}, MeleeCrit = {Coeff = 1 / 20}},
		Stamina = {Health = {Coeff = 10}},
		Intellect = {Mana = {Coeff = 15}, SpellCrit = {Coeff = 1.0 / 30}},
		Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
		Strength = {BlockAmount = {Coeff = 1 / 22}},
	},
	
	ROGUE =
	{
		Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 14.5}, MeleeCrit = {Coeff = 1 / 29}},
		Stamina = {Health = {Coeff = 10}},
		Intellect = {Mana = {Coeff = 15}},
		Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
		Strength = {BlockAmount = {Coeff = 1 / 22}},
	},
	
	SHAMAN =
	{
		Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 20}, MeleeCrit = {Coeff = 1 / 20}},
		Stamina = {Health = {Coeff = 10}},
		Intellect = {Mana = {Coeff = 15}, SpellCrit = {Coeff = 1.0 / 20}},
		Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
		Strength = {BlockAmount = {Coeff = 1 / 22}},
	},
	
	WARLOCK =
	{
		Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 20}, MeleeCrit = {Coeff = 1 / 20}},
		Stamina = {Health = {Coeff = 10}},
		Intellect = {Mana = {Coeff = 15}, SpellCrit = {Coeff = 1.0 / 30}},
		Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
		Strength = {BlockAmount = {Coeff = 1 / 22}},
	},
	
	WARRIOR =
	{
		Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 20}, MeleeCrit = {Coeff = 1 / 20}},
		Stamina = {Health = {Coeff = 10}},
		Intellect = {Mana = {Coeff = 15}},
		Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
		Strength = {BlockAmount = {Coeff = 1 / 22}},
	},
};

local Outfitter_cCombatEquipmentSlots =
{
	MainHandSlot = true,
	SecondaryHandSlot = true,
	RangedSlot = true,
	AmmoSlot = true,
};

local gOutfitter_OutfitStack = {};

local gOutfitter_SelectedOutfit = nil;
local gOutfitter_DisplayIsDirty = true;

local gOutfitter_CurrentZone = nil;
local gOutfitter_InCombat = false;
local gOutfitter_IsDead = false;
local gOutfitter_IsFeigning = false;

local gOutfitter_EquippedNeedsUpdate = false;
local gOutfitter_WeaponsNeedUpdate = false;
local gOutfitter_LastEquipmentUpdateTime = 0;
local Outfitter_cMinEquipmentUpdateInterval = 1.5;

local gOutfitter_CurrentOutfit = nil;
local gOutfitter_ExpectedOutfit = nil;
local gOutfitter_CurrentInventoryOutfit = nil;
local gOutfitter_EquippableItems = nil;

local gOutfitter_Initialized = false;
local gOutfitter_Suspended = false;

local Outfitter_cMaxDisplayedItems = 14;

local gOutfitter_PanelFrames =
{
	"OutfitterMainFrame",
	"OutfitterOptionsFrame",
	"OutfitterAboutFrame",
};

local gOutfitter_CurrentPanel = 0;

local	Outfitter_cShapeshiftSpecialIDs =
{
	-- Warriors
	
	[Outfitter_cBattleStance] = {ID = "Battle"},
	[Outfitter_cDefensiveStance] = {ID = "Defensive"},
	[Outfitter_cBerserkerStance] = {ID = "Berserker"},
	
	-- Druids
	
	[Outfitter_cBearForm] = {ID = "Bear"},
	[Outfitter_cCatForm] = {ID = "Cat"},
	[Outfitter_cAquaticForm] = {ID = "Aquatic"},
	[Outfitter_cTravelForm] = {ID = "Travel"},
	[Outfitter_cDireBearForm] = {ID = "Bear"},
	[Outfitter_cMoonkinForm] = {ID = "Moonkin"},
	
	-- Rogues
	
	[Outfitter_cStealth] = {ID = "Stealth"},
};

local gOutfitter_SpecialState = {};

StaticPopupDialogs["OUTFITTER_CONFIRM_DELETE"] =
{
	text = TEXT(Outfitter_cConfirmDeleteMsg),
	button1 = TEXT(DELETE),
	button2 = TEXT(CANCEL),
	OnAccept = function() Outfitter_DeleteSelectedOutfit(); end,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 1
};

StaticPopupDialogs["OUTFITTER_CONFIRM_REBUILD"] =
{
	text = TEXT(Outfitter_cConfirmRebuildMsg),
	button1 = TEXT(Outfitter_cRebuild),
	button2 = TEXT(CANCEL),
	OnAccept = function() Outfitter_RebuildSelectedOutfit(); end,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 1,
};

function Outfitter_ToggleOutfitterFrame()
	if Outfitter_IsOpen() then
		OutfitterFrame:Hide();
	else
		OutfitterFrame:Show();
	end
end

function Outfitter_IsOpen()
	return OutfitterFrame:IsVisible();
end

function Outfitter_OnLoad()
	Outfitter_RegisterEvent(this, "PLAYER_ENTERING_WORLD", Outfitter_PlayerEnteringWorld);
	Outfitter_RegisterEvent(this, "PLAYER_LEAVING_WORLD", Outfitter_PlayerLeavingWorld);
	Outfitter_RegisterEvent(this, "VARIABLES_LOADED", Outfitter_VariablesLoaded);
	
	-- For monitoring mounted, dining and shadowform states
	
	Outfitter_RegisterEvent(this, "PLAYER_AURAS_CHANGED", Outfitter_UpdateAuraStates);
	
	-- For monitoring plaguelands and battlegrounds
	
	Outfitter_RegisterEvent(this, "ZONE_CHANGED_NEW_AREA", Outfitter_UpdateZone);
	
	-- For monitoring player combat state
	
	Outfitter_RegisterEvent(this, "PLAYER_REGEN_ENABLED", Outfitter_RegenEnabled);
	Outfitter_RegisterEvent(this, "PLAYER_REGEN_DISABLED", Outfitter_RegenDisabled);
	
	-- For monitoring player dead/alive stat
	
	Outfitter_RegisterEvent(this, "PLAYER_DEAD", Outfitter_PlayerDead);
	Outfitter_RegisterEvent(this, "PLAYER_ALIVE", Outfitter_PlayerAlive);
	Outfitter_RegisterEvent(this, "PLAYER_UNGHOST", Outfitter_PlayerAlive);
	
	Outfitter_RegisterEvent(this, "UNIT_INVENTORY_CHANGED", Outfitter_InventoryChanged);
	
	-- For indicating which outfits are missing items
	
	Outfitter_RegisterEvent(this, "BAG_UPDATE", Outfitter_BagUpdate);
	Outfitter_RegisterEvent(this, "PLAYERBANKSLOTS_CHANGED", Outfitter_BankSlotsChanged);
	
	-- For monitoring bank bags
	
	Outfitter_RegisterEvent(this, "BANKFRAME_OPENED", Outfitter_BankFrameOpened);
	Outfitter_RegisterEvent(this, "BANKFRAME_CLOSED", Outfitter_BankFrameClosed);
	
	-- For unequipping the dining outfit
	
	Outfitter_RegisterEvent(this, "UNIT_HEALTH", Outfitter_UnitHealthOrManaChanged);
	Outfitter_RegisterEvent(this, "UNIT_MANA", Outfitter_UnitHealthOrManaChanged);
	
	Outfitter_SuspendEvent(this, "UNIT_HEALTH"); -- Don't actually care until the dining outfit equips
	Outfitter_SuspendEvent(this, "UNIT_MANA");
	
	-- Tabs
	
	PanelTemplates_SetNumTabs(this, table.getn(gOutfitter_PanelFrames));
	OutfitterFrame.selectedTab = gOutfitter_CurrentPanel;
	PanelTemplates_UpdateTabs(this);
	
	-- Install the /outfit command handler

	SlashCmdList["OUTFITTER"] = Outfitter_ExecuteCommand;
	
	SLASH_OUTFITTER1 = "/outfitter";
	
	-- Fake a leaving world event to suspend inventory/bag
	-- updating until loading is completed
	
	Outfitter_PlayerLeavingWorld();
end

function Outfitter_OnShow()
	Outfitter_ShowPanel(1); -- Always switch to the main view when showing the window
end

function Outfitter_OnHide()
	Outfitter_ClearSelection();
	OutfitterQuickSlots_Close();
	OutfitterFrame:Hide(); -- This seems redundant, but the OnHide handler gets called
	                       -- in response to the parent being hidden (the character window)
	                       -- so calling Hide() on the frame here ensures that when the
	                       -- character window is hidden then Outfitter won't be displayed
	                       -- next time it's opened
end

function Outfitter_OnEvent(pEvent)
	-- Ignore all events except for entering world until initialization is
	-- completed
	
	if not gOutfitter_Initialized
	and pEvent ~= "VARIABLES_LOADED" then
		if pEvent ~= Outfitter_cInitializationEvent then
			return;
		end
		
		Outfitter_Initialize();
	end
	
	--
	
	Outfitter_DispatchEvent(this, pEvent);
	Outfitter_Update(false);
end

function Outfitter_PlayerLeavingWorld()
	-- To improve load screen performance, suspend events which are
	-- fired repeatedly and rapidly during zoning
	
	gOutfitter_Suspended = true;
	
	Outfitter_SuspendEvent(OutfitterFrame, "BAG_UPDATE");
	Outfitter_SuspendEvent(OutfitterFrame, "UNIT_INVENTORY_CHANGED");
	Outfitter_SuspendEvent(OutfitterFrame, "UPDATE_INVENTORY_ALERTS");
	Outfitter_SuspendEvent(OutfitterFrame, "SPELLS_CHANGED");
	Outfitter_SuspendEvent(OutfitterFrame, "PLAYER_AURAS_CHANGED");
	Outfitter_SuspendEvent(OutfitterFrame, "PLAYERBANKSLOTS_CHANGED");
end

function Outfitter_PlayerEnteringWorld()
	OutfitterItemList_FlushEquippableItems();
	
	Outfitter_RegenEnabled();
	Outfitter_UpdateAuraStates();
	Outfitter_SetSpecialOutfitEnabled("Riding", false);
	
	Outfitter_ResumeLoadScreenEvents();
end

function Outfitter_ResumeLoadScreenEvents()
	if gOutfitter_Suspended then
		-- To improve load screen performance, suspend events which are
		-- fired repeatedly and rapidly during zoning
		
		gOutfitter_Suspended = false;

		Outfitter_ResumeEvent(OutfitterFrame, "BAG_UPDATE");
		Outfitter_ResumeEvent(OutfitterFrame, "UNIT_INVENTORY_CHANGED");
		Outfitter_ResumeEvent(OutfitterFrame, "UPDATE_INVENTORY_ALERTS");
		Outfitter_ResumeEvent(OutfitterFrame, "SPELLS_CHANGED");
		Outfitter_ResumeEvent(OutfitterFrame, "PLAYER_AURAS_CHANGED");
		Outfitter_ResumeEvent(OutfitterFrame, "PLAYERBANKSLOTS_CHANGED");
		
		Outfitter_InventoryChanged2();
	end
end

function Outfitter_VariablesLoaded()
	-- Change the initialization event to PLAYER_ALIVE if this is the first use
	-- This will ensure that the bags and inventory info has been loaded before
	-- trying to generate the automatic outfits
	
	if not gOutfitter_Settings
	or not gOutfitter_Settings.Outfits then
		Outfitter_cInitializationEvent = "PLAYER_ALIVE";
	end
end

function Outfitter_BankSlotsChanged()
	OutfitterItemList_FlushBagFromEquippableItems(-1);
	
	for vBagIndex = NUM_BAG_SLOTS + 1, NUM_BAG_SLOTS + NUM_BANKBAGSLOTS do
		OutfitterItemList_FlushBagFromEquippableItems(vBagIndex);
	end
	
	-- Force the bank bags to update since they now exist
	
	if gOutfitter_EquippableItems then
		gOutfitter_EquippableItems.NeedsUpdate = true;
	end
	
	gOutfitter_DisplayIsDirty = true;
	Outfitter_Update(false);
end

function Outfitter_BagUpdate()
	local	vBagIndex = arg1;
	
	OutfitterItemList_FlushBagFromEquippableItems(vBagIndex);

	-- This is a messy hack to ensure the database gets updated properly
	-- after an upgrade.  WoW doesn't always have the players items
	-- loaded on PLAYER_ENTERING_WORLD so once the bag update fires
	-- we check the databases again if necessary
	
	if gOutfitter_NeedItemCodesUpdated then
		gOutfitter_NeedItemCodesUpdated = gOutfitter_NeedItemCodesUpdated - 1;
		
		if gOutfitter_NeedItemCodesUpdated == 0 then
			gOutfitter_NeedItemCodesUpdated = nil;
		end
		
		if Outfitter_UpdateDatabaseItemCodes() then
			gOutfitter_NeedItemCodesUpdated = nil;
		end
	end
	
	--
	
	gOutfitter_DisplayIsDirty = true;
	Outfitter_Update(false);
end

local	gOutfitter_OutfitEvents = {};

function Outfitter_RegisterOutfitEvent(pEvent, pFunction)
	local	vHandlers = gOutfitter_OutfitEvents[pEvent];
	
	if not vHandlers then
		vHandlers = {};
		gOutfitter_OutfitEvents[pEvent] = vHandlers;
	end
	
	table.insert(vHandlers, pFunction);
end

function Outfitter_UnregisterOutfitEvent(pEvent, pFunction)
	local	vHandlers = gOutfitter_OutfitEvents[pEvent];
	
	if not vHandlers then
		return;
	end
	
	for vIndex, vFunction in vHandlers do
		if vFunction == pFunction then
			table.remove(vHandlers, vIndex);
			return;
		end
	end
end

function Outfitter_DispatchOutfitEvent(pEvent, pParameter1, pParameter2)
	-- Don't send out events until we're initialized
	
	if not gOutfitter_Initialized then
		return;
	end
	
	--
	
	OutfitterMinimapDropDown_OutfitEvent(pEvent, pParameter1, pParameter2);
	
	local	vHandlers = gOutfitter_OutfitEvents[pEvent];
	
	if not vHandlers then
		return;
	end
	
	for _, vFunction in vHandlers do
		-- Call in protected mode so that if they fail it doesn't
		-- screw up Outfitter or other addons wishing to be notified
		
		pcall(vFunction, pEvent, pParameter1, pParameter2);
	end
end

function Outfitter_BankFrameOpened()
	gOutfitter_BankFrameOpened = true;
	gOutfitter_DisplayIsDirty = true;
	
	Outfitter_BankSlotsChanged();
	
	Outfitter_Update(false);
end

function Outfitter_BankFrameClosed()
	gOutfitter_BankFrameOpened = false;
	
	Outfitter_BankSlotsChanged();
	
	gOutfitter_DisplayIsDirty = true;
	Outfitter_Update(false);
end

function Outfitter_RegenEnabled(pEvent)
	gOutfitter_InCombat = false;
end

function Outfitter_RegenDisabled(pEvent)
	gOutfitter_InCombat = true;
end

function Outfitter_PlayerDead(pEvent)
	gOutfitter_IsDead = true;
end

function Outfitter_PlayerAlive(pEvent)
	if not UnitIsDeadOrGhost("player") then
		gOutfitter_IsDead = false;
	end
end

function Outfitter_UnitHealthOrManaChanged()
	if arg1 ~= "player" then
		return;
	end
	
	local	vHealth = UnitHealth("player");
	local	vMaxHealth = UnitHealthMax("player");
	local	vFullHealth = false;
	local	vFullMana = false;
	
	if vHealth > (vMaxHealth * 0.99) then
		vFullHealth = true;
	end
	
	if UnitPowerType("player") == 0 then
		local	vMana = UnitMana("player");
		local	vMaxMana = UnitManaMax("player");
		
		if vMana > (vMaxMana * 0.99) then
			vFullMana = true;
		end
	else
		vFullMana = true;
	end
	
	if vFullHealth and vFullMana then
		Outfitter_SetSpecialOutfitEnabled("Dining", false);
	end
end

function Outfitter_InventoryChanged(pEvent)
	if arg1 ~= "player" then
		return;
	end
	
	Outfitter_InventoryChanged2();
end

function Outfitter_InventoryChanged2()
	OutfitterItemList_FlushEquippableItems(); -- Flush everything because the game sends bag update
	                                          -- events for an item after it's already appeared in the inventory.  This
	                                          -- creates a brief situation in which the item appears to be both in the
	                                          -- bank and in the inventory which causes various problems for Outfitter
	                                          -- when checking for items.
	
	gOutfitter_DisplayIsDirty = true; -- Update the list so the checkboxes reflect the current state
	
	local	vNewItemsOutfit, vCurrentOutfit = Outfitter_GetNewItemsOutfit(gOutfitter_CurrentOutfit);
	
	if vNewItemsOutfit then
		if not gOutfitter_Settings.Options.DisableAutoVisibility then
			-- If the cloak is changing, remember the visibility for the old one and set
			-- it for the new one
			
			if vNewItemsOutfit.Items.BackSlot then
				if gOutfitter_CurrentOutfit.Items.BackSlot.Code then
					if ShowingCloak() then
						gOutfitter_Settings.HideCloak[gOutfitter_CurrentOutfit.Items.BackSlot.Code] = false;
					else
						gOutfitter_Settings.HideCloak[gOutfitter_CurrentOutfit.Items.BackSlot.Code] = true;
					end
				end
				
				if gOutfitter_Settings.HideCloak[vNewItemsOutfit.Items.BackSlot.Code] ~= nil then
					if gOutfitter_Settings.HideCloak[vNewItemsOutfit.Items.BackSlot.Code] then
						ShowCloak(0);
					else
						ShowCloak(1);
					end
				end
			end
			
			-- If the helm is changing, remember the visibility for the old one and set
			-- it for the new one
			
			if vNewItemsOutfit.Items.HeadSlot then
				if gOutfitter_CurrentOutfit.Items.HeadSlot.Code then
					if ShowingHelm() then
						gOutfitter_Settings.HideHelm[gOutfitter_CurrentOutfit.Items.HeadSlot.Code] = false;
					else
						gOutfitter_Settings.HideHelm[gOutfitter_CurrentOutfit.Items.HeadSlot.Code] = true;
					end
				end
				
				if gOutfitter_Settings.HideHelm[vNewItemsOutfit.Items.HeadSlot.Code] ~= nil then
					if gOutfitter_Settings.HideHelm[vNewItemsOutfit.Items.HeadSlot.Code] then
						ShowHelm(0);
					else
						ShowHelm(1);
					end
				end
			end
		end
		
		-- Save the new outfit
		
		gOutfitter_CurrentOutfit = vCurrentOutfit;
		
		-- Close QuickSlots if there's an inventory change (except if the only
		-- change is with the ammo slots)
		
		if OutfitterQuickSlots.SlotName ~= "AmmoSlot"
		or not Outfitter_OutfitIsAmmoOnly(vNewItemsOutfit) then
			OutfitterQuickSlots_Close();
		end
		
		-- Update the selected outfit or temporary outfit
		
		Outfitter_SubtractOutfit(vNewItemsOutfit, gOutfitter_ExpectedOutfit);
		
		if gOutfitter_SelectedOutfit then
			Outfitter_UpdateOutfitFromInventory(gOutfitter_SelectedOutfit, vNewItemsOutfit);
		else
			Outfitter_UpdateTemporaryOutfit(vNewItemsOutfit);
		end
	end
	
	Outfitter_Update(true);
end

function Outfitter_OutfitIsAmmoOnly(pOutfit)
	local	vHasAmmoItem = false;
	
	for vInventorySlot, vItem in pOutfit.Items do
		if vInventorySlot ~= "AmmoSlot" then
			return false;
		else
			vHasAmmoItem = true;
		end
	end
	
	return vHasAmmoItem;
end

function Outfitter_ExecuteCommand(pCommand)
	vCommands =
	{
		wear = {useOutfit = true, func = Outfitter_WearOutfit},
		unwear = {useOutfit = true, func = Outfitter_RemoveOutfit},
		toggle = {useOutfit = true, func = Outfitter_ToggleOutfit},
		summary = {useOutfit = false, func = Outfitter_OutfitSummary},
	}

	local	vStartIndex, vEndIndex, vCommand, vParameter = string.find(pCommand, "(%w+) ?(.*)");
	
	if not vCommand then
		Outfitter_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE.."/outfitter wear <outfit name>"..NORMAL_FONT_COLOR_CODE..": Wear an outfit");
		Outfitter_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE.."/outfitter unwear <outfit name>"..NORMAL_FONT_COLOR_CODE..": Remove an outfit");
		Outfitter_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE.."/outfitter toggle <outfit name>"..NORMAL_FONT_COLOR_CODE..": Wears or removes an outfit");
		return;
	end
	
	vCommand = strlower(vCommand);
	
	local	vCommandInfo = vCommands[vCommand];
	
	if not vCommandInfo then
		Outfitter_ErrorMessage("Outfitter: Expected command");
		return;
	end
	
	local	vOutfit = nil;
	local	vCategoryID = nil;
	
	if vCommandInfo.useOutfit then
		if not vParameter then
			Outfitter_ErrorMessage("Outfitter: Expected outfit name for "..vCommand.." command");
			return;
		end
		
		vOutfit, vCategoryID = Outfitter_FindOutfitByName(vParameter);
		
		if not vOutfit then
			Outfitter_ErrorMessage("Outfitter: Couldn't find outfit named "..vParameter);
			return;
		end
		
		vCommandInfo.func(vOutfit, vCategoryID);
	else
		vCommandInfo.func();
	end
end

function Outfitter_AskRebuildOutfit(pOutfit, pCategoryID)
	gOutfitter_OutfitToRebuild = pOutfit;
	gOutfitter_OutfitCategoryToRebuild = pCategoryID;
	
	StaticPopup_Show("OUTFITTER_CONFIRM_REBUILD", gOutfitter_OutfitToRebuild.Name);
end

function Outfitter_RebuildSelectedOutfit()
	if not gOutfitter_OutfitToRebuild then
		return;
	end
	
	local	vOutfit = Outfitter_GenerateSmartOutfit("temp", gOutfitter_OutfitToRebuild.StatID, OutfitterItemList_GetEquippableItems(true));
	
	if vOutfit then
		gOutfitter_OutfitToRebuild.Items = vOutfit.Items;
		Outfitter_UpdateOutfitCategory(gOutfitter_OutfitToRebuild);
		Outfitter_WearOutfit(gOutfitter_OutfitToRebuild, gOutfitter_OutfitCategoryToRebuild);
		Outfitter_Update(true);
	end
	
	gOutfitter_OutfitToRebuild = nil;
	gOutfitter_OutfitCategoryToRebuild = nil;
end

function Outfitter_AskDeleteOutfit(pOutfit)
	gOutfitter_OutfitToDelete = pOutfit;
	StaticPopup_Show("OUTFITTER_CONFIRM_DELETE", gOutfitter_OutfitToDelete.Name);
end

function Outfitter_DeleteSelectedOutfit()
	if not gOutfitter_OutfitToDelete then
		return;
	end
	
	Outfitter_DeleteOutfit(gOutfitter_OutfitToDelete);
	
	Outfitter_Update(true);
end

function Outfitter_ShowPanel(pPanelIndex)
	Outfitter_CancelDialogs(); -- Force any dialogs to close if they're open
	
	if gOutfitter_CurrentPanel > 0
	and gOutfitter_CurrentPanel ~= pPanelIndex then
		Outfitter_HidePanel(gOutfitter_CurrentPanel);
	end
	
	-- NOTE: Don't check for redundant calls since this function
	-- will be called to reset the field values as well as to 
	-- actually show the panel when it's hidden
	
	gOutfitter_CurrentPanel = pPanelIndex;
	
	getglobal(gOutfitter_PanelFrames[pPanelIndex]):Show();
	
	PanelTemplates_SetTab(OutfitterFrame, pPanelIndex);
	
	-- Update the control values
	
	if pPanelIndex == 1 then
		-- Main panel
		
	elseif pPanelIndex == 2 then
		-- Options panel
		
	elseif pPanelIndex == 3 then
		-- About panel
		
	else
		Outfitter_ErrorMessage("Outfitter: Unknown index ("..pPanelIndex..") in ShowPanel()");
	end
	
	Outfitter_Update(false);
end

function Outfitter_HidePanel(pPanelIndex)
	if gOutfitter_CurrentPanel ~= pPanelIndex then
		return;
	end
	
	getglobal(gOutfitter_PanelFrames[pPanelIndex]):Hide();
	gOutfitter_CurrentPanel = 0;
end

function Outfitter_CancelDialogs()
end

function OutfitterItemDropDown_OnLoad()
	UIDropDownMenu_SetAnchor(0, 0, this, "TOPLEFT", this:GetName(), "CENTER");
	UIDropDownMenu_Initialize(this, OutfitterItemDropDown_Initialize);
	--UIDropDownMenu_Refresh(this); -- Don't refresh on menus which don't have a text portion
	
	this:SetHeight(this.SavedHeight);
end

function Outfitter_AddDividerMenuItem()
	UIDropDownMenu_AddButton({text = " ", notCheckable = true, notClickable = true});
end

function Outfitter_AddCategoryMenuItem(pName)
	UIDropDownMenu_AddButton({text = pName, notCheckable = true, notClickable = true});
end

function Outfitter_AddMenuItem(pFrame, pName, pValue, pChecked, pLevel, pColor, pDisabled)
	if not pColor then
		pColor = NORMAL_FONT_COLOR;
	end
	
	UIDropDownMenu_AddButton({text = pName, value = pValue, owner = pFrame, checked = pChecked, func = OutfitterDropDown_OnClick2, textR = pColor.r, textG = pColor.g, textB = pColor.b, disabled = pDisabled}, pLevel);
end

function Outfitter_AddSubmenuItem(pFrame, pName, pValue)
	UIDropDownMenu_AddButton({text = pName, owner = pFrame, hasArrow = 1, value = pValue, textR = NORMAL_FONT_COLOR.r, textG = NORMAL_FONT_COLOR.g, textB = NORMAL_FONT_COLOR.b});
end

function OutfitterItemDropDown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	local	vItem = vFrame:GetParent():GetParent();
	local	vOutfit, vCategoryID = Outfitter_GetOutfitFromListItem(vItem);
	
	if not vOutfit then
		return;
	end
	
	if UIDROPDOWNMENU_MENU_LEVEL == 1 then
		local	vIsSpecialOutfit = vCategoryID == "Special";
		
		Outfitter_AddCategoryMenuItem(vOutfit.Name);
		
		if vIsSpecialOutfit then
			Outfitter_AddMenuItem(vFrame, Outfitter_cDisableOutfit, "DISABLE", vOutfit.Disabled);
			Outfitter_AddMenuItem(vFrame, Outfitter_cDisableOutfitInBG, "BGDISABLE", vOutfit.BGDisabled);
			--hax
			Outfitter_AddMenuItem(vFrame, Outfitter_cDisableOutfitInInstance, "INSTDISABLE", vOutfit.InstDisabled);
		else
			Outfitter_AddMenuItem(vFrame, PET_RENAME, "RENAME");
		end
		
		if not vIsSpecialOutfit
		and vOutfit.StatID then
			local	vStatName = Outfitter_GetStatIDName(vOutfit.StatID);
			
			if vStatName then
				Outfitter_AddMenuItem(vFrame, format(Outfitter_cRebuildOutfitFormat, vStatName), "REBUILD");
			end
		end
		
		Outfitter_AddSubmenuItem(vFrame, Outfitter_cKeyBinding, "BINDING");
		
		if not vIsSpecialOutfit then
			Outfitter_AddMenuItem(vFrame, DELETE, "DELETE");
		end
		
		Outfitter_AddCategoryMenuItem(Outfitter_cBankCategoryTitle);
		Outfitter_AddMenuItem(vFrame, Outfitter_cDepositToBank, "DEPOSIT", nil, nil, nil, not gOutfitter_BankFrameOpened);
		Outfitter_AddMenuItem(vFrame, Outfitter_cDepositUniqueToBank, "DEPOSITUNIQUE", nil, nil, nil, not gOutfitter_BankFrameOpened);
		Outfitter_AddMenuItem(vFrame, Outfitter_cWithdrawFromBank, "WITHDRAW", nil, nil, nil, not gOutfitter_BankFrameOpened);
		
		if not vIsSpecialOutfit
		and vCategoryID ~= "Complete" then
			Outfitter_AddCategoryMenuItem(Outfitter_cOutfitCategoryTitle);
			Outfitter_AddMenuItem(vFrame, Outfitter_cPartialOutfits, "PARTIAL", vCategoryID == "Partial");
			Outfitter_AddMenuItem(vFrame, Outfitter_cAccessoryOutfits, "ACCESSORY", vCategoryID == "Accessory");
		end
		
	elseif UIDROPDOWNMENU_MENU_LEVEL == 2 then
		if UIDROPDOWNMENU_MENU_VALUE == "BINDING" then
			for vIndex = 1, 10 do
				Outfitter_AddMenuItem(vFrame, getglobal("BINDING_NAME_OUTFITTER_OUTFIT"..vIndex), "BINDING"..vIndex, vOutfit.BindingIndex == vIndex, UIDROPDOWNMENU_MENU_LEVEL);
			end
		end
	end
	
	vFrame:SetHeight(vFrame.SavedHeight);
end

function Outfitter_SetShowMinimapButton(pShowButton)
	gOutfitter_Settings.Options.HideMinimapButton = not pShowButton;
	
	if gOutfitter_Settings.Options.HideMinimapButton then
		OutfitterMinimapButton:Hide();
	else
		OutfitterMinimapButton:Show();
	end
	
	Outfitter_Update(false);
end

function Outfitter_SetRememberVisibility(pRememberVisibility)
	gOutfitter_Settings.Options.DisableAutoVisibility = not pRememberVisibility;
	
	Outfitter_Update(false);
end

function Outfitter_SetShowHotkeyMessages(pShowHotkeyMessages)
	gOutfitter_Settings.Options.DisableHotkeyMessages = not pShowHotkeyMessages;
	
	Outfitter_Update(false);
end

function OutfitterMinimapDropDown_OnLoad()
	UIDropDownMenu_SetAnchor(3, -7, this, "TOPRIGHT", this:GetName(), "TOPLEFT");
	UIDropDownMenu_Initialize(this, OutfitterMinimapDropDown_Initialize);
	--UIDropDownMenu_Refresh(this); -- Don't refresh on menus which don't have a text portion
	
	Outfitter_RegisterOutfitEvent("WEAR_OUTFIT", OutfitterMinimapDropDown_OutfitEvent);
	Outfitter_RegisterOutfitEvent("UNWEAR_OUTFIT", OutfitterMinimapDropDown_OutfitEvent);
end

function OutfitterMinimapDropDown_OutfitEvent(pEvent, pParameter1, pParameter2)
	if UIDROPDOWNMENU_OPEN_MENU ~= "OutfitterMinimapButton" then
		return;
	end
	
	UIDropDownMenu_Initialize(OutfitterMinimapButton, OutfitterMinimapDropDown_Initialize);
end

function OutfitterMinimapDropDown_AdjustScreenPosition(pMenu)
	local	vListFrame = getglobal("DropDownList1");
	
	if not vListFrame:IsVisible() then
		return;
	end
	
	local	vCenterX, vCenterY = pMenu:GetCenter();
	local	vScreenWidth, vScreenHeight = GetScreenWidth(), GetScreenHeight();
	
	local	vAnchor;
	local	vOffsetX, vOffsetY;
	
	if vCenterY < vScreenHeight / 2 then
		vAnchor = "BOTTOM";
		vOffsetY = -8;
	else
		vAnchor = "TOP";
		vOffsetY = -17;
	end
	
	if vCenterX < vScreenWidth / 2 then
		vAnchor = vAnchor.."LEFT";
		vOffsetX = 21;
	else
		vAnchor = vAnchor.."RIGHT";
		vOffsetX = 3;
	end
	
	vListFrame:ClearAllPoints();
	vListFrame:SetPoint(vAnchor, pMenu.relativeTo, pMenu.relativePoint, vOffsetX, vOffsetY);
end

function Outfitter_OutfitIsVisible(pOutfit)
	return not pOutfit.Disabled
	   and not Outfitter_IsEmptyOutfit(pOutfit);
end

function Outfitter_HasVisibleOutfits(pOutfits)
	if not pOutfits then
		return false;
	end
	
	for vIndex, vOutfit in pOutfits do
		if Outfitter_OutfitIsVisible(vOutfit) then	
			return true;
		end
	end
	
	return false;
end

function OutfitterMinimapDropDown_Initialize()
	-- Just return if not initialized yet
	
	if not gOutfitter_Initialized then
		return;
	end
	
	--
	
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	
	Outfitter_AddCategoryMenuItem(Outfitter_cTitleVersion);
	Outfitter_AddMenuItem(vFrame, Outfitter_cOpenOutfitter, 0);
	
	OutfitterMinimapDropDown_InitializeOutfitList();
end

function Outfitter_GetCategoryOrder()
	return gOutfitter_cCategoryOrder;
end

function Outfitter_GetOutfitsByCategoryID(pCategoryID)
	return gOutfitter_Settings.Outfits[pCategoryID];
end

function OutfitterMinimapDropDown_InitializeOutfitList()
	-- Just return if not initialized yet
	
	if not gOutfitter_Initialized then
		return;
	end
	
	--
	
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	local	vEquippableItems = OutfitterItemList_GetEquippableItems();
	local	vCategoryOrder = Outfitter_GetCategoryOrder();
	
	for vCategoryIndex, vCategoryID in vCategoryOrder do
		local	vCategoryName = getglobal("Outfitter_c"..vCategoryID.."Outfits");
		local	vOutfits = Outfitter_GetOutfitsByCategoryID(vCategoryID);
		
		if Outfitter_HasVisibleOutfits(vOutfits) then
			Outfitter_AddCategoryMenuItem(vCategoryName);
			
			for vIndex, vOutfit in vOutfits do
				if Outfitter_OutfitIsVisible(vOutfit) then
					local	vWearingOutfit = Outfitter_WearingOutfit(vOutfit);
					local	vMissingItems, vBankedItems = OutfitterItemList_GetMissingItems(vEquippableItems, vOutfit);
					local	vItemColor = NORMAL_FONT_COLOR;
					
					if vMissingItems then
						vItemColor = RED_FONT_COLOR;
					elseif vBankedItems then
						vItemColor = BANKED_FONT_COLOR;
					end
					
					Outfitter_AddMenuItem(vFrame, vOutfit.Name, {CategoryID = vCategoryID, Index = vIndex}, vWearingOutfit, nil, vItemColor);
				end
			end
		end
	end
end

function OutfitterDropDown_OnClick()
	UIDropDownMenu_SetSelectedValue(this.owner, this.value);
	OutfitterDropDown_OnClick2();
end

function OutfitterDropDown_OnClick2()
	if this.owner.ChangedValueFunc then
		this.owner.ChangedValueFunc(this.owner, this.value);
	end
	
	CloseDropDownMenus();
end

function OutfitterItem_SetTextColor(pItem, pRed, pGreen, pBlue)
	local	vItemNameField;
	
	if pItem.isCategory then
		vItemNameField = getglobal(pItem:GetName().."CategoryName");
	else
		vItemNameField = getglobal(pItem:GetName().."OutfitName");
	end
	
	vItemNameField:SetTextColor(pRed, pGreen, pBlue);
end

Outfitter_cCategoryDescriptions =
{
	Complete = Outfitter_cCompleteCategoryDescripton,
	Partial = Outfitter_cPartialCategoryDescription,
	Accessory = Outfitter_cAccessoryCategoryDescription,
	Special = Outfitter_cSpecialCategoryDescription,
	OddsNEnds = Outfitter_cOddsNEndsCategoryDescription,
};

Outfitter_cMissingItemsSeparator = ", ";

function Outfitter_GenerateItemListString(pLabel, pListColorCode, pItems)
	local	vItemList = nil;

	for vIndex, vOutfitItem in pItems do
		if not vItemList then
			vItemList = HIGHLIGHT_FONT_COLOR_CODE..pLabel..pListColorCode..vOutfitItem.Name;
		else
			vItemList = vItemList..Outfitter_cMissingItemsSeparator..vOutfitItem.Name;
		end
	end
	
	return vItemList;
end

function OutfitterItem_OnEnter(pItem)
	OutfitterItem_SetTextColor(pItem, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b);
	
	if pItem.isCategory then
		local	vDescription = Outfitter_cCategoryDescriptions[pItem.categoryID];
		
		if vDescription then
			local	vCategoryName = getglobal("Outfitter_c"..pItem.categoryID.."Outfits");
			
			GameTooltip_AddNewbieTip(vCategoryName, 1.0, 1.0, 1.0, vDescription, 1);
		end
		
		ResetCursor();
	elseif pItem.isOutfitItem then
		local	vHasCooldown, vRepairCost;
		
		GameTooltip:SetOwner(pItem, "ANCHOR_TOP");
		
		if pItem.outfitItem.Location.SlotName then
			if not pItem.outfitItem.Location.SlotID then
				local	vSlotID, vEmptySlotTexture = GetInventorySlotInfo(pItem.outfitItem.Location.SlotName);
				
				pItem.outfitItem.Location.SlotID = vSlotID;
			end
			
			GameTooltip:SetInventoryItem("player", pItem.outfitItem.Location.SlotID);
		else
			vHasCooldown, vRepairCost = GameTooltip:SetBagItem(pItem.outfitItem.Location.BagIndex, pItem.outfitItem.Location.BagSlotIndex);
		end
		
		GameTooltip:Show();

		if InRepairMode() and (vRepairCost and vRepairCost > 0) then
			GameTooltip:AddLine(TEXT(REPAIR_COST), "", 1, 1, 1);
			SetTooltipMoney(GameTooltip, vRepairCost);
			GameTooltip:Show();
		elseif MerchantFrame:IsShown() and MerchantFrame.selectedTab == 1 then
			if pItem.outfitItem.Location.BagIndex then
				ShowContainerSellCursor(pItem.outfitItem.Location.BagIndex, pItem.outfitItem.Location.BagSlotIndex);
			end
		else
			ResetCursor();
		end
	else
		local	vOutfit = Outfitter_GetOutfitFromListItem(pItem);
		
		if pItem.MissingItems
		or pItem.BankedItems then
			GameTooltip:SetOwner(pItem, "ANCHOR_LEFT");
			
			GameTooltip:AddLine(vOutfit.Name);
			
			if pItem.MissingItems then
				local	vItemList = Outfitter_GenerateItemListString(Outfitter_cMissingItemsLabel, RED_FONT_COLOR_CODE, pItem.MissingItems);
				GameTooltip:AddLine(vItemList, RED_FONT_COLOR.r, RED_FONT_COLOR.g, RED_FONT_COLOR.b, true);
			end
			
			if pItem.BankedItems then
				local	vItemList = Outfitter_GenerateItemListString(Outfitter_cBankedItemsLabel, BANKED_FONT_COLOR_CODE, pItem.BankedItems);
				GameTooltip:AddLine(vItemList, RED_FONT_COLOR.r, RED_FONT_COLOR.g, RED_FONT_COLOR.b, true);
			end
			
			GameTooltip:Show();
		elseif vOutfit.SpecialID then
			local	vDescription = Outfitter_cSpecialOutfitDescriptions[vOutfit.SpecialID];
			
			if vDescription then
				GameTooltip_AddNewbieTip(vOutfit.Name, 1.0, 1.0, 1.0, vDescription, 1);
			end
		end
		
		ResetCursor();
	end
end

function OutfitterItem_OnLeave(pItem)
	if pItem.isCategory then
		OutfitterItem_SetTextColor(pItem, 1, 1, 1);
	else
		OutfitterItem_SetTextColor(pItem, pItem.DefaultColor.r, pItem.DefaultColor.g, pItem.DefaultColor.b);
	end
	
	GameTooltip:Hide();
end

function OutfitterItem_OnClick(pItem, pButton, pIgnoreModifiers)
	if pItem.isCategory then
		local	vCategoryOutfits = gOutfitter_Settings.Outfits[pItem.categoryID];
		
		gOutfitter_Collapsed[pItem.categoryID] = not gOutfitter_Collapsed[pItem.categoryID];
		gOutfitter_DisplayIsDirty = true;
	elseif pItem.isOutfitItem then
		if pButton == "LeftButton" then
			Outfitter_PickupItemLocation(pItem.outfitItem.Location);
			StackSplitFrame:Hide();
		else
			if MerchantFrame:IsShown() and MerchantFrame.selectedTab == 2 then
				-- Don't sell the item if the buyback tab is selected
				return;
			else
				if pItem.outfitItem.Location.BagIndex then
					UseContainerItem(pItem.outfitItem.Location.BagIndex, pItem.outfitItem.Location.BagSlotIndex);
					StackSplitFrame:Hide();
				end
			end
		end
	else
		local	vOutfit = Outfitter_GetOutfitFromListItem(pItem);
		
		if not vOutfit then
			-- Error: outfit not found
			return;
		end
		
		vOutfit.Disabled = nil;
		Outfitter_WearOutfit(vOutfit, pItem.categoryID);
	end
	
	Outfitter_Update(true);
end

function OutfitterItem_CheckboxClicked(pItem)
	if pItem.isCategory then
		return;
	end
	
	local	vOutfits = gOutfitter_Settings.Outfits[pItem.categoryID];
	
	if not vOutfits then
		-- Error: outfit category not found
		return;
	end
	
	local	vOutfit = vOutfits[pItem.outfitIndex];
	
	if not vOutfit then
		-- Error: outfit not found
		return;
	end
	
	local	vCheckbox = getglobal(pItem:GetName().."OutfitSelected");
	
	if vCheckbox:GetChecked() then
		vOutfit.Disabled = nil;
		Outfitter_WearOutfit(vOutfit, pItem.categoryID);
	else
		Outfitter_RemoveOutfit(vOutfit);
	end
	
	Outfitter_Update(true);
end

function OutfitterItem_SetToOutfit(pItemIndex, pOutfit, pCategoryID, pOutfitIndex, pEquippableItems)
	local	vItemName = "OutfitterItem"..pItemIndex;
	local	vItem = getglobal(vItemName);
	local	vOutfitFrameName = vItemName.."Outfit";
	local	vOutfitFrame = getglobal(vOutfitFrameName);
	local	vItemFrame = getglobal(vItemName.."Item");
	local	vCategoryFrame = getglobal(vItemName.."Category");
	local	vMissingItems, vBankedItems = OutfitterItemList_GetMissingItems(pEquippableItems, pOutfit);
	
	vOutfitFrame:Show();
	vCategoryFrame:Hide();
	vItemFrame:Hide();
	
	local	vItemSelectedCheckmark = getglobal(vOutfitFrameName.."Selected");
	local	vItemNameField = getglobal(vOutfitFrameName.."Name");
	local	vItemMenu = getglobal(vOutfitFrameName.."Menu");
	
	vItemSelectedCheckmark:Show();
	
	if Outfitter_WearingOutfit(pOutfit) then
		vItemSelectedCheckmark:SetChecked(true);
	else
		vItemSelectedCheckmark:SetChecked(nil);
	end
	
	vItem.MissingItems = vMissingItems;
	vItem.BankedItems = vBankedItems;
	
	if pOutfit.Disabled then
		vItemNameField:SetText(format(Outfitter_cDisabledOutfitName, pOutfit.Name));
		vItem.DefaultColor = GRAY_FONT_COLOR;
	else
		vItemNameField:SetText(pOutfit.Name);
		if vMissingItems then
			vItem.DefaultColor = RED_FONT_COLOR;
		elseif vBankedItems then
			vItem.DefaultColor = BANKED_FONT_COLOR;
		else
			vItem.DefaultColor = NORMAL_FONT_COLOR;
		end
	end
	
	vItemNameField:SetTextColor(vItem.DefaultColor.r, vItem.DefaultColor.g, vItem.DefaultColor.b);
	
	vItemMenu:Show();
	
	vItem.isCategory = false;
	vItem.isOutfitItem = false;
	vItem.outfitItem = nil;
	vItem.categoryID = pCategoryID;
	vItem.outfitIndex = pOutfitIndex;
	
	vItem:Show();
	
	-- Update the highlighting
	
	if gOutfitter_SelectedOutfit == pOutfit then
		OutfitterMainFrameHighlight:SetPoint("TOPLEFT", vItem, "TOPLEFT", 0, 0);
		OutfitterMainFrameHighlight:Show();
	end
end

function OutfitterItem_SetToItem(pItemIndex, pOutfitItem)
	local	vItemName = "OutfitterItem"..pItemIndex;
	local	vItem = getglobal(vItemName);
	local	vCategoryFrameName = vItemName.."Category";
	local	vItemFrameName = vItemName.."Item";
	local	vItemFrame = getglobal(vItemFrameName);
	local	vOutfitFrame = getglobal(vItemName.."Outfit");
	local	vCategoryFrame = getglobal(vCategoryFrameName);
	
	vItem.isOutfitItem = true;
	vItem.isCategory = false;
	vItem.outfitItem = pOutfitItem;
	
	vItemFrame:Show();
	vOutfitFrame:Hide();
	vCategoryFrame:Hide();

	local	vItemNameField = getglobal(vItemFrameName.."Name");
	local	vItemIcon = getglobal(vItemFrameName.."Icon");
	
	vItemNameField:SetText(pOutfitItem.Name);
	
	if pOutfitItem.Quality then
		vItem.DefaultColor = ITEM_QUALITY_COLORS[pOutfitItem.Quality];
	else
		vItem.DefaultColor = GRAY_FONT_COLOR;
	end
	
	if pOutfitItem.Texture then
		vItemIcon:SetTexture(pOutfitItem.Texture);
		vItemIcon:Show();
	else
		vItemIcon:Hide();
	end
	
	vItemNameField:SetTextColor(vItem.DefaultColor.r, vItem.DefaultColor.g, vItem.DefaultColor.b);
	
	vItem:Show();
end

function OutfitterItem_SetToCategory(pItemIndex, pCategoryID)
	local	vCategoryName = getglobal("Outfitter_c"..pCategoryID.."Outfits");
	local	vItemName = "OutfitterItem"..pItemIndex;
	local	vItem = getglobal(vItemName);
	local	vCategoryFrameName = vItemName.."Category";
	local	vOutfitFrame = getglobal(vItemName.."Outfit");
	local	vItemFrame = getglobal(vItemName.."Item");
	local	vCategoryFrame = getglobal(vCategoryFrameName);
	
	vOutfitFrame:Hide();
	vCategoryFrame:Show();
	vItemFrame:Hide();
	
	local	vItemNameField = getglobal(vCategoryFrameName.."Name");
	local	vExpandButton = getglobal(vCategoryFrameName.."Expand");
	
	vItem.MissingItems = nil;
	vItem.BankedItems = nil;
	
	if gOutfitter_Collapsed[pCategoryID] then
		vExpandButton:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-Up"); 
	else
		vExpandButton:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-Up");
	end
	
	vItemNameField:SetText(vCategoryName);
	
	vItem.isCategory = true;
	vItem.isOutfitItem = false;
	vItem.outfitItem = nil;
	vItem.categoryID = pCategoryID;
	
	vItem:Show();
end

function Outfitter_AddOutfitsToList(pOutfits, pCategoryID, pItemIndex, pFirstItemIndex, pEquippableItems)
	local	vOutfits = pOutfits[pCategoryID];
	local	vItemIndex = pItemIndex;
	local	vFirstItemIndex = pFirstItemIndex;
	
	if vFirstItemIndex == 0 then
		OutfitterItem_SetToCategory(vItemIndex, pCategoryID, false);
		vItemIndex = vItemIndex + 1;
	else
		vFirstItemIndex = vFirstItemIndex - 1;
	end

	if vItemIndex >= Outfitter_cMaxDisplayedItems then
		return vItemIndex, vFirstItemIndex;
	end

	if not gOutfitter_Collapsed[pCategoryID]
	and vOutfits then
		for vIndex, vOutfit in vOutfits do
			if vFirstItemIndex == 0 then
				OutfitterItem_SetToOutfit(vItemIndex, vOutfit, pCategoryID, vIndex, pEquippableItems);
				vItemIndex = vItemIndex + 1;
				
				if vItemIndex >= Outfitter_cMaxDisplayedItems then
					return vItemIndex, vFirstItemIndex;
				end
			else
				vFirstItemIndex = vFirstItemIndex - 1;
			end
		end
	end
	
	return vItemIndex, vFirstItemIndex;
end

function Outfitter_AddOutfitItemsToList(pOutfitItems, pCategoryID, pItemIndex, pFirstItemIndex)
	local	vItemIndex = pItemIndex;
	local	vFirstItemIndex = pFirstItemIndex;
	
	if vFirstItemIndex == 0 then
		OutfitterItem_SetToCategory(vItemIndex, pCategoryID, false);
		vItemIndex = vItemIndex + 1;
	else
		vFirstItemIndex = vFirstItemIndex - 1;
	end

	if vItemIndex >= Outfitter_cMaxDisplayedItems then
		return vItemIndex, vFirstItemIndex;
	end

	if not gOutfitter_Collapsed[pCategoryID] then
		for vIndex, vOutfitItem in pOutfitItems do
			if vFirstItemIndex == 0 then
				OutfitterItem_SetToItem(vItemIndex, vOutfitItem);
				vItemIndex = vItemIndex + 1;
				
				if vItemIndex >= Outfitter_cMaxDisplayedItems then
					return vItemIndex, vFirstItemIndex;
				end
			else
				vFirstItemIndex = vFirstItemIndex - 1;
			end
		end
	end
	
	return vItemIndex, vFirstItemIndex;
end

function Outfitter_SortOutfits()
	for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
		table.sort(vOutfits, Outfiter_CompareOutfitNames);
	end
end

function Outfiter_CompareOutfitNames(pOutfit1, pOutfit2)
	return pOutfit1.Name < pOutfit2.Name;
end

function Outfitter_Update(pUpdateSlotEnables)
	if not OutfitterFrame:IsVisible() then
		return;
	end
	
	if gOutfitter_CurrentPanel == 1 then
		-- Main panel
		
		if not gOutfitter_DisplayIsDirty then
			return;
		end
		
		gOutfitter_DisplayIsDirty = false;
		
		-- Sort the outfits
		
		Outfitter_SortOutfits();
		
		-- Get the equippable items so outfits can be marked if they're missing anything
		
		local	vEquippableItems = OutfitterItemList_GetEquippableItems();
		
		-- Update the slot enables if they're shown
		
		if pUpdateSlotEnables
		and OutfitterSlotEnables:IsVisible() then
			Outfitter_UpdateSlotEnables(gOutfitter_SelectedOutfit, vEquippableItems);
		end
		
		OutfitterItemList_CompiledUnusedItemsList(vEquippableItems);
		
		-- Update the list
		
		OutfitterMainFrameHighlight:Hide();

		local	vFirstItemIndex = FauxScrollFrame_GetOffset(OutfitterMainFrameScrollFrame);
		local	vItemIndex = 0;
		
		OutfitterItemList_ResetIgnoreItemFlags(vEquippableItems);
		
		for vCategoryIndex, vCategoryID in gOutfitter_cCategoryOrder do
			vItemIndex, vFirstItemIndex = Outfitter_AddOutfitsToList(gOutfitter_Settings.Outfits, vCategoryID, vItemIndex, vFirstItemIndex, vEquippableItems);
			
			if vItemIndex >= Outfitter_cMaxDisplayedItems then
				break;
			end
		end
		
		if vItemIndex < Outfitter_cMaxDisplayedItems
		and vEquippableItems.UnusedItems then
			vItemIndex, vFirstItemIndex = Outfitter_AddOutfitItemsToList(vEquippableItems.UnusedItems, "OddsNEnds", vItemIndex, vFirstItemIndex);
		end
		
		-- Hide any unused items
		
		for vItemIndex2 = vItemIndex, (Outfitter_cMaxDisplayedItems - 1) do
			local	vItemName = "OutfitterItem"..vItemIndex2;
			local	vItem = getglobal(vItemName);
			
			vItem:Hide();
		end
		
		local	vTotalNumItems = 0;
		
		for vCategoryIndex, vCategoryID in gOutfitter_cCategoryOrder do
			vTotalNumItems = vTotalNumItems + 1;
			
			local	vOutfits = gOutfitter_Settings.Outfits[vCategoryID];
			
			if not gOutfitter_Collapsed[vCategoryID]
			and vOutfits then
				vTotalNumItems = vTotalNumItems + table.getn(vOutfits);
			end
		end
		
		if vEquippableItems.UnusedItems then
			vTotalNumItems = vTotalNumItems + 1;
			
			if not gOutfitter_Collapsed["OddsNEnds"] then
				vTotalNumItems = vTotalNumItems + table.getn(vEquippableItems.UnusedItems);
			end
		end
		
		FauxScrollFrame_Update(
				OutfitterMainFrameScrollFrame,
				vTotalNumItems,                 -- numItems
				Outfitter_cMaxDisplayedItems,   -- numToDisplay
				18,                             -- valueStep
				nil, nil, nil,                  -- button, smallWidth, bigWidth
				nil,                            -- highlightFrame
				0, 0);                          -- smallHighlightWidth, bigHighlightWidth
	elseif gOutfitter_CurrentPanel == 2 then -- Options panel
		OutfitterShowMinimapButton:SetChecked(not gOutfitter_Settings.Options.HideMinimapButton);
		OutfitterRememberVisibility:SetChecked(not gOutfitter_Settings.Options.DisableAutoVisibility);
		OutfitterShowHotkeyMessages:SetChecked(not gOutfitter_Settings.Options.DisableHotkeyMessages);
	end
end

function Outfitter_OnVerticalScroll()
	gOutfitter_DisplayIsDirty = true;
	Outfitter_Update(false);
end

function Outfitter_SelectOutfit(pOutfit, pCategoryID)
	if not Outfitter_IsOpen() then
		return;
	end
	
	gOutfitter_SelectedOutfit = pOutfit;
	
	-- Get the equippable items so outfits can be marked if they're missing anything
	
	local	vEquippableItems = OutfitterItemList_GetEquippableItems();
	
	-- Update the slot enables
	
	Outfitter_UpdateSlotEnables(pOutfit, vEquippableItems);
	OutfitterSlotEnables:Show();
	
	-- Done, rebuild the list
	
	gOutfitter_DisplayIsDirty = true;
end

function Outfitter_UpdateSlotEnables(pOutfit, pEquippableItems)
	if UnitHasRelicSlot("player") then
		OutfitterEnableAmmoSlot:Hide();
	else
		OutfitterEnableAmmoSlot:Show();
	end
	
	for _, vInventorySlot in Outfitter_cSlotNames do
		local	vOutfitItem = pOutfit.Items[vInventorySlot];
		local	vCheckbox = getglobal("OutfitterEnable"..vInventorySlot);
		
		if not vOutfitItem then
			vCheckbox:SetChecked(false);
		else
			if OutfitterItemList_InventorySlotContainsItem(pEquippableItems, vInventorySlot, vOutfitItem) then
				vCheckbox:SetCheckedTexture("Interface\\Buttons\\UI-CheckBox-Check");
				vCheckbox.IsUnknown = false;
			else
				vCheckbox:SetCheckedTexture("Interface\\Addons\\Outfitter\\Textures\\CheckboxUnknown");
				vCheckbox.IsUnknown = true;
			end
			
			vCheckbox:SetChecked(true);
		end
	end
end

function Outfitter_ClearSelection()
	gOutfitter_SelectedOutfit = nil;
	gOutfitter_DisplayIsDirty = true;
	OutfitterSlotEnables:Hide();
end

function Outfitter_FindOutfitItemIndex(pOutfit)
	local	vOutfitCategoryID, vOutfitIndex = Outfitter_FindOutfit(pOutfit);
	
	if not vOutfitCategoryID then
		return nil;
	end
	
	local	vItemIndex = 0;
	
	for vCategoryIndex, vCategoryID in gOutfitter_cCategoryOrder do
		vItemIndex = vItemIndex + 1;
		
		if not gOutfitter_Collapsed[vCategoryID] then
			if vOutfitCategoryID == vCategoryID then
				return vItemIndex + vOutfitIndex - 1;
			else
				vItemIndex = vItemIndex + table.getn(gOutfitter_Settings.Outfits[vCategoryID]);
			end
		end
	end
	
	return nil;
end

function OutfitterStack_FindOutfit(pOutfit)
	for vIndex, vOutfit in gOutfitter_OutfitStack do
		if vOutfit == pOutfit then
			return true, vIndex;
		end
	end
	
	return false, nil;
end

function OutfitterStack_FindOutfitByCategory(pCategoryID)
	for vIndex, vOutfit in gOutfitter_OutfitStack do
		if vOutfit.CategoryID == pCategoryID then
			return true, vIndex;
		end
	end
	
	return false, nil;
end

function OutfitterStack_Clear()
	for vIndex, vOutfit in gOutfitter_OutfitStack do
		Outfitter_DispatchOutfitEvent("UNWEAR_OUTFIT", vOutfit.Name, vOutfit)
	end
	
	gOutfitter_OutfitStack = {};
	gOutfitter_Settings.LastOutfitStack = {};
	gOutfitter_DisplayIsDirty = true;
	
	if gOutfitter_Settings.Options.ShowStackContents then
		Outfitter_DebugMessage("Outfitter stack cleared");
	end
end

function OutfitterStack_ClearCategory(pCategoryID)
	local	vIndex = 1;
	local	vStackLength = table.getn(gOutfitter_OutfitStack);
	local	vChanged = false;
	
	while vIndex <= vStackLength do
		local	vOutfit = gOutfitter_OutfitStack[vIndex];
		
		if vOutfit
		and vOutfit.CategoryID == pCategoryID then
			Outfitter_DispatchOutfitEvent("UNWEAR_OUTFIT", vOutfit.Name, vOutfit)
			
			table.remove(gOutfitter_OutfitStack, vIndex);
			table.remove(gOutfitter_Settings.LastOutfitStack, vIndex);
			
			vStackLength = vStackLength - 1;
			vChanged = true;
		else
			vIndex = vIndex + 1;
		end
	end
	
	OutfitterStack_CollapseTemporaryOutfits();
	
	if vChanged then
		if gOutfitter_Settings.Options.ShowStackContents then
			OutfitterStack_DumpStackContents("Clear category "..pCategoryID);
		end
		
		gOutfitter_DisplayIsDirty = true;
	end
end

function OutfitterStack_GetTemporaryOutfit()
	local	vStackSize = table.getn(gOutfitter_OutfitStack);
	
	if vStackSize == 0 then
		return nil;
	end
	
	local	vOutfit = gOutfitter_OutfitStack[vStackSize];
	
	if vOutfit.Name then
		return nil;
	end
	
	return vOutfit;
end

function OutfitterStack_CollapseTemporaryOutfits()
	local	vIndex = 1;
	local	vStackLength = table.getn(gOutfitter_OutfitStack);
	local	vTemporaryOutfit1 = nil;
	
	while vIndex <= vStackLength do
		local	vOutfit = gOutfitter_OutfitStack[vIndex];
		
		if vOutfit
		and vOutfit.Name == nil then
			if vTemporaryOutfit1 then
				-- Copy the items up
				
				for vInventorySlot, vItem in vTemporaryOutfit1.Items do
					if not vOutfit.Items[vInventorySlot] then
						vOutfit.Items[vInventorySlot] = vItem;
					end
				end
				
				-- Remove the lower temp outfit
				
				table.remove(gOutfitter_OutfitStack, vIndex - 1);
				vStackLength = vStackLength - 1;
			else
				vIndex = vIndex + 1;
			end
			
			vTemporaryOutfit1 = vOutfit;
		else
			vTemporaryOutfit1 = nil;
			vIndex = vIndex + 1;
		end
	end
end

function OutfitterStack_IsTopmostOutfit(pOutfit)
	local	vStackLength = table.getn(gOutfitter_OutfitStack);
	
	if vStackLength == 0 then
		return false;
	end
	
	return gOutfitter_OutfitStack[vStackLength] == pOutfit;
end

function OutfitterStack_AddOutfit(pOutfit, pBelowOutfit)
	local	vFound, vIndex = OutfitterStack_FindOutfit(pOutfit);
	
	-- If it's already on then remove it from the stack
	-- so it can be added to the end
	
	if vFound then
		table.remove(gOutfitter_OutfitStack, vIndex);
		table.remove(gOutfitter_Settings.LastOutfitStack, vIndex);
		Outfitter_DispatchOutfitEvent("UNWEAR_OUTFIT", pOutfit.Name, pOutfit);
	end
	
	-- Figure out the position to insert at
	
	local	vStackLength = table.getn(gOutfitter_OutfitStack);
	local	vInsertIndex = vStackLength + 1;
	
	if pBelowOutfit then
		local	vFound2, vIndex = OutfitterStack_FindOutfit(pBelowOutfit);
		
		if vFound2 then
			vInsertIndex = vIndex;
		end
	end
	
	--[[ Always insert below the temporary outfit
	
	local	vTemporaryOutfit;
	
	if vStackLength > 0 then
		vTemporaryOutfit = gOutfitter_OutfitStack[vStackLength];
	end
	
	if vTemporaryOutfit and vTemporaryOutfit.Name == nil then
		-- Knock out any slots used by the new outfit if it's being inserted at the top
		
		if vInsertIndex >= vStackLength then
			for vInventorySlot, vItem in pOutfit.Items do
				vTemporaryOutfit.Items[vInventorySlot] = nil;
			end
			
			-- Remove the temporary outfit if it's empty now
			
			if Outfitter_IsEmptyOutfit(vTemporaryOutfit) then
				table.remove(gOutfitter_OutfitStack, vStackLength);
				table.remove(gOutfitter_Settings.LastOutfitStack, vStackLength);
				
				vInsertIndex = vStackLength;
				vStackLength = vStackLength - 1;
			else
				vInsertIndex = vStackLength;
			end
		end
	end
	
	]]--
	
	-- Add the outfit
	
	table.insert(gOutfitter_OutfitStack, vInsertIndex, pOutfit);
	
	if pOutfit.Name then
		table.insert(gOutfitter_Settings.LastOutfitStack, vInsertIndex, {Name = pOutfit.Name});
	else
		table.insert(gOutfitter_Settings.LastOutfitStack, vInsertIndex, pOutfit);
	end
	
	gOutfitter_DisplayIsDirty = true;
	
	if gOutfitter_Settings.Options.ShowStackContents then
		OutfitterStack_DumpStackContents("Add outfit");
	end
	
	if vFound then
		OutfitterStack_CollapseTemporaryOutfits();
	end
	
	Outfitter_DispatchOutfitEvent("WEAR_OUTFIT", pOutfit.Name, pOutfit);
end

function OutfitterStack_RemoveOutfit(pOutfit)
	local	vFound, vIndex = OutfitterStack_FindOutfit(pOutfit);
	
	if not vFound then
		return false;
	end
	
	-- Remove the outfit
	
	table.remove(gOutfitter_OutfitStack, vIndex);
	table.remove(gOutfitter_Settings.LastOutfitStack, vIndex);
	
	OutfitterStack_CollapseTemporaryOutfits();
	
	gOutfitter_DisplayIsDirty = true;
	
	if gOutfitter_Settings.Options.ShowStackContents then
		OutfitterStack_DumpStackContents("Remove outfit");
	end
	
	return true;
end

function OutfitterStack_RestoreSavedStack()
	if not gOutfitter_Settings.LastOutfitStack then
		gOutfitter_Settings.LastOutfitStack = {};
	end
	
	for vIndex, vOutfit in gOutfitter_Settings.LastOutfitStack do
		if vOutfit.Name then
			vOutfit = Outfitter_FindOutfitByName(vOutfit.Name);
		end
		
		if vOutfit then
			table.insert(gOutfitter_OutfitStack, vOutfit);
		end
	end
	
	gOutfitter_ExpectedOutfit = Outfitter_GetCompiledOutfit();
	
	Outfitter_UpdateTemporaryOutfit(Outfitter_GetNewItemsOutfit(gOutfitter_ExpectedOutfit));
	
	if gOutfitter_Settings.Options.ShowStackContents then
		OutfitterStack_DumpStackContents("Restore saved stack");
	end
end

function OutfitterStack_DumpStackContents(pOperation)
	Outfitter_DebugMessage("Outfitter Stack Contents: "..pOperation);
	
	for vIndex, vOutfit in gOutfitter_OutfitStack do
		if vOutfit.Name then
			Outfitter_DebugMessage("Slot "..vIndex..": "..vOutfit.Name);
		else
			Outfitter_DebugMessage("Slot "..vIndex..": Temporaray outfit");
		end
	end
end

function Outfitter_WearOutfit(pOutfit, pCategoryID, pWearBelowOutfit)
	if pOutfit.Disabled then
		return;
	end
	
	--
	
	Outfitter_BeginEquipmentUpdate();
	
	if pCategoryID == "Complete" then
		OutfitterStack_Clear();
	elseif pCategoryID == "Partial" then
		OutfitterStack_ClearCategory(pCategoryID);
		OutfitterStack_ClearCategory("Accessory");
	end
	
	OutfitterStack_AddOutfit(pOutfit, pWearBelowOutfit);
	
	-- If outfitter is open then also select the outfit
	
	if Outfitter_IsOpen() then
		if OutfitterStack_IsTopmostOutfit(pOutfit) then
			Outfitter_SelectOutfit(pOutfit, pCategoryID);
		else
			Outfitter_ClearSelection();
		end
	end
	
	-- Update the equipment
	
	gOutfitter_EquippedNeedsUpdate = true;
	gOutfitter_WeaponsNeedUpdate = true;
	
	Outfitter_EndEquipmentUpdate("Outfitter_WearOutfit");
end

function Outfitter_SetOutfitBindingIndex(pOutfit, pBindingIndex)
	for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
		for vOutfitIndex, vOutfit in vOutfits do
			if vOutfit.BindingIndex == pBindingIndex then
				vOutfit.BindingIndex = nil;
			end
		end
	end
	
	pOutfit.BindingIndex = pBindingIndex;
end

local	gOutfitter_LastBindingIndex = nil;
local	gOutfitter_LastBindingTime = nil;
local	Outfitter_cMinBindingTime = 0.75;

function Outfitter_WearBoundOutfit(pBindingIndex)
	-- Check for the user spamming the button so prevent the outfit from
	-- toggling if they're panicking
	
	local	vTime = GetTime();
	
	if gOutfitter_LastBindingIndex == pBindingIndex then
		local	vElapsed = vTime - gOutfitter_LastBindingTime;
		
		if vElapsed < Outfitter_cMinBindingTime then
			gOutfitter_LastBindingTime = vTime;
			return;
		end
	end
	
	--
	
	for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
		for vOutfitIndex, vOutfit in vOutfits do
			if vOutfit.BindingIndex == pBindingIndex then
				vOutfit.Disabled = nil;
				if vCategoryID == "Complete" then
					Outfitter_WearOutfit(vOutfit, vCategoryID);
					if not gOutfitter_Settings.Options.DisableHotkeyMessages then
						UIErrorsFrame:AddMessage(format(Outfitter_cEquipOutfitMessageFormat, vOutfit.Name), OUTFIT_MESSAGE_COLOR.r, OUTFIT_MESSAGE_COLOR.g, OUTFIT_MESSAGE_COLOR.b);
					end
				else
					local	vEquipped = Outfitter_ToggleOutfit(vOutfit, vCategoryID);
					
					if not gOutfitter_Settings.Options.DisableHotkeyMessages then
						if vEquipped then
							UIErrorsFrame:AddMessage(format(Outfitter_cEquipOutfitMessageFormat, vOutfit.Name), OUTFIT_MESSAGE_COLOR.r, OUTFIT_MESSAGE_COLOR.g, OUTFIT_MESSAGE_COLOR.b);
						else
							UIErrorsFrame:AddMessage(format(Outfitter_cUnequipOutfitMessageFormat, vOutfit.Name), OUTFIT_MESSAGE_COLOR.r, OUTFIT_MESSAGE_COLOR.g, OUTFIT_MESSAGE_COLOR.b);
						end
					end

				end
				
				-- Remember the binding used to filter for button spam
				
				gOutfitter_LastBindingIndex = pBindingIndex;
				gOutfitter_LastBindingTime = vTime;
				
				return;
			end
		end
	end
end

function Outfitter_FindOutfit(pOutfit)
	for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
		for vOutfitIndex, vOutfit in vOutfits do
			if vOutfit == pOutfit then
				return vCategoryID, vOutfitIndex;
			end
		end
	end
	
	return nil, nil;
end

function Outfitter_FindOutfitByName(pName)
	if not pName
	or pName == "" then
		return nil;
	end
	
	local	vLowerName = strlower(pName);
	
	for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
		for vOutfitIndex, vOutfit in vOutfits do
			if strlower(vOutfit.Name) == vLowerName then
				return vOutfit, vCategoryID, vOutfitIndex;
			end
		end
	end
	
	return nil, nil;
end

-- Outfitter doesn't use this function, but other addons such as
-- Fishing Buddy might use it to locate specific generated outfits

function Outfitter_FindOutfitByStatID(pStatID)
	if not pStatID or pStatID == "" then
		return nil;
	end

	for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
		for vOutfitIndex, vOutfit in vOutfits do
			if vOutfit.StatID and vOutfit.StatID == pStatID then
				return vOutfit, vCategoryID, vOutfitIndex;
			end
		end
	end
	
	return nil;
end

function Outfitter_RemoveOutfit(pOutfit)
	if not OutfitterStack_RemoveOutfit(pOutfit) then
		return;
	end
	
	-- Stop monitoring health and mana if it's the dining outfit
	
	if pOutfit.SpecialID == "Dining" then
		Outfitter_SuspendEvent(OutfitterFrame, "UNIT_HEALTH");
		Outfitter_SuspendEvent(OutfitterFrame, "UNIT_MANA");
	end
	
	--
	
	Outfitter_BeginEquipmentUpdate();
	
	-- Clear the selection if the outfit being removed
	-- is selected too
	
	if gOutfitter_SelectedOutfit == pOutfit then
		Outfitter_ClearSelection();
	end
	
	-- Update the list
	
	gOutfitter_EquippedNeedsUpdate = true;
	gOutfitter_WeaponsNeedUpdate = true;
	
	Outfitter_EndEquipmentUpdate("Outfitter_RemoveOutfit");
	
	Outfitter_DispatchOutfitEvent("UNWEAR_OUTFIT", pOutfit.Name, pOutfit);
end

function Outfitter_ToggleOutfit(pOutfit, pCategoryID)
	if Outfitter_WearingOutfit(pOutfit) then
		Outfitter_RemoveOutfit(pOutfit);
		return false;
	else
		Outfitter_WearOutfit(pOutfit, pCategoryID);
		return true;
	end
end

function Outfitter_OutfitSummary()
	local	_, vPlayerClass = UnitClass("player");
	local	vStatDistribution = gOutfitter_StatDistribution[vPlayerClass];
	local	vCurrentOutfitStats = OutfitterTankPoints_GetCurrentOutfitStats(vStatDistribution);
	
	Outfitter_DumpArray("Current Stats", vCurrentOutfitStats);
end

function Outfitter_GetCompiledOutfit()
	local	vCompiledOutfit = Outfitter_NewEmptyOutfit();
	
	vCompiledOutfit.SourceOutfit = {};
	
	for vStackIndex, vOutfit in gOutfitter_OutfitStack do
		for vInventorySlot, vOutfitItem in vOutfit.Items do
			vCompiledOutfit.Items[vInventorySlot] = vOutfitItem;
			vCompiledOutfit.SourceOutfit[vInventorySlot] = vOutfit.Name;
		end
	end
	
	return vCompiledOutfit;
end

function Outfitter_GetExpectedOutfit(pExcludeOutfit)
	local	vCompiledOutfit = Outfitter_NewEmptyOutfit();
	
	vCompiledOutfit.SourceOutfit = {};
	
	for vStackIndex, vOutfit in gOutfitter_OutfitStack do
		if vOutfit ~= pExcludeOutfit then
			for vInventorySlot, vOutfitItem in vOutfit.Items do
				vCompiledOutfit.Items[vInventorySlot] = vOutfitItem;
				vCompiledOutfit.SourceOutfit[vInventorySlot] = vOutfit.Name;
			end
		end
	end
	
	return vCompiledOutfit;
end

function Outfitter_GetEmptyBagSlot(pStartBagIndex, pStartBagSlotIndex, pIncludeBank)
	local	vStartBagIndex = pStartBagIndex;
	local	vStartBagSlotIndex = pStartBagSlotIndex;
	
	if not vStartBagIndex then
		vStartBagIndex = NUM_BAG_SLOTS;
	end
	
	if not vStartBagSlotIndex then
		vStartBagSlotIndex = 1;
	end
	
	local	vEndBagIndex = 0;
	
	if pIncludeBank then
		vEndBagIndex = -1;
	end
	
	for vBagIndex = vStartBagIndex, vEndBagIndex, -1 do
		-- Skip the bag if it's a specialty bag (ammo pouch, quiver, shard bag)
		
		local	vSkipBag = false;
		
		if vBagIndex > 0 then -- Don't worry about the backpack
			local	vItemLink = GetInventoryItemLink("player", ContainerIDToInventoryID(vBagIndex));
			local	vItemInfo = Outfitter_GetItemInfoFromLink(vItemLink);
			
			if vItemInfo
			and Outfitter_cSpecialtyBags[vItemInfo.Code] ~= nil then
				vSkipBag = true;
			end
		end
		
		-- Search the bag for empty slots
		
		if not vSkipBag then
			local	vNumBagSlots = GetContainerNumSlots(vBagIndex);
			
			if vNumBagSlots > 0 then
				for vSlotIndex = vStartBagSlotIndex, vNumBagSlots do
					local	vItemInfo = Outfitter_GetBagItemInfo(vBagIndex, vSlotIndex);
					
					if not vItemInfo then
						return {BagIndex = vBagIndex, BagSlotIndex = vSlotIndex};
					end
				end
			end
		end
		
		vStartBagSlotIndex = 1;
	end
	
	return nil;
end

function Outfitter_GetEmptyBagSlotList()
	local	vEmptyBagSlots = {};
	
	local	vBagIndex = NUM_BAG_SLOTS;
	local	vBagSlotIndex = 1;
	
	while true do
		local	vBagSlotInfo = Outfitter_GetEmptyBagSlot(vBagIndex, vBagSlotIndex);
		
		if not vBagSlotInfo then
			return vEmptyBagSlots;
		end
		
		table.insert(vEmptyBagSlots, vBagSlotInfo);
		
		vBagIndex = vBagSlotInfo.BagIndex;
		vBagSlotIndex = vBagSlotInfo.BagSlotIndex + 1;
	end
end

function Outfitter_GetEmptyBankSlotList()
	local	vEmptyBagSlots = {};
	
	local	vBagIndex = NUM_BAG_SLOTS + NUM_BANKBAGSLOTS;
	local	vBagSlotIndex = 1;
	
	while true do
		local	vBagSlotInfo = Outfitter_GetEmptyBagSlot(vBagIndex, vBagSlotIndex, true);
		
		if not vBagSlotInfo then
			return vEmptyBagSlots;
		
		elseif vBagSlotInfo.BagIndex > NUM_BAG_SLOTS
		or vBagSlotInfo.BagIndex < 0 then
			table.insert(vEmptyBagSlots, vBagSlotInfo);
		end
		
		vBagIndex = vBagSlotInfo.BagIndex;
		vBagSlotIndex = vBagSlotInfo.BagSlotIndex + 1;
	end
end

function Outfitter_FindItemsInBagsForSlot(pSlotName)
	local	vInventorySlot = pSlotName;
	
	-- Alias the slot names down for finger and trinket
	
	if vInventorySlot == "Finger1Slot" then
		vInventorySlot = "Finger0Slot";
	elseif vInventorySlot == "Trinket1Slot" then
		vInventorySlot = "Trinket0Slot";
	end
	
	--
	
	local	vItems = {};
	local	vNumBags, vFirstBagIndex = Outfitter_GetNumBags();
	
	for vBagIndex = vFirstBagIndex, vNumBags do
		local	vNumBagSlots = GetContainerNumSlots(vBagIndex);
		
		if vNumBagSlots > 0 then
			for vSlotIndex = 1, vNumBagSlots do
				local	vItemInfo = Outfitter_GetBagItemInfo(vBagIndex, vSlotIndex);
				
				if vItemInfo then
					local	vItemSlotName = vItemInfo.ItemSlotName;
					
					if vItemInfo.MetaSlotName then
						vItemSlotName = vItemInfo.MetaSlotName;
					end
					
					if vItemSlotName == "TwoHandSlot" then
						vItemSlotName = "MainHandSlot";
					elseif vItemSlotName == "Weapon0Slot" then
						if vInventorySlot == "MainHandSlot"
						or vInventorySlot == "SecondaryHandSlot" then
							vItemSlotName = vInventorySlot;
						end
					end
					
					if vItemSlotName == vInventorySlot then
						table.insert(vItems, {BagIndex = vBagIndex, BagSlotIndex = vSlotIndex, Code = vItemInfo.Code, Name = vItemInfo.Name});
					end
				end
			end
		end
	end
	
	if table.getn(vItems) == 0 then	
		return nil;
	end
	
	return vItems;
end

function Outfitter_PickupItemLocation(pItemLocation)
	if pItemLocation == nil then
		Outfitter_ErrorMessage("Outfitter: nil location in PickupItemLocation");
		return;
	end
	
	if pItemLocation.BagIndex then
		PickupContainerItem(pItemLocation.BagIndex, pItemLocation.BagSlotIndex);
	elseif pItemLocation.SlotName then
		local	vSlotID, vEmptySlotTexture = GetInventorySlotInfo(pItemLocation.SlotName);
		
		PickupInventoryItem(vSlotID);
	else
		Outfitter_ErrorMessage("Outfitter: Unknown location in PickupItemLocation");
		return;
	end
end

function Outfitter_BuildUnequipChangeList(pOutfit, pEquippableItems)
	local	vEquipmentChangeList = {};

	for vInventorySlot, vOutfitItem in pOutfit.Items do
		local	vItem, vIgnoredItem = OutfitterItemList_FindItemOrAlt(pEquippableItems, vOutfitItem, true);
		
		if vItem then
			table.insert(vEquipmentChangeList, {FromLocation = vItem.Location, Item = vItem, ToLocation = nil});
		end
	end -- for
	
	return vEquipmentChangeList;
end

function Outfitter_BuildEquipmentChangeList(pOutfit, pEquippableItems)
	local	vEquipmentChangeList = {};
	
	OutfitterItemList_ResetIgnoreItemFlags(pEquippableItems);
	
	-- Remove items which are already in the correct slot from the outfit and from the
	-- equippable items list
	
	for vInventorySlot, vOutfitItem in pOutfit.Items do
		local	vContainsItem, vItem = OutfitterItemList_InventorySlotContainsItem(pEquippableItems, vInventorySlot, vOutfitItem);
		
		if vContainsItem then
			pOutfit.Items[vInventorySlot] = nil;
			
			if vItem then
				vItem.IgnoreItem = true;
			end
		end
	end
	
	-- Scan the outfit using the Outfitter_cSlotNames array as an index so that changes
	-- are executed in the specified order
	
	for _, vInventorySlot in Outfitter_cSlotNames do
		local	vOutfitItem = pOutfit.Items[vInventorySlot];
		
		if vOutfitItem then
			local	vSlotID, vEmptySlotTexture = GetInventorySlotInfo(vInventorySlot);
			local	vCurrentItemInfo = Outfitter_GetInventoryItemInfo(vInventorySlot);
			
			-- Empty the slot if it's supposed to be blank
			
			if vOutfitItem.Code == 0 then
				if vCurrentItemInfo then
					table.insert(vEquipmentChangeList, {SlotName = vInventorySlot, SlotID = vSlotID, ItemName = vOutfitItem.Name, ItemLocation = nil});
				end

			else
				-- Find the item
				
				local	vItem, vIgnoredItem = OutfitterItemList_FindItemOrAlt(pEquippableItems, vOutfitItem, true);
				
				-- If the item wasn't found then show an appropriate error message
				
				if not vItem then
					if vOutfitItem.Name then
						if vIgnoredItem then
							local	vSlotDisplayName = Outfitter_cSlotDisplayNames[vInventorySlot];
							
							if not vSlotDisplayName then
								vSlotDisplayName = vInventorySlot;
							end
							
							Outfitter_ErrorMessage(format(Outfitter_cItemAlreadyUsedError, vOutfitItem.Name, vSlotDisplayName));
						else
							Outfitter_ErrorMessage(format(Outfitter_cItemNotFoundError, vOutfitItem.Name));
						end
					else
						Outfitter_ErrorMessage(format(Outfitter_cItemNotFoundError, "unknown"));
					end
				
				-- Generate a change to move the item from its present location to the correct slot
				
				else
					pOutfit.Items[vInventorySlot].MetaSlotName = vItem.MetaSlotName;
					table.insert(vEquipmentChangeList, {SlotName = vInventorySlot, SlotID = vSlotID, ItemName = vOutfitItem.Name, ItemMetaSlotName = vItem.MetaSlotName, ItemLocation = vItem});
				end
			end
		end -- if
	end -- for
	
	if table.getn(vEquipmentChangeList) == 0 then
		return nil;
	end
	
	Outfitter_OptimizeEquipmentChangeList(vEquipmentChangeList);
	
	return vEquipmentChangeList;
end

function Outfitter_FindEquipmentChangeForSlot(pEquipmentChangeList, pSlotName)
	for vChangeIndex, vEquipmentChange in pEquipmentChangeList do
		if vEquipmentChange.SlotName == pSlotName then
			return vChangeIndex, vEquipmentChange;
		end
	end
	
	return nil, nil;
end

function Outfitter_FixSlotSwapChange(pEquipmentList, pChangeIndex1, pEquipmentChange1, pSlotName1, pChangeIndex2, pEquipmentChange2, pSlotName2)
	-- No problem if both slots will be emptied
	
	if not pEquipmentChange1.ItemLocation
	and not pEquipmentChange2.ItemLocation then
		return;
	end
	
	-- No problem if neither slot is being moved to the other one
	
	local	vSlot2ToSlot1 = pEquipmentChange1.ItemLocation ~= nil
			            and pEquipmentChange1.ItemLocation.SlotName == pSlotName2;
	
	local	vSlot1ToSlot2 = pEquipmentChange2.ItemLocation ~= nil
			            and pEquipmentChange2.ItemLocation.SlotName == pSlotName1;
	
	-- No problem if the slots are swapping with each other
	-- or not moving between each other at all
	
	if vSlot2ToSlot1 == vSlot1ToSlot2 then
		return;
	end
	
	-- Slot 1 is moving to slot 2
	
	if vSlot1ToSlot2 then
		
		if pEquipmentChange1.ItemLocation then
			-- Swap change 1 and change 2 around
			
			pEquipmentList[pChangeIndex1] = pEquipmentChange2;
			pEquipmentList[pChangeIndex2] = pEquipmentChange1;
			
			-- Insert a change to empty slot 2
			
			table.insert(pEquipmentList, pChangeIndex1, {SlotName = pEquipmentChange2.SlotName, SlotID = pEquipmentChange2.SlotID, ItemLocation = nil});
		else
			-- Slot 1 is going to be empty, so empty slot 2 instead
			-- and then when slot 1 is moved it'll swap the empty space
			
			pEquipmentChange1.SlotName = pSlotName2;
			pEquipmentChange1.SlotID = pEquipmentChange2.SlotID;
			pEquipmentChange1.ItemLocation = nil;
		end
		
	-- Slot 2 is moving to slot 1
	
	else
		if pEquipmentChange2.ItemLocation then
			-- Insert a change to empty slot 1 first
			
			table.insert(pEquipmentList, pChangeIndex1, {SlotName = pEquipmentChange1.SlotName, SlotID = pEquipmentChange1.SlotID, ItemLocation = nil});
		else
			-- Slot 2 is going to be empty, so empty slot 1 instead
			-- and then when slot 2 is moved it'll swap the empty space
			
			pEquipmentChange2.SlotName = pSlotName1;
			pEquipmentChange2.SlotID = pEquipmentChange1.SlotID;
			pEquipmentChange2.ItemLocation = nil;
			
			-- Change the order so that slot 1 gets emptied before the move
			
			pEquipmentList[pChangeIndex1] = pEquipmentChange2;
			pEquipmentList[pChangeIndex2] = pEquipmentChange1;
		end
	end
end

function Outfitter_OptimizeEquipmentChangeList(pEquipmentChangeList)
	local	vSwapList =
	{
		{Slot1 = "Finger0Slot", Slot2 = "Finger1Slot"},
		{Slot1 = "Trinket0Slot", Slot2 = "Trinket1Slot"},
		{Slot1 = "MainHandSlot", Slot2 = "SecondaryHandSlot"},
	};
	
	local	vDidSlot = {};
	
	local	vChangeIndex = 1;
	local	vNumChanges = table.getn(pEquipmentChangeList);
	
	while vChangeIndex <= vNumChanges do
		local	vEquipmentChange = pEquipmentChangeList[vChangeIndex];
		
		-- If a two-hand weapon is being equipped, remove the change event
		-- for removing the offhand slot
		
		if vEquipmentChange.ItemMetaSlotName == "TwoHandSlot" then
			local	vChangeIndex2, vEquipmentChange2 = Outfitter_FindEquipmentChangeForSlot(pEquipmentChangeList, "SecondaryHandSlot");
			
			-- If there's a change for the offhand slot, remove it
			
			if vChangeIndex2 then
				table.remove(pEquipmentChangeList, vChangeIndex2);
				
				if vChangeIndex2 < vChangeIndex then
					vChangeIndex = vChangeIndex - 1;
				end
				
				vNumChanges = vNumChanges - 1;
			end
			
			-- Insert a new change for the offhand slot to empty it ahead
			-- of equipping the two-hand item
			
			local	vSlotID, vEmptySlotTexture = GetInventorySlotInfo("SecondaryHandSlot");
			
			table.insert(pEquipmentChangeList, vChangeIndex, {SlotName = "SecondaryHandSlot", SlotID = vSlotID, ItemLocation = nil});
			
		-- Otherwise see if the change needs to be re-arranged so that slot
		-- swapping works correctly
		
		else
			for vSwapListIndex, vSwapSlotInfo in vSwapList do
				if vEquipmentChange.SlotName == vSwapSlotInfo.Slot1
				and not vDidSlot[vEquipmentChange.SlotName] then
					local	vChangeIndex2, vEquipmentChange2 = Outfitter_FindEquipmentChangeForSlot(pEquipmentChangeList, vSwapSlotInfo.Slot2);
					
					if vChangeIndex2 then
						Outfitter_FixSlotSwapChange(pEquipmentChangeList, vChangeIndex, vEquipmentChange, vSwapSlotInfo.Slot1, vChangeIndex2, vEquipmentChange2, vSwapSlotInfo.Slot2);
					end
					
					vDidSlot[vEquipmentChange.SlotName] = true;
					
					vNumChanges = table.getn(pEquipmentChangeList);
				end
			end
		end
		
		vChangeIndex = vChangeIndex + 1;
	end
end

function Outfitter_ExecuteEquipmentChangeList(pEquipmentChangeList, pEmptyBagSlots, pExpectedEquippableItems)
	for vChangeIndex, vEquipmentChange in pEquipmentChangeList do
		if vEquipmentChange.ItemLocation then
			Outfitter_PickupItemLocation(vEquipmentChange.ItemLocation);
			EquipCursorItem(vEquipmentChange.SlotID);
			
			if pExpectedEquippableItems then
				OutfitterItemList_SwapLocationWithInventorySlot(pExpectedEquippableItems, vEquipmentChange.ItemLocation, vEquipmentChange.SlotName);
			end
		else
			-- Remove the item
			
			if not pEmptyBagSlots
			or table.getn(pEmptyBagSlots) == 0 then
				local	vItemInfo = Outfitter_GetInventoryItemInfo(vEquipmentChange.SlotName);
				
				if not vItemInfo then
					Outfitter_ErrorMessage("Outfitter internal error: Can't empty slot "..vEquipmentChange.SlotName.." because bags are full but slot is empty");
				else
					Outfitter_ErrorMessage(format(Outfitter_cBagsFullError, vItemInfo.Name));
				end
			else
				local	vBagIndex = pEmptyBagSlots[1].BagIndex;
				local	vBagSlotIndex = pEmptyBagSlots[1].BagSlotIndex;
				
				table.remove(pEmptyBagSlots, 1);
				
				PickupInventoryItem(vEquipmentChange.SlotID);
				PickupContainerItem(vBagIndex, vBagSlotIndex);
				
				if pExpectedEquippableItems then
					OutfitterItemList_SwapBagSlotWithInventorySlot(pExpectedEquippableItems, vBagIndex, vBagSlotIndex, vEquipmentChange.SlotName);
				end
			end
		end
	end
end

function Outfitter_ExecuteEquipmentChangeList2(pEquipmentChangeList, pEmptySlots, pBagsFullErrorFormat, pExpectedEquippableItems)
	for vChangeIndex, vEquipmentChange in pEquipmentChangeList do
		if vEquipmentChange.ToLocation then
			Outfitter_PickupItemLocation(vEquipmentChange.FromLocation);
			EquipCursorItem(vEquipmentChange.SlotID);
			
			if pExpectedEquippableItems then
				OutfitterItemList_SwapLocationWithInventorySlot(pExpectedEquippableItems, vEquipmentChange.ToLocation, vEquipmentChange.SlotName);
			end
		else
			-- Remove the item
			
			if not pEmptySlots
			or table.getn(pEmptySlots) == 0 then
				Outfitter_ErrorMessage(format(pBagsFullErrorFormat, vEquipmentChange.Item.Name));
			else
				local	vToLocation = {BagIndex = pEmptySlots[1].BagIndex, BagSlotIndex = pEmptySlots[1].BagSlotIndex};
				
				table.remove(pEmptySlots, 1);
				
				Outfitter_PickupItemLocation(vEquipmentChange.FromLocation);
				Outfitter_PickupItemLocation(vToLocation);
				
				if pExpectedEquippableItems then
					OutfitterItemList_SwapLocations(pExpectedEquippableItems, vEquipmentChange.FromLocation, vToLocation);
				end
			end
		end
	end
end

function Outfitter_OutfitHasCombatEquipmentSlots(pOutfit)
	for vEquipmentSlot, _ in Outfitter_cCombatEquipmentSlots do
		if pOutfit.Items[vEquipmentSlot] then
			return true;
		end
	end
	
	return false;
end

function Outfitter_OutfitOnlyHasCombatEquipmentSlots(pOutfit)
	for vEquipmentSlot, _ in pOutfit.Items do
		if not Outfitter_cCombatEquipmentSlots[vEquipmentSlot] then
			return false;
		end
	end
	
	return true;
end

local	gOutfitter_EquipmentUpdateCount = 0;

function Outfitter_BeginEquipmentUpdate()
	gOutfitter_EquipmentUpdateCount = gOutfitter_EquipmentUpdateCount + 1;
end

function Outfitter_EndEquipmentUpdate(pCallerName)
	gOutfitter_EquipmentUpdateCount = gOutfitter_EquipmentUpdateCount - 1;
	
	if gOutfitter_EquipmentUpdateCount == 0 then
		Outfitter_UpdateEquippedItems();
		Outfitter_Update(false);
	end
end

function Outfitter_UpdateEquippedItems()
	if not gOutfitter_EquippedNeedsUpdate
	and not gOutfitter_WeaponsNeedUpdate then
		return;
	end
	
	-- Delay all changes until they're alive
	
	if gOutfitter_IsDead then
--	or gOutfitter_IsFeigning then -- no longer disabling outfit changes during FD
		return;
	end
	
	local	vCurrentTime = GetTime();
	
	if vCurrentTime - gOutfitter_LastEquipmentUpdateTime < Outfitter_cMinEquipmentUpdateInterval then
		OutfitterTimer_AdjustTimer();
		return;
	end
	
	gOutfitter_LastEquipmentUpdateTime = vCurrentTime;
	
	local	vWeaponsNeedUpdate = gOutfitter_WeaponsNeedUpdate;
	
	gOutfitter_EquippedNeedsUpdate = false;
	gOutfitter_WeaponsNeedUpdate = false;
	
	-- Compile the outfit
	
	local	vEquippableItems = OutfitterItemList_GetEquippableItems();
	local	vCompiledOutfit = Outfitter_GetCompiledOutfit();
	
	-- If the outfit contains non-weapon changes then
	-- delay the change until they're out of combat but go
	-- ahead and swap the weapon slots if there are any
	
	if gOutfitter_InCombat then
		if vWeaponsNeedUpdate
		and Outfitter_OutfitHasCombatEquipmentSlots(vCompiledOutfit) then
			
			-- Allow the weapon change to proceed but defer the rest
			-- until they're out of combat
			
			local	vWeaponOutfit = Outfitter_NewEmptyOutfit();
			
			for vEquipmentSlot, _ in Outfitter_cCombatEquipmentSlots do
				vWeaponOutfit.Items[vEquipmentSlot] = vCompiledOutfit.Items[vEquipmentSlot];
			end
			
			-- Still need to update the rest once they exit combat
			-- if there are non-equipment slot items
			
			if not Outfitter_OutfitOnlyHasCombatEquipmentSlots(vCompiledOutfit) then
				gOutfitter_EquippedNeedsUpdate = true;
			end
			
			-- Switch to the weapons-only part
			
			vCompiledOutfit = vWeaponOutfit;
		else
			-- No weapon changes, just defer the whole outfit change
			
			gOutfitter_EquippedNeedsUpdate = true;
			return;
		end
	end
	
	-- Equip it
	
	local	vEquipmentChangeList = Outfitter_BuildEquipmentChangeList(vCompiledOutfit, vEquippableItems);
	
	if vEquipmentChangeList then
		-- local	vExpectedEquippableItems = OutfitterItemList_New();
	
		Outfitter_ExecuteEquipmentChangeList(vEquipmentChangeList, Outfitter_GetEmptyBagSlotList(), vExpectedEquippableItems);
		
		-- Outfitter_DumpArray("ExpectedEquippableItems", vExpectedEquippableItems);
	end
	
	-- Update the outfit we're expecting to see on the player
	
	for vInventorySlot, vItem in vCompiledOutfit.Items do
		gOutfitter_ExpectedOutfit.Items[vInventorySlot] = vCompiledOutfit.Items[vInventorySlot];
	end
end

function Outfitter_InitDebugging()
	if gOutfitter_InitializedDebug then
		return;
	end
	
	gOutfitter_InitializedDebug = true;
	
	-- Find the debug frame if there is one
	
	for vChatIndex = 1, NUM_CHAT_WINDOWS do
		local	vChatFrame = getglobal("ChatFrame"..vChatIndex);
		
		if vChatFrame
		and (vChatFrame:IsVisible() or vChatFrame.isDocked) then
			local	vTab = getglobal("ChatFrame"..vChatIndex.."Tab");
			local	vName = vTab:GetText();
			
			if vName == "Debug" then
				gOutfitter_DebugFrame = vChatFrame;
				if gOutfitter_DebugFrame:GetMaxLines() < 1000 then
					gOutfitter_DebugFrame:SetMaxLines(1000);
				end
				_ERRORMESSAGE = function(message) Outfitter_DebugMessage(message); end;
			end
		end
	end
	
	if gOutfitter_DebugFrame then
		Outfitter_DebugMessage("Found debugging chat frame");
	end
end

function Outfitter_DebugMessage(pMessage)
	if gOutfitter_DebugFrame then
		gOutfitter_DebugFrame:AddMessage("DEBUG: "..pMessage, 0.7, 0.3, 1.0);
		
		local vTabFlash = getglobal(gOutfitter_DebugFrame:GetName().."TabFlash");
		
		vTabFlash:Show();
		UIFrameFlash(vTabFlash, 0.25, 0.25, 60, nil, 0.5, 0.5);
	else
		DEFAULT_CHAT_FRAME:AddMessage("DEBUG: "..pMessage, 0.7, 0.3, 1.0);
	end
end

function Outfitter_ErrorMessage(pMessage)
	DEFAULT_CHAT_FRAME:AddMessage(pMessage, 0.8, 0.3, 0.5);
end

function Outfitter_TestMessage(pMessage)
	if gOutfitter_DebugFrame then
		gOutfitter_DebugFrame:AddMessage("TEST: "..pMessage, 0.7, 0.3, 1.0);
	else
		DEFAULT_CHAT_FRAME:AddMessage("TEST: "..pMessage, 0.7, 0.3, 1.0);
	end
end

function Outfitter_NoteMessage(pMessage)
	DEFAULT_CHAT_FRAME:AddMessage(pMessage, 0.6, 1.0, 0.3);
end

function Outfitter_RenameLink(pLink, pName)
	local	vMessage = string.gsub(pLink, "%[.*%]", "["..pName.."]");
	-- local	vMessage = string.gsub(pMessage, "||", "|");
	
	DEFAULT_CHAT_FRAME:AddMessage(vMessage);
end

function Outfitter_DumpArray(pPrefixString, pArray)
	if not pArray then
		Outfitter_DebugMessage(pPrefixString.." is nil");
		return;
	end
	
	local	vFoundElement = false;
	
	for vIndex, vElement in pArray do
		vFoundElement = true;
		
		local	vType = type(vElement);
		local	vPrefix;
		
		if type(vIndex) == "number" then
			vPrefix = pPrefixString.."["..vIndex.."]";
		else
			vPrefix = pPrefixString.."."..vIndex;
		end
		
		if vType == "number" then
			Outfitter_DebugMessage(vPrefix.." = "..vElement);
		elseif vType == "string" then
			Outfitter_DebugMessage(vPrefix.." = \""..vElement.."\"");
		elseif vType == "boolean" then
			if vElement then
				Outfitter_DebugMessage(vPrefix.." = true");
			else
				Outfitter_DebugMessage(vPrefix.." = false");
			end
		elseif vType == "table" then
			Outfitter_DumpArray(vPrefix, vElement);
		else
			Outfitter_DebugMessage(vPrefix.." "..vType);
		end
	end
	
	if not vFoundElement then
		Outfitter_DebugMessage(pPrefixString.." is empty");
	end
end

function Outfitter_InventorySlotIsEmpty(pInventorySlot)
	return Outfitter_GetInventoryItemInfo(pInventorySlot) == nil;
end

function Outfitter_GetBagItemInfo(pBagIndex, pSlotIndex)
	local	vItemLink = GetContainerItemLink(pBagIndex, pSlotIndex);
	local	vItemInfo = Outfitter_GetItemInfoFromLink(vItemLink);
	
	if not vItemInfo then
		return nil;
	end
	
	vItemInfo.Texture, _, _, vItemInfo.Quality, _ = GetContainerItemInfo(pBagIndex, pSlotIndex);
	
	return vItemInfo;
end

local	gOutfitter_AmmoSlotInfoCache = nil;

function Outfitter_FindAmmoSlotItem(pName, pTexture)
	if gOutfitter_AmmoSlotInfoCache
	and gOutfitter_AmmoSlotInfoCache.Name == pName
	and gOutfitter_AmmoSlotInfoCache.Texture == pTexture then
		return gOutfitter_AmmoSlotInfoCache.ItemInfo;
	end
		
	for vBagIndex = 0, NUM_BAG_SLOTS do
		local	vNumBagSlots = GetContainerNumSlots(vBagIndex);
		
		if vNumBagSlots > 0 then
			for vBagSlotIndex = 1, vNumBagSlots do
				local	vTexture = GetContainerItemInfo(vBagIndex, vBagSlotIndex);
				
				if vTexture == pTexture then
					local	vItemInfo = Outfitter_GetBagItemInfo(vBagIndex, vBagSlotIndex);
					
					if vItemInfo.Name == pName then
						if not gOutfitter_AmmoSlotInfoCache then
							gOutfitter_AmmoSlotInfoCache = {};
						end
						
						gOutfitter_AmmoSlotInfoCache.Name = pName;
						gOutfitter_AmmoSlotInfoCache.Texture = pTexture;
						gOutfitter_AmmoSlotInfoCache.ItemInfo = vItemInfo;
						
						return vItemInfo;
					end
				end
			end -- for vBagSlotIndex
		end -- if vNumBagSlots
	end -- for vBagIndex
	
	return nil;
end

function Outfitter_GetInventoryItemInfo(pInventorySlot)
	local	vSlotID = GetInventorySlotInfo(pInventorySlot);
	local	vItemLink = GetInventoryItemLink("player", vSlotID);
	
	-- GetInventoryItemLink doesn't work for the ammo slot, so instead get the icon
	-- for the slot and then search for a matching icon in the bags
	
	if vItemLink == nil
	and pInventorySlot == "AmmoSlot" then
		OutfitterTooltip:SetOwner(OutfitterFrame, "ANCHOR_BOTTOMRIGHT", 0, 0);
		OutfitterTooltip:SetInventoryItem("player", vSlotID);
		
		if not OutfitterTooltipTextLeft1:IsShown() then
			OutfitterTooltip:Hide();
			return nil;
		end
		
		local	vAmmoItemName = OutfitterTooltipTextLeft1:GetText();
		
		OutfitterTooltip:Hide();
		
		local	vAmmoItemTexture = GetInventoryItemTexture("player", vSlotID);
		
		return Outfitter_FindAmmoSlotItem(vAmmoItemName, vAmmoItemTexture);
	end
	
	local	vItemInfo = Outfitter_GetItemInfoFromLink(vItemLink);
	
	if not vItemInfo then
		return nil;
	end
	
	vItemInfo.Quality = GetInventoryItemQuality("player", vSlotID);
	vItemInfo.Texture = GetInventoryItemTexture("player", vSlotID);
	
	return vItemInfo;
end

function Outfitter_GetItemInfoFromLink(pItemLink)
	if not pItemLink then
		return nil;
	end
	-- |cff1eff00|Hitem:1465:803:0:0|h[Tigerbane]|h|r
	-- |cff1eff00|Hitem:1465:803:0:0|h[Tigerbane]|h|r
	-- |(hex code for item color)|Hitem:(item ID code):(enchant code):(added stats code):0|h[(item name)]|h|r
	
	local	vStartIndex, vEndIndex, vLinkColor, vItemCode, vItemEnchantCode, vItemSubCode, vUnknownCode, vItemName = strfind(pItemLink, "|(%x+)|Hitem:(%d+):(%d+):(%d+):(%d+)|h%[([^%]]+)%]|h|r");
	
	if not vStartIndex then
		return nil;
	end
	
	vItemCode = tonumber(vItemCode);
	vItemSubCode = tonumber(vItemSubCode);
	vItemEnchantCode = tonumber(vItemEnchantCode);
	
	local	vItemFamilyName,
			vItemLink,
			vItemQuality,
			vItemLevel,
			vItemType,
			vItemSubType,
			vItemCount,
			vItemInvType = GetItemInfo(vItemCode);
	
	local	vItemInfo =
	{
		Code = vItemCode,
		SubCode = vItemSubCode,
		Name = vItemName,
		EnchantCode = vItemEnchantCode,
		Level = vItemLevel,
	};
	
	-- Just return if there's no inventory type
	
	if not vItemInvType
	or vItemInvType == "" then
		return vItemInfo;
	end
	
	-- Just return if we don't know anything about the inventory type
	
	local	vInvTypeInfo = Outfitter_cInvTypeToSlotName[vItemInvType];
	
	if not vInvTypeInfo then
		Outfitter_ErrorMessage("Outfitter error: Unknown slot type "..vItemInvType.." for item "..vItemName);
		return vItemInfo;
	end
	
	-- Get the slot name
	
	if not vInvTypeInfo.SlotName then
		Outfitter_ErrorMessage("Unknown slot name for inventory type "..vItemInvType);
		return vItemInfo;
	end
	
	vItemInfo.ItemSlotName = vInvTypeInfo.SlotName;
	vItemInfo.MetaSlotName = vInvTypeInfo.MetaSlotName;
	
	-- Return the info
	
	return vItemInfo;
end

function Outfitter_CreateNewOutfit()
	OutfitterNameOutfit_Open(nil);
end

function Outfitter_NewEmptyOutfit(pName)
	return {Name = pName, Items = {}};
end

function Outfitter_IsEmptyOutfit(pOutfit)
	return Outfitter_ArrayIsEmpty(pOutfit.Items);
end

function Outfitter_NewNakedOutfit(pName)
	local	vOutfit = Outfitter_NewEmptyOutfit(pName);
	
	for _, vInventorySlot in Outfitter_cSlotNames do
		Outfitter_AddOutfitItem(vOutfit, vInventorySlot, 0, 0, "", 0);
	end
	
	return vOutfit;
end

function Outfitter_AddOutfitItem(pOutfit, pSlotName, pItemCode, pItemSubCode, pItemName, pItemEnchantCode)
	pOutfit.Items[pSlotName] = {Code = pItemCode, SubCode = pItemSubCode, Name = pItemName, EnchantCode = pItemEnchantCode};
end

function Outfitter_AddOutfitStatItem(pOutfit, pSlotName, pItemCode, pItemSubCode, pItemName, pItemEnchantCode, pStatID, pStatValue)
	if not pSlotName then
		Outfitter_ErrorMessage("AddOutfitStatItem: SlotName is nil for "..pItemName);
		return;
	end
	
	if not pStatID then
		Outfitter_ErrorMessage("AddOutfitStatItem: StatID is nil for "..pItemName);
		return;
	end
	
	Outfitter_AddOutfitItem(pOutfit, pSlotName, pItemCode, pItemSubCode, pItemName, pItemEnchantCode);
	pOutfit.Items[pSlotName][pStatID] = pStatValue;
end

function Outfitter_AddOutfitStatItemIfBetter(pOutfit, pSlotName, pItemCode, pItemSubCode, pItemName, pItemEnchantCode, pStatID, pStatValue)
	local	vCurrentItem = pOutfit.Items[pSlotName];
	local	vAlternateSlotName = Outfitter_cHalfAlternateStatSlot[pSlotName];
	
	if not vCurrentItem
	or not vCurrentItem[pStatID]
	or vCurrentItem[pStatID] < pStatValue then
		-- If we're bumping the current item, see if it should be moved to the alternate slot
		
		if vCurrentItem
		and vCurrentItem[pStatID]
		and vAlternateSlotName then
			Outfitter_AddOutfitStatItemIfBetter(pOutfit, vAlternateSlotName, vCurrentItem.Code, vCurrentItem.SubCode, vCurrentItem.Name, vCurrentItem.EnchantCode, pStatID, vCurrentItem[pStatID])
		end
		
		Outfitter_AddOutfitStatItem(pOutfit, pSlotName, pItemCode, pItemSubCode, pItemName, pItemEnchantCode, pStatID, pStatValue);
	else
		if not vAlternateSlotName then
			return;
		end
		
		return Outfitter_AddOutfitStatItemIfBetter(pOutfit, vAlternateSlotName, pItemCode, pItemSubCode, pItemName, pItemEnchantCode, pStatID, pStatValue);
	end
end

function Outfitter_AddStats(pItem1, pItem2, pStatID)
	local	vStat = 0;
	
	if pItem1
	and pItem1[pStatID] then
		vStat = pItem1[pStatID];
	end
	
	if pItem2
	and pItem2[pStatID] then
		vStat = vStat + pItem2[pStatID];
	end
	
	return vStat;
end

function Outfitter_CollapseMetaSlotsIfBetter(pOutfit, pStatID)
	-- Compare the weapon slot with the 1H/OH slots
	
	local	vWeapon0Item = pOutfit.Items.Weapon0Slot;
	local	vWeapon1Item = pOutfit.Items.Weapon1Slot;
	
	if vWeapon0Item or vWeapon1Item then
		-- Try the various combinations of MH/OH/W0/W1
		
		local	v1HItem = pOutfit.Items.MainHandSlot;
		local	vOHItem = pOutfit.Items.SecondaryHandSlot;
		
		local	vCombinations =
		{
			{MainHand = v1HItem, SecondaryHand = vOHItem, AllowEmptyMainHand = true},
			{MainHand = v1HItem, SecondaryHand = vWeapon0Item, AllowEmptyMainHand = false},
			{MainHand = v1HItem, SecondaryHand = vWeapon1Item, AllowEmptyMainHand = false},
			{MainHand = vWeapon0Item, SecondaryHand = vOHItem, AllowEmptyMainHand = true},
			{MainHand = vWeapon1Item, SecondaryHand = vOHItem, AllowEmptyMainHand = true},
			{MainHand = vWeapon0Item, SecondaryHand = vWeapon1Item, AllowEmptyMainHand = false},
		};
		
		local	vBestCombinationIndex = nil;
		local	vBestCombinationValue = nil;
		
		for vIndex = 1, 6 do
			local	vCombination = vCombinations[vIndex];
			
			-- Ignore combinations where the main hand is empty if
			-- that's not allowed in this combinations
			
			if vCombination.AllowEmptyMainHand
			or vCombination.MainHand then
				local	vCombinationValue = Outfitter_AddStats(vCombination.MainHand, vCombination.SecondaryHand, pStatID);
				
				if not vBestCombinationIndex
				or vCombinationValue > vBestCombinationValue then
					vBestCombinationIndex = vIndex;
					vBestCombinationValue = vCombinationValue;
				end
			end
		end
		
		if vBestCombinationIndex then
			local	vCombination = vCombinations[vBestCombinationIndex];
			
			pOutfit.Items.MainHandSlot = vCombination.MainHand;
			pOutfit.Items.SecondaryHandSlot = vCombination.SecondaryHand;
		end
		
		pOutfit.Items.Weapon0Slot = nil;
		pOutfit.Items.Weapon1Slot = nil;
	end
	
	-- Compare the 2H slot with the 1H/OH slots
	
	local	v2HItem = pOutfit.Items.TwoHandSlot;
	
	if v2HItem then
		local	v1HItem = pOutfit.Items.MainHandSlot;
		local	vOHItem = pOutfit.Items.SecondaryHandSlot;
		local	v1HOHTotalStat = Outfitter_AddStats(v1HItem, vOHItem, pStatID);
		
		if v2HItem[pStatID]
		and v2HItem[pStatID] > v1HOHTotalStat then
			pOutfit.Items.MainHandSlot = v2HItem;
			pOutfit.Items.SecondaryHandSlot = nil;
		end
		
		pOutfit.Items.TwoHandSlot = nil;
	end
end

function Outfitter_RemoveOutfitItem(pOutfit, pSlotName)
	pOutfit.Items[pSlotName] = nil;
end

function Outfitter_GetInventoryOutfit(pName, pOutfit)
	local	vOutfit;
	
	if pOutfit then
		vOutfit = pOutfit;
	else
		vOutfit = Outfitter_NewEmptyOutfit(pName);
	end
	
	for _, vInventorySlot in Outfitter_cSlotNames do
		local	vItemInfo = Outfitter_GetInventoryItemInfo(vInventorySlot);
		
		-- To avoid extra memory operations, only update the item if it's different
		
		local	vExistingItem = vOutfit.Items[vInventorySlot];
		
		if not vItemInfo then
			if not vExistingItem
			or vExistingItem.Code ~= 0 then
				Outfitter_AddOutfitItem(vOutfit, vInventorySlot, 0, 0, "", 0);
			end
		else
			if not vExistingItem
			or vExistingItem.Code ~= vItemInfo.Code
			or vExistingItem.SubCode ~= vItemInfo.SubCode
			or vExistingItem.EnchantCode ~= vItemInfo.EnchantCode then
				Outfitter_AddOutfitItem(vOutfit, vInventorySlot, vItemInfo.Code, vItemInfo.SubCode, vItemInfo.Name, vItemInfo.EnchantCode);
			end
		end
	end
	
	return vOutfit;
end

function Outfitter_UpdateOutfitFromInventory(pOutfit, pNewItemsOutfit)
	if not pNewItemsOutfit then
		return;
	end
	
	for vInventorySlot, vItem in pNewItemsOutfit.Items do
		-- Only update slots which aren't in an unknown state
		
		local	vCheckbox = getglobal("OutfitterEnable"..vInventorySlot);
		
		if not vCheckbox:GetChecked()
		or not vCheckbox.IsUnknown then
			pOutfit.Items[vInventorySlot] = vItem;
			Outfitter_NoteMessage(format(Outfitter_cAddingItem, vItem.Name, pOutfit.Name));
			Outfitter_UpdateOutfitCategory(pOutfit);
		end
	end
	
	-- Add the new items to the current compiled outfit
	
	for vInventorySlot, vItem in pNewItemsOutfit.Items do
		gOutfitter_ExpectedOutfit.Items[vInventorySlot] = pNewItemsOutfit.Items[vInventorySlot];
	end
	
	gOutfitter_DisplayIsDirty = true;
end

function Outfitter_SubtractOutfit(pOutfit1, pOutfit2, pCheckAlternateSlots)
	local	vEquippableItems = OutfitterItemList_GetEquippableItems();
	
	-- Remove items from pOutfit1 if they match the item in pOutfit2
	
	for _, vInventorySlot in Outfitter_cSlotNames do
		local	vItem1 = pOutfit1.Items[vInventorySlot];
		local	vItem2 = pOutfit2.Items[vInventorySlot];
		
		if OutfitterItemList_ItemsAreSame(vEquippableItems, vItem1, vItem2) then
			pOutfit1.Items[vInventorySlot] = nil;
		elseif pCheckAlternateSlots then
			local	vAlternateSlotName = Outfitter_cFullAlternateStatSlot[vInventorySlot];
			
			vItem2 = pOutfit2.Items[vAlternateSlotName];
			
			if OutfitterItemList_ItemsAreSame(vEquippableItems, vItem1, vItem2) then
				pOutfit1.Items[vInventorySlot] = nil;
			end
		end
	end
end

function Outfitter_GetNewItemsOutfit(pPreviousOutfit)
	-- Get the current outfit and the list
	-- of equippable items
	
	gOutfitter_CurrentInventoryOutfit = Outfitter_GetInventoryOutfit(gOutfitter_CurrentInventoryOutfit);
	
	local	vEquippableItems = OutfitterItemList_GetEquippableItems();
	
	-- Create a temporary outfit from the differences
	
	local	vNewItemsOutfit = Outfitter_NewEmptyOutfit();
	local	vOutfitHasItems = false;
	
	for _, vInventorySlot in Outfitter_cSlotNames do
		local	vCurrentItem = gOutfitter_CurrentInventoryOutfit.Items[vInventorySlot];
		local	vPreviousItem = pPreviousOutfit.Items[vInventorySlot];
		local	vSkipSlot = false;
		
		if vInventorySlot == "SecondaryHandSlot" then
			local	vMainHandItem = pPreviousOutfit.Items["MainHandSlot"];
			
			if vMainHandItem
			and vMainHandItem.MetaSlotName == "TwoHandSlot" then
				vSkipSlot = true;
			end
		elseif vInventorySlot == "AmmoSlot"
		and (not vCurrentItem or vCurrentItem.Code == 0) then
			vSkipSlot = true;
		end
		
		if not vSkipSlot
		and not OutfitterItemList_InventorySlotContainsItem(vEquippableItems, vInventorySlot, vPreviousItem) then
			vNewItemsOutfit.Items[vInventorySlot] = vCurrentItem;
			vOutfitHasItems = true;
		end
	end
	
	if not vOutfitHasItems then
		return nil;
	end
	
	return vNewItemsOutfit, gOutfitter_CurrentInventoryOutfit;
end

function Outfitter_UpdateTemporaryOutfit(pNewItemsOutfit)
	-- Just return if nothing has changed
	
	if not pNewItemsOutfit then
		return;
	end
	
	-- Merge the new items with an existing temporary outfit
	
	local	vTemporaryOutfit = OutfitterStack_GetTemporaryOutfit();
	local	vUsingExistingTempOutfit = false;
	
	if vTemporaryOutfit then
	
		for vInventorySlot, vItem in pNewItemsOutfit.Items do
			vTemporaryOutfit.Items[vInventorySlot] = vItem;
		end
		
		vUsingExistingTempOutfit = true;
	
	-- Otherwise add the new items as the temporary outfit
	
	else
		vTemporaryOutfit = pNewItemsOutfit;
	end
	
	-- Subtract out items which are expected to be in the outfit
	
	local	vExpectedOutfit = Outfitter_GetExpectedOutfit(vTemporaryOutfit);
	
	Outfitter_SubtractOutfit(vTemporaryOutfit, vExpectedOutfit);
	
	if Outfitter_IsEmptyOutfit(vTemporaryOutfit) then
		if vUsingExistingTempOutfit then
			Outfitter_RemoveOutfit(vTemporaryOutfit);
		end
	else
		if not vUsingExistingTempOutfit then
			OutfitterStack_AddOutfit(vTemporaryOutfit);
		end
	end
	
	-- Add the new items to the current compiled outfit
	
	for vInventorySlot, vItem in pNewItemsOutfit.Items do
		gOutfitter_ExpectedOutfit.Items[vInventorySlot] = vItem;
	end
end

function Outfitter_SetSlotEnable(pSlotName, pEnable)
	if not gOutfitter_SelectedOutfit then
		return;
	end
	
	if pEnable then
		local	vItemInfo = Outfitter_GetInventoryItemInfo(pSlotName);
		
		if vItemInfo then
			gOutfitter_SelectedOutfit.Items[pSlotName] = {Code = vItemInfo.Code, SubCode = vItemInfo.SubCode, Name = vItemInfo.Name, EnchantCode = vItemInfo.EnchantCode};
		else
			gOutfitter_SelectedOutfit.Items[pSlotName] = {Code = 0, SubCode = 0, Name = "", EnchantCode = 0};
		end
	else
		gOutfitter_SelectedOutfit.Items[pSlotName] = nil;
	end
	
	gOutfitter_DisplayIsDirty = true;
end

function Outfitter_GetSpecialOutfit(pSpecialID)
	for vOutfitIndex, vOutfit in gOutfitter_Settings.Outfits.Special do
		if vOutfit.SpecialID == pSpecialID then
			return vOutfit;
		end
	end
	
	return nil;
end

function Outfitter_GetPlayerAuraStates()
	local		vAuraStates =
	{
		Dining = false,
		Shadowform = false,
		Riding = false,
		GhostWolf = false,
		Feigning = false,
		Evocate = false,
		Monkey = false,
		Hawk = false,
		Cheetah = false,
		Pack = false,
		Beast = false,
		Wild = false
	};
	
	local		vBuffIndex = 1;
	
	while true do
		vTexture = UnitBuff("player", vBuffIndex);
		
		if not vTexture then
			return vAuraStates;
		end
		
		local	vStartIndex, vEndIndex, vTextureName = string.find(vTexture, "([^%\\]*)$");
		
		--
		
		local	vSpecialID = gOutfitter_AuraIconSpecialID[vTextureName];
		
		if vSpecialID then
			vAuraStates[vSpecialID] = true;
		
		--
		
		elseif not vAuraStates.Dining
		and string.find(vTextureName, "INV_Drink") then
			vAuraStates.Dining = true;
		
		--
		
		else
			local	vTextLine1, vTextLine2 = Outfitter_GetBuffTooltipText(vBuffIndex);
			
			if vTextLine1 then
				local	vSpecialID = gOutfitter_SpellNameSpecialID[vTextLine1];
				
				if vSpecialID then
					vAuraStates[vSpecialID] = true;
				
				elseif vTextLine2
					and (
						string.find(vTextLine2, Outfitter_cMountSpeedFormat) or --Mount fix by Red Mage Joe
						string.find(vTextLine2, "Riding") or
						string.find(vTextLine2, "Slow and steady...")
					) then
					vAuraStates.Riding = true;
				end
			end
		end
		
		vBuffIndex = vBuffIndex + 1;
	end
end

function Outfitter_GetBuffTooltipText(pBuffIndex)
	OutfitterTooltip:SetOwner(OutfitterFrame, "ANCHOR_BOTTOMRIGHT", 0, 0);
	OutfitterTooltip:SetUnitBuff("player", pBuffIndex);
	
	local	vText1, vText2;
	
	if OutfitterTooltipTextLeft1:IsShown() then
		vText1 = OutfitterTooltipTextLeft1:GetText();
	end -- if IsShown

	if OutfitterTooltipTextLeft2:IsShown() then
		vText2 = OutfitterTooltipTextLeft2:GetText();
	end -- if IsShown

	OutfitterTooltip:Hide();
	
	return vText1, vText2;
end

function Outfitter_UpdateAuraStates()
	-- Check for special aura outfits
	
	local	vAuraStates = Outfitter_GetPlayerAuraStates();
	
	for vSpecialID, vIsActive in vAuraStates do
		if vSpecialID == "Feigning" then
			gOutfitter_IsFeigning = vIsActive;
		else
			if not gOutfitter_SpecialState[vSpecialID] then
				gOutfitter_SpecialState[vSpecialID] = false;
			end
			
			if gOutfitter_SpecialState[vSpecialID] ~= vIsActive then
				gOutfitter_SpecialState[vSpecialID] = vIsActive;
				Outfitter_SetSpecialOutfitEnabled(vSpecialID, vIsActive);
			end
		end
	end
	
	-- As of 1.12 aura changes are the only way to detect shapeshifts, so update those too
	
	Outfitter_UpdateShapeshiftState();
end

function Outfitter_UpdateShapeshiftState()
	local	vNumForms = GetNumShapeshiftForms();
	
	for vIndex = 1, vNumForms do
		local	vTexture, vName, vIsActive, vIsCastable = GetShapeshiftFormInfo(vIndex);
		local	vSpecialID = Outfitter_cShapeshiftSpecialIDs[vName];
		
		if vSpecialID then
			if not vIsActive then
				vIsActive = false;
			end
			
			if gOutfitter_SpecialState[vSpecialID.ID] == nil then
				gOutfitter_SpecialState[vSpecialID.ID] = Outfitter_WearingSpecialOutfit(vSpecialID.ID);
			end
			
			if gOutfitter_SpecialState[vSpecialID.ID] ~= vIsActive then
				gOutfitter_SpecialState[vSpecialID.ID] = vIsActive;
				Outfitter_SetSpecialOutfitEnabled(vSpecialID.ID, vIsActive);
			end
		end
	end
end

function Outfitter_SetSpecialOutfitEnabled(pSpecialID, pEnable)
	local	vOutfit = Outfitter_GetSpecialOutfit(pSpecialID);

	if not vOutfit
	or vOutfit.Disabled
	or (pEnable and vOutfit.BGDisabled and Outfitter_InBattlegroundZone())
	or (pEnable and vOutfit.InstDisabled and Outfitter_InInstanceZone()) then
		return;
	end

	if pEnable then
		-- Start monitoring health and mana if it's the dining outfit
		
		if pSpecialID == "Dining" then
			Outfitter_ResumeEvent(OutfitterFrame, "UNIT_HEALTH");
			Outfitter_ResumeEvent(OutfitterFrame, "UNIT_MANA");
		end
		
		--
		
		local	vWearBelowOutfit = nil;
		
		-- If it's the ArgentDawn outfit, wear it below the
		-- riding outfit.  Once the player dismounts then
		-- overlapping items from the ArgentDawn outfit will equip.
		-- This will prevent the Argent Dawn trinket from interfering
		-- with the carrot trinket when riding into the plaguelands
		
		if pSpecialID == "ArgentDawn" then
			vWearBelowOutfit = Outfitter_GetSpecialOutfit("Riding");
		end
		
		--
		
		Outfitter_WearOutfit(vOutfit, "Special", vWearBelowOutfit);
	else
		Outfitter_RemoveOutfit(vOutfit);
	end
end

function Outfitter_WearingSpecialOutfit(pSpecialID)
	for vIndex, vOutfit in gOutfitter_OutfitStack do
		if vOutfit.SpecialID == pSpecialID then
			return true, vIndex;
		end
	end
end

function Outfitter_UpdateZone()
	local	vCurrentZone = GetZoneText();
	local	vPVPType, vFactionName, vIsArena = GetZonePVPInfo();
	
	if vCurrentZone == gOutfitter_CurrentZone then
		return;
	end
	
	gOutfitter_CurrentZone = vCurrentZone;
	
	local	vZoneSpecialIDMap = Outfitter_cZoneSpecialIDMap[vCurrentZone];
	local	vSpecialZoneStates = {};
	
	if vZoneSpecialIDMap then
		for _, vZoneSpecialID in vZoneSpecialIDMap do
			if vZoneSpecialID ~= "City" or vPVPType ~= "hostile" then
				vSpecialZoneStates[vZoneSpecialID] = true;
			end
		end
	end
	
	for _, vSpecialID in Outfitter_cZoneSpecialIDs do
		local	vIsActive = vSpecialZoneStates[vSpecialID];
		
		if vIsActive == nil then
			vIsActive = false;
		end
		
		local	vCurrentIsActive = gOutfitter_SpecialState[vSpecialID];
		
		if vCurrentIsActive == nil then
			vCurrentIsActive = Outfitter_WearingSpecialOutfit(vSpecialID);
			gOutfitter_SpecialState[vSpecialID] = vCurrentIsActive;
		end
		
		if vCurrentIsActive ~= vIsActive then
			gOutfitter_SpecialState[vSpecialID] = vIsActive;
			Outfitter_SetSpecialOutfitEnabled(vSpecialID, vIsActive);
		end
	end
end

function Outfitter_InBattlegroundZone()
	local	vZoneSpecialIDMap = Outfitter_cZoneSpecialIDMap[gOutfitter_CurrentZone];
	
	return vZoneSpecialIDMap and vZoneSpecialIDMap[1] == "Battleground";
end

function Outfitter_InInstanceZone()
	local	vZoneSpecialIDMap = Outfitter_cZoneSpecialIDMap[gOutfitter_CurrentZone];
	
	return vZoneSpecialIDMap and vZoneSpecialIDMap[1] == "Instance";
end

function Outfitter_SetAllSlotEnables(pEnable)
	for _, vInventorySlot in Outfitter_cSlotNames do
		Outfitter_SetSlotEnable(vInventorySlot, pEnable);
	end
	
	Outfitter_UpdateOutfitCategory(gOutfitter_SelectedOutfit);
	Outfitter_Update(true);
end

function Outfitter_OutfitIsComplete(pOutfit, pIgnoreAmmoSlot)
	for _, vInventorySlot in Outfitter_cSlotNames do
		if not pOutfit.Items[vInventorySlot]
		and (not pIgnoreAmmoSlot or vInventorySlot ~= "AmmoSlot") then
			return false;
		end
	end
	
	return true;
end

function Outfitter_CalculateOutfitCategory(pOutfit)
	local	vIgnoreAmmoSlot = UnitHasRelicSlot("player");

	if Outfitter_OutfitIsComplete(pOutfit, vIgnoreAmmoSlot) then
		return "Complete";
	elseif pOutfit.IsAccessory then
		return "Accessory";
	else
		return "Partial";
	end
end

function Outfitter_UpdateOutfitCategory(pOutfit)
	if not pOutfit then
		return;
	end
	
	local	vTargetCategoryID = Outfitter_CalculateOutfitCategory(pOutfit);
	local	vOutfitCategoryID, vOutfitIndex = Outfitter_FindOutfit(pOutfit);
	
	-- Don't move special outfits around
	
	if vOutfitCategoryID == "Special" then
		return;
	end
	
	-- Move the outfit if necessary
	
	if vTargetCategoryID ~= vOutfitCategoryID then
		table.remove(gOutfitter_Settings.Outfits[vOutfitCategoryID], vOutfitIndex);
		Outfitter_AddOutfit(pOutfit);
	end
end

function Outfitter_DeleteOutfit(pOutfit)
	local	vWearingOutfit = Outfitter_WearingOutfit(pOutfit);
	local	vOutfitCategoryID, vOutfitIndex = Outfitter_FindOutfit(pOutfit);
	
	if not vOutfitCategoryID then
		return;
	end
	
	-- Delete the outfit
	
	table.remove(gOutfitter_Settings.Outfits[vOutfitCategoryID], vOutfitIndex);
	
	-- Deselect the outfit
	
	if pOutfit == gOutfitter_SelectedOutfit then
		Outfitter_ClearSelection();
	end
	
	-- Remove the outfit if it's being worn
	
	Outfitter_RemoveOutfit(pOutfit);
	
	--
	
	gOutfitter_DisplayIsDirty = true;
end

function Outfitter_AddOutfit(pOutfit)
	local	vCategoryID;
	
	if pOutfit.SpecialID then
		vCategoryID = "Special"
	else
		vCategoryID = Outfitter_CalculateOutfitCategory(pOutfit);
	end
	
	if not gOutfitter_Settings.Outfits then
		gOutfitter_Settings.Outfits = {};
	end
	
	if not gOutfitter_Settings.Outfits[vCategoryID] then
		gOutfitter_Settings.Outfits[vCategoryID] = {};
	end
	
	table.insert(gOutfitter_Settings.Outfits[vCategoryID], pOutfit);
	pOutfit.CategoryID = vCategoryID;
	
	gOutfitter_DisplayIsDirty = true;
	
	return vCategoryID;
end

function Outfitter_SlotEnableClicked(pCheckbox, pButton)
	-- If the user is attempting to drop an item put it in the slot for them
	
	if CursorHasItem() then
		local	vSlotID, vEmptySlotTexture = GetInventorySlotInfo(pCheckbox.SlotName);
		PickupInventoryItem(vSlotID);
		return;
	end
	
	--
	
	local	vChecked = pCheckbox:GetChecked();
	
	if pCheckbox.IsUnknown then
		pCheckbox.IsUnknown = false;
		pCheckbox:SetCheckedTexture("Interface\\Buttons\\UI-CheckBox-Check");
		vChecked = true;
	end
	
	Outfitter_SetSlotEnable(pCheckbox.SlotName, vChecked);
	Outfitter_UpdateOutfitCategory(gOutfitter_SelectedOutfit);
	Outfitter_Update(true);
end

function Outfitter_FindMultipleItemLocation(pItems, pEquippableItems)
	for vListIndex, vListItem in pItems do
		local	vItem = OutfitterItemList_FindItemOrAlt(pEquippableItems, vListItem);
		
		if vItem then
			return vItem, vListItem;
		end
	end
	
	return nil, nil;
end

function Outfitter_FindAndAddItemsToOutfit(pOutfit, pSlotName, pItems, pEquippableItems)
	vItemLocation, vItem = Outfitter_FindMultipleItemLocation(pItems, pEquippableItems);
	
	if vItemLocation then
		local	vInventorySlot = pSlotName;
		
		if not vInventorySlot then
			vInventorySlot = vItemLocation.ItemSlotName;
		end
		
		Outfitter_AddOutfitItem(pOutfit, vInventorySlot, vItem.Code, vItem.SubCode, vItem.Name, vItem.EnchantCOde);
	end
end

function Outfitter_AddItemsWithStatToOutfit(pOutfit, pStatID, pEquippableItems)
	local	vItemStats;
	
	if not pEquippableItems then
		return;
	end
	
	for vInventorySlot, vItems in pEquippableItems.ItemsBySlot do
		for vIndex, vItem in vItems do
			local	vStatValue = vItem.Stats[pStatID];
			
			if vStatValue then
				local	vSlotName = vItem.MetaSlotName;
				
				if not vSlotName then
					vSlotName = vItem.ItemSlotName;
				end
				
				Outfitter_AddOutfitStatItemIfBetter(pOutfit, vSlotName, vItem.Code, vItem.SubCode, vItem.Name, vItem.EnchantCode, pStatID, vStatValue);
			end
		end
	end
	
	-- Collapse the meta slots (currently just 2H vs. 1H/OH)
	
	Outfitter_CollapseMetaSlotsIfBetter(pOutfit, pStatID);
end

function Outfitter_IsInitialized()
	return gOutfitter_Initialized;
end

function Outfitter_Initialize()
	if gOutfitter_Initialized then
		return;
	end
	
	--
	
	if not gOutfitter_Settings then
		gOutfitter_Settings = {};
		gOutfitter_Settings.Version = 7;
		gOutfitter_Settings.Options = {};
		gOutfitter_Settings.LastOutfitStack = {};
		gOutfitter_Settings.HideHelm = {};
		gOutfitter_Settings.HideCloak = {};
	end
	
	if not gOutfitter_Settings.HideHelm then
		gOutfitter_Settings.HideHelm = {};
	end
	
	if not gOutfitter_Settings.HideCloak then
		gOutfitter_Settings.HideCloak = {};
	end
	
	--
	
	Outfitter_InitDebugging();
	
	-- Initialize the outfits and outfit stack
	
	gOutfitter_CurrentOutfit = Outfitter_GetInventoryOutfit();
	
	if not gOutfitter_Settings.Outfits then
		Outfitter_InitializeOutfits();
	end
	
	Outfitter_CheckDatabase();
	
	Outfitter_InitializeSpecialOccassionOutfits(); -- Make sure the special occassion outfits are intact
	                                               -- since the user has no way of creating them himself
	OutfitterStack_RestoreSavedStack();
	
	-- Set the minimap button
	
	if gOutfitter_Settings.Options.HideMinimapButton then
		OutfitterMinimapButton:Hide();
	else
		OutfitterMinimapButton:Show();
	end
	
	if not gOutfitter_Settings.Options.MinimapButtonAngle then
		gOutfitter_Settings.Options.MinimapButtonAngle = -1.5708;
	end
	
	OutfitterMinimapButton_SetPositionAngle(gOutfitter_Settings.Options.MinimapButtonAngle);
	
	-- Hook QuickSlots into the paper doll frame
	
	Outfitter_HookPaperDollFrame();
	
	-- Done initializing
	
	gOutfitter_Initialized = true;

	-- Make sure the outfit state is good
	
	Outfitter_SetSpecialOutfitEnabled("Riding", false);
	Outfitter_SetSpecialOutfitEnabled("Spirit", false);
	Outfitter_UpdateAuraStates();
	
	Outfitter_ResumeLoadScreenEvents();
	
	Outfitter_DispatchOutfitEvent("OUTFITTER_INIT")
end

function Outfitter_InitializeOutfits()
	local	vOutfit, vItemLocation, vItem;
	local	vEquippableItems = OutfitterItemList_GetEquippableItems(true);
	
	-- Create the outfit categories
	
	gOutfitter_Settings.Outfits = {};
	
	for vCategoryIndex, vCategoryID in gOutfitter_cCategoryOrder do
		gOutfitter_Settings.Outfits[vCategoryID] = {};
	end

	-- Create the normal outfit using the current
	-- inventory and set it as the currently equipped outfit
	
	vOutfit = Outfitter_GetInventoryOutfit(Outfitter_cNormalOutfit);
	Outfitter_AddOutfit(vOutfit);
	gOutfitter_Settings.LastOutfitStack = {{Name = Outfitter_cNormalOutfit}};
	gOutfitter_OutfitStack = {vOutfit};
	
	-- Create the naked outfit
	
	vOutfit = Outfitter_NewNakedOutfit(Outfitter_cNakedOutfit);
	Outfitter_AddOutfit(vOutfit);
	
	-- Generate the smart outfits
	
	for vSmartIndex, vSmartOutfit in Outfitter_cSmartOutfits do
		vOutfit = Outfitter_GenerateSmartOutfit(vSmartOutfit.Name, vSmartOutfit.StatID, vEquippableItems);
		
		if vOutfit then
			vOutfit.IsAccessory = vSmartOutfit.IsAccessory;
			Outfitter_AddOutfit(vOutfit);
		end
	end
	
	Outfitter_InitializeSpecialOccassionOutfits();
end

function Outfitter_CreateEmptySpecialOccassionOutfit(pSpecialID, pName)
	vOutfit = Outfitter_GetSpecialOutfit(pSpecialID);
	
	if not vOutfit then
		vOutfit = Outfitter_NewEmptyOutfit(pName);
		vOutfit.SpecialID = pSpecialID;
		
		Outfitter_AddOutfit(vOutfit);
	end
end

function Outfitter_InitializeSpecialOccassionOutfits()
	local	vEquippableItems = OutfitterItemList_GetEquippableItems(true);
	local	vOutfit;
	
	-- Find an argent dawn trinket and set the argent dawn outfit
	
	vOutfit = Outfitter_GetSpecialOutfit("ArgentDawn");
	
	if not vOutfit then
		vOutfit = Outfitter_GenerateSmartOutfit(Outfitter_cArgentDawnOutfit, "ArgentDawn", vEquippableItems, true);
		vOutfit.SpecialID = "ArgentDawn";
		Outfitter_AddOutfit(vOutfit);
	end
	
	-- Find riding items
	
	vOutfit = Outfitter_GetSpecialOutfit("Riding");
	
	if not vOutfit then
		vOutfit = Outfitter_GenerateSmartOutfit(Outfitter_cRidingOutfit, "Riding", vEquippableItems, true);
		vOutfit.SpecialID = "Riding";
		vOutfit.BGDisabled = true;
		Outfitter_AddOutfit(vOutfit);
	end
	
	-- Create the dining outfit
	
	Outfitter_CreateEmptySpecialOccassionOutfit("Dining", Outfitter_cDiningOutfit);
	
	-- Create the Battlegrounds outfits
	
	Outfitter_CreateEmptySpecialOccassionOutfit("Battleground", Outfitter_cBattlegroundOutfit);
	Outfitter_CreateEmptySpecialOccassionOutfit("AB", Outfitter_cABOutfit);
	Outfitter_CreateEmptySpecialOccassionOutfit("AV", Outfitter_cAVOutfit);
	Outfitter_CreateEmptySpecialOccassionOutfit("WSG", Outfitter_cWSGOutfit);
	Outfitter_CreateEmptySpecialOccassionOutfit("BR", Outfitter_cBROutfit);
	Outfitter_CreateEmptySpecialOccassionOutfit("SGV", Outfitter_cSGVOutfit);
	
	-- Create the city outfit
	
	Outfitter_CreateEmptySpecialOccassionOutfit("City", Outfitter_cCityOutfit);
	
	-- Create class-specific outfits
	
	Outfitter_InitializeClassOutfits();
end

function Outfitter_InitializeClassOutfits()
	local	vClassName = Outfitter_cNormalizedClassName[UnitClass("player")];
	local	vOutfits = Outfitter_cClassSpecialOutfits[vClassName];
	
	if not vOutfits then
		return;
	end
	
	for vIndex, vOutfitInfo in vOutfits do
		Outfitter_CreateEmptySpecialOccassionOutfit(vOutfitInfo.SpecialID, vOutfitInfo.Name);
	end
end

function Outfitter_IsStatText(pText)
	for vStatIndex, vStatInfo in Outfitter_cItemStatFormats do
		local	vStartIndex, vEndIndex, vValue = string.find(pText, vStatInfo.Format);
		
		if vStartIndex then
			vValue = tonumber(vValue);
			
			if not vValue then
				vValue = vStatInfo.Value;
			end
			
			if not vValue then
				vValue = 0;
			end
			
			return vStatInfo.Types, vValue;
		end
	end
	
	return nil, nil;
end

function Outfitter_GetItemStatsFromTooltip(pTooltip, pDistribution)
	local	vStats = {};
	local	vTooltipName = pTooltip:GetName();
	
	for vLineIndex = 1, 30 do
		local	vLeftText = getglobal(vTooltipName.."TextLeft"..vLineIndex):GetText();
		-- local	vRightText = getglobal(vTooltipName.."TextRight"..vLineIndex):GetText();
		
		if vLeftText then
			-- Check for the start of the set bonus section
			
			local	vStartIndex, vEndIndex, vValue = string.find(vLeftText, "%(%d/%d%)");
			
			if vStartIndex then
				break;
			end
			
			--
			
			for vStatString in string.gfind(vLeftText, "([^/]+)") do
				local	vStatIDs, vValue = Outfitter_IsStatText(vStatString);
				
				if vStatIDs then
					for vStatIDIndex, vStatID in vStatIDs do
						OutfitterStats_AddStatValue(vStats, vStatID, vValue, pDistribution);
					end
				end
			end
		end
	end -- for vLineIndex
	
	return vStats;
end

function Outfitter_TooltipContainsText(pTooltip, pText)
	local	vTooltipName = pTooltip:GetName();
	
	for vLineIndex = 1, 30 do
		local	vLeftText = getglobal(vTooltipName.."TextLeft"..vLineIndex):GetText();
		
		if vLeftText
		and string.find(vLeftText, pText) then
			return true;
		end
	end -- for vLineIndex
	
	return false;
end

function Outfitter_CanEquipBagItem(pBagIndex, pBagSlotIndex)
	local	vItemInfo = Outfitter_GetBagItemInfo(pBagIndex, pBagSlotIndex);
	
	if vItemInfo
	and vItemInfo.Level
	and UnitLevel("player") < vItemInfo.Level then
		return false;
	end
	
	return true;
end

function Outfitter_BagItemWillBind(pBagIndex, pBagSlotIndex)
	local	vItemLink = GetContainerItemLink(pBagIndex, pBagSlotIndex);
	
	if not vItemLink then
		return nil;
	end
	
	OutfitterTooltip:SetOwner(OutfitterFrame, "ANCHOR_BOTTOMRIGHT", 0, 0);
	OutfitterTooltip:SetBagItem(pBagIndex, pBagSlotIndex);
	
	local	vIsBOE = Outfitter_TooltipContainsText(OutfitterTooltip, ITEM_BIND_ON_EQUIP);
	
	OutfitterTooltip:Hide();
	
	return vIsBOE;
end

function Outfitter_GenerateSmartOutfit(pName, pStatID, pEquippableItems, pAllowEmptyOutfit)
	local	vOutfit = Outfitter_NewEmptyOutfit(pName);
	
	if pStatID == "TANKPOINTS" then
		return;
	end
	
	local	vItems = Outfitter_cStatIDItems[pStatID];
	
	OutfitterItemList_ResetIgnoreItemFlags(pEquippableItems);
	
	if vItems then
		Outfitter_FindAndAddItemsToOutfit(vOutfit, nil, vItems, pEquippableItems);
	end
	
	Outfitter_AddItemsWithStatToOutfit(vOutfit, pStatID, pEquippableItems);
	
	if not pAllowEmptyOutfit
	and Outfitter_IsEmptyOutfit(vOutfit) then
		return nil;
	end
	
	vOutfit.StatID = pStatID;
	
	return vOutfit;
end

function Outfitter_ArrayIsEmpty(pArray)
	if not pArray then
		return true;
	end
	
	for vIndex, vValue in pArray do
		return false;
	end
	
	return true;
end

function OutfitterNameOutfit_Open(pOutfit)
	gOutfitter_OutfitToRename = pOutfit;
	
	if gOutfitter_OutfitToRename then
		OutfitterNameOutfitDialogTitle:SetText(Outfitter_cRenameOutfit);
		OutfitterNameOutfitDialogName:SetText(gOutfitter_OutfitToRename.Name);
		OutfitterNameOutfitDialogCreateUsing:Hide();
		OutfitterNameOutfitDialog:SetHeight(OutfitterNameOutfitDialog.baseHeight - 35);
	else
		OutfitterNameOutfitDialogTitle:SetText(Outfitter_cNewOutfit);
		OutfitterNameOutfitDialogName:SetText("");
		OutfitterDropDown_SetSelectedValue(OutfitterNameOutfitDialogCreateUsing, 0);
		OutfitterNameOutfitDialogCreateUsing:Show();
		OutfitterNameOutfitDialog:SetHeight(OutfitterNameOutfitDialog.baseHeight);
		OutfitterNameOutfitDialogCreateUsing.ChangedValueFunc = OutfitterNameOutfit_CheckForStatOutfit;
	end
	
	OutfitterNameOutfitDialog:Show();
	OutfitterNameOutfitDialogName:SetFocus();
end

function OutfitterNameOutfit_CheckForStatOutfit(pMenu, pValue)
	OutfitterNameOutfit_Update(true);
end

function OutfitterNameOutfit_Done()
	local	vName = OutfitterNameOutfitDialogName:GetText();
	
	if vName
	and vName ~= "" then
		if gOutfitter_OutfitToRename then
			local	vWearingOutfit = Outfitter_WearingOutfit(gOutfitter_OutfitToRename);
			
			if vWearingOutfit then
				Outfitter_DispatchOutfitEvent("UNWEAR_OUTFIT", gOutfitter_OutfitToRename.Name, gOutfitter_OutfitToRename)
			end
			
			gOutfitter_OutfitToRename.Name = vName;
			gOutfitter_DisplayIsDirty = true;

			if vWearingOutfit then
				Outfitter_DispatchOutfitEvent("WEAR_OUTFIT", gOutfitter_OutfitToRename.Name, gOutfitter_OutfitToRename)
			end
		else
			-- New outift
			
			local	vStatID = UIDropDownMenu_GetSelectedValue(OutfitterNameOutfitDialogCreateUsing);
			local	vOutfit;
			
			if not vStatID
			or vStatID == 0 then
				vOutfit = Outfitter_GetInventoryOutfit(vName);
			elseif vStatID == "EMPTY" then
				vOutfit = Outfitter_NewEmptyOutfit(vName);
			else
				vOutfit = Outfitter_GenerateSmartOutfit(vName, vStatID, OutfitterItemList_GetEquippableItems(true));
			end
			
			if not vOutfit then
				vOutfit = Outfitter_NewEmptyOutfit(vName);
			end
			
			local	vCategoryID = Outfitter_AddOutfit(vOutfit);
			
			Outfitter_WearOutfit(vOutfit, vCategoryID);
		end
	end
	
	OutfitterNameOutfitDialog:Hide();
	
	Outfitter_Update(true);
end

function OutfitterNameOutfit_Cancel()
	OutfitterNameOutfitDialog:Hide();
end

function OutfitterNameOutfit_Update(pCheckForStatOutfit)
	local	vEnableDoneButton = true;
	local	vErrorMessage = nil;
	
	-- If there's no name entered then disable the okay button
	
	local	vName = OutfitterNameOutfitDialogName:GetText();
	
	if not vName
	or vName == "" then
		vEnableDoneButton = false;
	else
		local	vOutfit = Outfitter_FindOutfitByName(vName);
		
		if vOutfit
		and vOutfit ~= gOutfitter_OutfitToRename then
			vErrorMessage = Outfitter_cNameAlreadyUsedError;
			vEnableDoneButton = false;
		end
	end
	
	-- 
	
	if not vErrorMessage
	and pCheckForStatOutfit then
		local	vStatID = UIDropDownMenu_GetSelectedValue(OutfitterNameOutfitDialogCreateUsing);
		
		if vStatID
		and vStatID ~= 0
		and vStatID ~= "EMPTY" then
			local	vOutfit = Outfitter_GenerateSmartOutfit("temp outfit", vStatID, OutfitterItemList_GetEquippableItems(true));
			
			if not vOutfit
			or Outfitter_IsEmptyOutfit(vOutfit) then
				vErrorMessage = Outfitter_cNoItemsWithStatError;
			end
		end
	end
	
	if vErrorMessage then
		OutfitterNameOutfitDialogError:SetText(vErrorMessage);
		OutfitterNameOutfitDialogError:Show();
	else
		OutfitterNameOutfitDialogError:Hide();
	end
	
	Outfitter_SetButtonEnable(OutfitterNameOutfitDialogDoneButton, vEnableDoneButton);
end

function Outfitter_SetButtonEnable(pButton, pEnabled)
	if pEnabled then
		pButton:Enable();
		pButton:SetAlpha(1.0);
		pButton:EnableMouse(true);
		--getglobal(pButton:GetName().."Text"):SetAlpha(1.0);
	else
		pButton:Disable();
		pButton:SetAlpha(0.7);
		pButton:EnableMouse(false);
		--getglobal(pButton:GetName().."Text"):SetAlpha(0.7);
	end
end

function Outfitter_GetOutfitFromListItem(pItem)
	if pItem.isCategory then
		return nil;
	end
	
	if not gOutfitter_Settings.Outfits then
		return nil;
	end
	
	local	vOutfits = gOutfitter_Settings.Outfits[pItem.categoryID];
	
	if not vOutfits then
		-- Error: outfit category not found
		return nil;
	end
	
	return vOutfits[pItem.outfitIndex], pItem.categoryID;
end

function Outfitter_OutfitItemSelected(pMenu, pValue)
	local	vItem = pMenu:GetParent():GetParent();
	local	vOutfit, vCategoryID = Outfitter_GetOutfitFromListItem(vItem);
	
	if not vOutfit then
		Outfitter_ErrorMessage("Outfitter Error: Outfit for menu item "..vItem:GetName().." not found");
		return;
	end
	
	-- Perform the selected action
	
	if pValue == "DELETE" then
		Outfitter_AskDeleteOutfit(vOutfit);
	elseif pValue == "RENAME" then
		OutfitterNameOutfit_Open(vOutfit);
	elseif pValue == "DISABLE" then
		if vOutfit.Disabled then
			vOutfit.Disabled = nil;
		else
			vOutfit.Disabled = true;
		end
		gOutfitter_DisplayIsDirty = true;
	elseif pValue == "BGDISABLE" then
		if vOutfit.BGDisabled then
			vOutfit.BGDisabled = nil;
		else
			vOutfit.BGDisabled = true;
		end
		gOutfitter_DisplayIsDirty = true;
	elseif pValue == "INSTDISABLE" then
		if vOutfit.InstDisabled then
			vOutfit.InstDisabled = nil;
		else
			vOutfit.InstDisabled = true;
		end
		gOutfitter_DisplayIsDirty = true;
	elseif pValue == "ACCESSORY" then
		vOutfit.IsAccessory = true;
		Outfitter_UpdateOutfitCategory(vOutfit);
	elseif pValue == "PARTIAL" then
		vOutfit.IsAccessory = nil;
		Outfitter_UpdateOutfitCategory(vOutfit);
	elseif string.sub(pValue, 1, 7) == "BINDING" then
		Outfitter_SetOutfitBindingIndex(vOutfit, tonumber(string.sub(pValue, 8)));
	elseif pValue == "REBUILD" then
		Outfitter_AskRebuildOutfit(vOutfit, vCategoryID);
	elseif pValue == "DEPOSIT" then
		Outfitter_DepositOutfit(vOutfit);
	elseif pValue == "DEPOSITUNIQUE" then
		Outfitter_DepositOutfit(vOutfit, true);
	elseif pValue == "WITHDRAW" then
		Outfitter_WithdrawOutfit(vOutfit);
	end
	
	Outfitter_Update(true);
end

function OutfitterStatDropdown_OnLoad()
	UIDropDownMenu_Initialize(this, OutfitterStatDropdown_Initialize);
	UIDropDownMenu_SetWidth(150);
	UIDropDownMenu_Refresh(this);
end

function Outfitter_GetStatIDName(pStatID)
	for vStatIndex, vStatInfo in Outfitter_cItemStatInfo do
		if vStatInfo.ID == pStatID then
			return vStatInfo.Name;
		end
	end
	
	return nil;
end

function OutfitterStatDropdown_Initialize()
	local	vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
	
	if UIDROPDOWNMENU_MENU_LEVEL == 2 then
		for vStatIndex, vStatInfo in Outfitter_cItemStatInfo do
			if vStatInfo.Category == UIDROPDOWNMENU_MENU_VALUE then
				UIDropDownMenu_AddButton({text = vStatInfo.Name, value = vStatInfo.ID, owner = vFrame, func = OutfitterDropDown_OnClick}, UIDROPDOWNMENU_MENU_LEVEL);
			end
		end
	else
		UIDropDownMenu_AddButton({text = Outfitter_cUseCurrentOutfit, value = 0, owner = vFrame, func = OutfitterDropDown_OnClick});
		UIDropDownMenu_AddButton({text = Outfitter_cUseEmptyOutfit, value = "EMPTY", owner = vFrame, func = OutfitterDropDown_OnClick});
		
		UIDropDownMenu_AddButton({text = " ", notCheckable = true, notClickable = true});
		
		for vCategoryIndex, vCategoryInfo in Outfitter_cStatCategoryInfo do
			UIDropDownMenu_AddButton({text = vCategoryInfo.Name, owner = vFrame, hasArrow = 1, value = vCategoryInfo.Category});
		end
		
		if false and IsAddOnLoaded("TankPoints") then
			UIDropDownMenu_AddButton({text = " ", notCheckable = true, notClickable = true});
			UIDropDownMenu_AddButton({text = Outfitter_cTankPoints, value="TANKPOINTS", owner = vFrame, func = OutfitterDropDown_OnClick});
		end
	end
end

function OutfitterDropDown_SetSelectedValue(pDropDown, pValue)
	UIDropDownMenu_SetText("", pDropDown); -- Set to empty in case the selected value isn't there

	UIDropDownMenu_Initialize(pDropDown, pDropDown.initialize);
	UIDropDownMenu_SetSelectedValue(pDropDown, pValue);
	
	-- All done if the item text got set successfully
	
	local	vItemText = UIDropDownMenu_GetText(pDropDown);
	
	if vItemText and vItemText ~= "" then
		return;
	end
	
	-- Scan for submenus
	
	local	vRootListFrameName = "DropDownList1";
	local	vRootListFrame = getglobal(vRootListFrameName);
	local	vRootNumItems = vRootListFrame.numButtons;
	
	for vRootItemIndex = 1, vRootNumItems do
		local	vItem = getglobal(vRootListFrameName.."Button"..vRootItemIndex);
		
		if vItem.hasArrow then
			local	vSubMenuFrame = getglobal("DropDownList2");
			
			UIDROPDOWNMENU_OPEN_MENU = pDropDown:GetName();
			UIDROPDOWNMENU_MENU_VALUE = vItem.value;
			UIDROPDOWNMENU_MENU_LEVEL = 2;
			
			UIDropDownMenu_Initialize(pDropDown, pDropDown.initialize, nil, 2);
			UIDropDownMenu_SetSelectedValue(pDropDown, pValue);
			
			-- All done if the item text got set successfully
			
			local	vItemText = UIDropDownMenu_GetText(pDropDown);
			
			if vItemText and vItemText ~= "" then
				return;
			end
			
			-- Switch back to the root menu
			
			UIDROPDOWNMENU_OPEN_MENU = nil;
			UIDropDownMenu_Initialize(pDropDown, pDropDown.initialize, nil, 1);
		end
	end
end

function OutfitterScrollbarTrench_SizeChanged(pScrollbarTrench)
	local	vScrollbarTrenchName = pScrollbarTrench:GetName();
	local	vScrollbarTrenchMiddle = getglobal(vScrollbarTrenchName.."Middle");
	
	local	vMiddleHeight= pScrollbarTrench:GetHeight() - 51;
	vScrollbarTrenchMiddle:SetHeight(vMiddleHeight);
end

function OutfitterInputBox_OnLoad(pChildDepth)
	if not pChildDepth then
		pChildDepth = 0;
	end
	
	local	vParent = this:GetParent();
	
	for vDepthIndex = 1, pChildDepth do
		vParent = vParent:GetParent();
	end
	
	if vParent.lastEditBox then
		this.prevEditBox = vParent.lastEditBox;
		this.nextEditBox = vParent.lastEditBox.nextEditBox;
		
		this.prevEditBox.nextEditBox = this;
		this.nextEditBox.prevEditBox = this;
	else
		this.prevEditBox = this;
		this.nextEditBox = this;
	end

	vParent.lastEditBox = this;
end

function OutfitterInputBox_TabPressed()
	local		vReverse = IsShiftKeyDown();
	local		vEditBox = this;
	
	for vIndex = 1, 50 do
		local	vNextEditBox;
			
		if vReverse then
			vNextEditBox = vEditBox.prevEditBox;
		else
			vNextEditBox = vEditBox.nextEditBox;
		end
		
		if vNextEditBox:IsVisible()
		and not vNextEditBox.isDisabled then
			vNextEditBox:SetFocus();
			return;
		end
		
		vEditBox = vNextEditBox;
	end
end

function OutfitterTimer_AdjustTimer()
	local	vNeedTimer = false;
	
	if OutfitterMinimapButton.IsDragging then
		vNeedTimer = true;
	end
	
	if gOutfitter_EquippedNeedsUpdate
	or gOutfitter_WeaponsNeedUpdate then
		vNeedTimer = true;
	end
	
	if vNeedTimer then
		OutfitterUpdateFrame:Show();
	else
		OutfitterUpdateFrame:Hide();
		OutfitterUpdateFrame.Elapsed = nil;
	end
end

function OutfitterUpdateFrame_OnUpdate(pElapsed)
	if OutfitterMinimapButton.IsDragging then
		OutfitterMinimapButton_UpdateDragPosition();
	end
	
	if not OutfitterUpdateFrame.Elapsed then
		OutfitterUpdateFrame.Elapsed = 0;
	else
		OutfitterUpdateFrame.Elapsed = OutfitterUpdateFrame.Elapsed + pElapsed;
		
		if OutfitterUpdateFrame.Elapsed > 0.25 then
			Outfitter_UpdateEquippedItems();
			OutfitterUpdateFrame.Elapsed = 0;
		end
	end
	
	OutfitterTimer_AdjustTimer();
end

function OutfitterMinimapButton_MouseDown()
	-- Remember where the cursor was in case the user drags
	
	local	vCursorX, vCursorY = GetCursorPosition();
	
	vCursorX = vCursorX / this:GetEffectiveScale();
	vCursorY = vCursorY / this:GetEffectiveScale();
	
	OutfitterMinimapButton.CursorStartX = vCursorX;
	OutfitterMinimapButton.CursorStartY = vCursorY;
	
	local	vCenterX, vCenterY = OutfitterMinimapButton:GetCenter();
	local	vMinimapCenterX, vMinimapCenterY = Minimap:GetCenter();
	
	OutfitterMinimapButton.CenterStartX = vCenterX - vMinimapCenterX;
	OutfitterMinimapButton.CenterStartY = vCenterY - vMinimapCenterY;
end

function OutfitterMinimapButton_DragStart()
	OutfitterMinimapButton.IsDragging = true;
	OutfitterTimer_AdjustTimer();
end

function OutfitterMinimapButton_DragEnd()
	OutfitterMinimapButton.IsDragging = false;
	OutfitterTimer_AdjustTimer();
end

function OutfitterMinimapButton_UpdateDragPosition()
	-- Remember where the cursor was in case the user drags
	
	local	vCursorX, vCursorY = GetCursorPosition();
	
	vCursorX = vCursorX / this:GetEffectiveScale();
	vCursorY = vCursorY / this:GetEffectiveScale();
	
	local	vCursorDeltaX = vCursorX - OutfitterMinimapButton.CursorStartX;
	local	vCursorDeltaY = vCursorY - OutfitterMinimapButton.CursorStartY;
	
	--
	
	local	vCenterX = OutfitterMinimapButton.CenterStartX + vCursorDeltaX;
	local	vCenterY = OutfitterMinimapButton.CenterStartY + vCursorDeltaY;
	
	-- Calculate the angle
	
	local	vAngle = math.atan2(vCenterX, vCenterY);
	
	-- Set the new position
	
	OutfitterMinimapButton_SetPositionAngle(vAngle);
end

function Outfitter_RestrictAngle(pAngle, pRestrictStart, pRestrictEnd)
	if pAngle <= pRestrictStart
	or pAngle >= pRestrictEnd then
		return pAngle;
	end
	
	local	vDistance = (pAngle - pRestrictStart) / (pRestrictEnd - pRestrictStart);
	
	if vDistance > 0.5 then
		return pRestrictEnd;
	else
		return pRestrictStart;
	end
end

function OutfitterMinimapButton_SetPositionAngle(pAngle)
	local	vAngle = pAngle;
	
	-- Restrict the angle from going over the date/time icon or the zoom in/out icons
	
	local	vRestrictedStartAngle = nil;
	local	vRestrictedEndAngle = nil;
	
	if GameTimeFrame:IsVisible() then
		if MinimapZoomIn:IsVisible()
		or MinimapZoomOut:IsVisible() then
			vAngle = Outfitter_RestrictAngle(vAngle, 0.4302272732931596, 2.930420793963121);
		else
			vAngle = Outfitter_RestrictAngle(vAngle, 0.4302272732931596, 1.720531504573905);
		end
		
	elseif MinimapZoomIn:IsVisible()
	or MinimapZoomOut:IsVisible() then
		vAngle = Outfitter_RestrictAngle(vAngle, 1.720531504573905, 2.930420793963121);
	end
	
	-- Restrict it from the tracking icon area
	
	vAngle = Outfitter_RestrictAngle(vAngle, -1.290357134304173, -0.4918423429923585);
	
	--
	
	local	vRadius = 80;
	
	vCenterX = math.sin(vAngle) * vRadius;
	vCenterY = math.cos(vAngle) * vRadius;
	
	OutfitterMinimapButton:SetPoint("CENTER", "Minimap", "CENTER", vCenterX - 1, vCenterY - 1);
	
	gOutfitter_Settings.Options.MinimapButtonAngle = vAngle;
end

function OutfitterMinimapButton_ItemSelected(pMenu, pValue)
	local	vType = type(pValue);
	
	if vType == "table" then
		local	vCategoryID = pValue.CategoryID;
		local	vIndex = pValue.Index;
		local	vOutfit = gOutfitter_Settings.Outfits[vCategoryID][vIndex];
		local	vDoToggle = vCategoryID ~= "Complete";
		
		if vDoToggle
		and Outfitter_WearingOutfit(vOutfit) then
			Outfitter_RemoveOutfit(vOutfit);
		else
			Outfitter_WearOutfit(vOutfit, vCategoryID);
		end
		
		if vDoToggle then
			return true;
		end
	else
		if pValue == 0 then -- Open Outfitter
			ShowUIPanel(CharacterFrame);
			CharacterFrame_ShowSubFrame("PaperDollFrame");
			OutfitterFrame:Show();
		end
	end
	
	return false;
end

function Outfitter_WearingOutfit(pOutfit)
	return OutfitterStack_FindOutfit(pOutfit);
end

function Outfitter_GetCurrentOutfitInfo()
	if not gOutfitter_Initialized then
		return "", nil;
	end
	
	local	vStackLength = table.getn(gOutfitter_OutfitStack);
	
	if vStackLength == 0 then
		return "", nil;
	end
	
	local	vOutfit = gOutfitter_OutfitStack[vStackLength];
	
	if vOutfit and vOutfit.Name then
		return vOutfit.Name, vOutfit;
	else
		return Outfitter_cCustom, vOutfit;
	end
end

function Outfitter_CheckDatabase()
	local	vOutfit;
	
	if not gOutfitter_Settings.Version then
		local	vOutfits = gOutfitter_Settings.Outfits[vCategoryID];
		
		if gOutfitter_Settings.Outfits then
			for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
				for vIndex, vOutfit in vOutfits do
					if Outfitter_OutfitIsComplete(vOutfit, true) then
						Outfitter_AddOutfitItem(vOutfit, "AmmoSlot", 0, 0, "", 0);
					end
				end
			end
		end
		
		gOutfitter_Settings.Version = 1;
	end
	
	-- Versions 1 and 2 both simply add class outfits
	-- so just reinitialize those
	
	if gOutfitter_Settings.Version < 3 then
		Outfitter_InitializeClassOutfits();
		gOutfitter_Settings.Version = 3;
	end
	
	-- Version 4 sets the BGDisabled flag for the mounted outfit
	
	if gOutfitter_Settings.Version < 4 then
		local	vRidingOutfit = Outfitter_GetSpecialOutfit("Riding");
		
		if vRidingOutfit then
			vRidingOutfit.BGDisabled = true;
			vRidingOutfit.InstDisabled = true;
		end
		
		gOutfitter_Settings.Version = 4;
	end
	
	-- Version 5 adds moonkin form, just reinitialize class outfits

	if gOutfitter_Settings.Version < 5 then
		Outfitter_InitializeClassOutfits();
		gOutfitter_Settings.Version = 5;
	end
	
	-- Make sure all outfits have an associated category ID
	
	if gOutfitter_Settings.Outfits then
		for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
			for vIndex, vOutfit in vOutfits do
				vOutfit.CategoryID = vCategoryID;
			end
		end
	end
	
	-- Version 6 and 7 adds item sub-code and enchantment codes
	-- (7 tries to clean up failed updates from 6)
	
	if gOutfitter_Settings.Version < 7 then
		if not Outfitter_UpdateDatabaseItemCodes() then
			gOutfitter_NeedItemCodesUpdated = 5; -- Do up to five attempts at updated the item codes
		end
		
		gOutfitter_Settings.Version = 7;
	end
	
	-- Scan the outfits and make sure everything is in order
	
	for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
		for vIndex, vOutfit in vOutfits do
			Outfitter_CheckOutfit(vOutfit);
		end
	end
end

function Outfitter_CheckOutfit(pOutfit)
	if not pOutfit.Name then
		pOutfit.Name = "Damaged outfit";
	end
	
	if not pOutfit.Items then
		pOutfit.Items = {};
	end
	
	for vInventorySlot, vItem in pOutfit.Items do
		if not vItem.Code then
			vItem.Code = 0;
		end
		
		if not vItem.SubCode then
			vItem.SubCode = 0;
		end
		
		if not vItem.Name then
			vItem.Name = "";
		end
		
		if not vItem.EnchantCode then
			vItem.EnchantCode = 0;
		end
	end
end

function Outfitter_UpdateDatabaseItemCodes()
	local	vEquippableItems = OutfitterItemList_GetEquippableItems();
	local	vResult = true;
	
	for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
		for vIndex, vOutfit in vOutfits do
			for vInventorySlot, vOutfitItem in vOutfit.Items do
				if vOutfitItem.Code ~= 0 then
					local	vItem = OutfitterItemList_FindItemOrAlt(vEquippableItems, vOutfitItem, false, true);
					
					if vItem then
						vOutfitItem.SubCode = vItem.SubCode;
						vOutfitItem.Name = vItem.Name;
						vOutfitItem.EnchantCode = vItem.EnchantCode;
						vOutfitItem.Checksum = nil;
					else
						vResult = false;
					end
				end
			end
		end
	end
	
	return vResult;
end

local	gOutfitter_PaperDollItemSlotButton_OnClick;

function Outfitter_HookPaperDollFrame()
	gOutfitter_PaperDollItemSlotButton_OnClick = PaperDollItemSlotButton_OnClick;
	PaperDollItemSlotButton_OnClick = Outfitter_PaperDollItemSlotButton_OnClick
end

local	Outfitter_cMaxNumQuickSlots = 9;
local	Outfitter_cSlotIDToInventorySlot = nil;

function Outfitter_PaperDollItemSlotButton_OnClick(pButton, pIgnoreModifiers)
	-- Build the table to convert from slot ID to inventory slot name
	
	if not Outfitter_cSlotIDToInventorySlot then
		Outfitter_cSlotIDToInventorySlot = {};
		
		for _, vInventorySlot in Outfitter_cSlotNames do
			local	vSlotID = GetInventorySlotInfo(vInventorySlot);
			
			Outfitter_cSlotIDToInventorySlot[vSlotID] = vInventorySlot;
		end
	end
	
	--
	
	local	vSlotID = this:GetID();
	local	vInventorySlot = Outfitter_cSlotIDToInventorySlot[vSlotID];
	local	vItemLink = GetInventoryItemLink("player", vSlotID);
	local	vSlotIsEmpty = vItemLink == nil;
	
	-- Call the original function
	
	gOutfitter_PaperDollItemSlotButton_OnClick(pButton, pIgnoreModifiers);
	
	-- If there's an item on the cursor then open the slots otherwise
	-- make sure they're closed
	
	if not OutfitterQuickSlots:IsVisible()
	and (CursorHasItem() or vSlotIsEmpty) then
		-- Hide the tooltip so that it isn't in the way
		
		GameTooltip:Hide();
		
		-- Open QuickSlots
		
		OutfitterQuickSlots_Open(vInventorySlot);
	else
		OutfitterQuickSlots_Close();
	end
end

function OutfitterItemList_AddItem(pItemList, pItem)
	-- Add the item to the code list

	local	vItemFamily = pItemList.ItemsByCode[pItem.Code];

	if not vItemFamily then
		vItemFamily = {};
		pItemList.ItemsByCode[pItem.Code] = vItemFamily;
	end
	
	table.insert(vItemFamily, pItem);
	
	-- Add the item to the slot list
	
	local	vItemSlot = pItemList.ItemsBySlot[pItem.ItemSlotName];
	
	if not vItemSlot then
		vItemSlot = {};
		pItemList.ItemsBySlot[pItem.ItemSlotName] = vItemSlot;
	end
	
	table.insert(vItemSlot, pItem);
	
	-- Add the item to the bags
	
	if pItem.Location.BagIndex then
		local	vBagItems = pItemList.BagItems[pItem.Location.BagIndex];
		
		if not vBagItems then
			vBagItems = {};
			pItemList.BagItems[pItem.Location.BagIndex] = vBagItems;
		end
		
		vBagItems[pItem.Location.BagSlotIndex] = pItem;
		
	-- Add the item to the inventory
	
	elseif pItem.Location.SlotName then
		pItemList.InventoryItems[pItem.Location.SlotName] = pItem;
	end
end

function Outfitter_GetNumBags()
	if gOutfitter_BankFrameOpened then
		return NUM_BAG_SLOTS + NUM_BANKBAGSLOTS, -1;
	else
		return NUM_BAG_SLOTS, 0;
	end
end

function OutfitterItemList_FlushEquippableItems()
	gOutfitter_EquippableItems = nil;
end

function OutfitterItemList_FlushBagFromEquippableItems(pBagIndex)
	if gOutfitter_EquippableItems
	and gOutfitter_EquippableItems.BagItems[pBagIndex] then
		for vBagSlotIndex, vItem in gOutfitter_EquippableItems.BagItems[pBagIndex] do
			OutfitterItemList_RemoveItem(gOutfitter_EquippableItems, vItem);
		end
		
		gOutfitter_EquippableItems.NeedsUpdate = true;
		gOutfitter_EquippableItems.BagItems[pBagIndex] = nil;
	end
end

function OutfitterItemList_FlushInventoryFromEquippableItems()
	if gOutfitter_EquippableItems then
		for vInventorySlot, vItem in gOutfitter_EquippableItems.InventoryItems do
			OutfitterItemList_RemoveItem(gOutfitter_EquippableItems, vItem);
		end
		
		gOutfitter_EquippableItems.NeedsUpdate = true;
		gOutfitter_EquippableItems.InventoryItems = nil;
	end
end

function OutfitterItemList_New()
	return {ItemsByCode = {}, ItemsBySlot = {}, InventoryItems = nil, BagItems = {}};
end

function OutfitterItemList_RemoveItem(pItemList, pItem)
	-- Remove the item from the code list
	
	local	vItems = pItemList.ItemsByCode[pItem.Code];
	
	for vIndex, vItem in vItems do
		if vItem == pItem then
			table.remove(vItems, vIndex);
			break;
		end
	end

	-- Remove the item from the slot list
	
	local	vItemSlot = pItemList.ItemsBySlot[pItem.ItemSlotName];
	
	if vItemSlot then
		for vIndex, vItem in vItemSlot do
			if vItem == pItem then
				table.remove(vItemSlot, vIndex);
				break;
			end
		end
	end
	
	-- Remove the item from the bags list
	
	if pItem.Location.BagIndex then
		local	vBagItems = pItemList.BagItems[pItem.Location.BagIndex];
		
		if vBagItems then
			vBagItems[pItem.Location.BagSlotIndex] = nil;
		end
		
	-- Remove the item from the inventory list
	
	elseif pItem.Location.SlotName then
		pItemList.InventoryItems[pItem.Location.SlotName] = nil;
	end
end

function OutfitterItemList_GetInventoryOutfit(pEquippableItems)
	return pEquippableItems.InventoryItems;
end

function OutfitterItemList_ResetIgnoreItemFlags(pItemList)
	for vItemCode, vItemFamily in pItemList.ItemsByCode do
		for _, vItem in vItemFamily do
			vItem.IgnoreItem = nil;
		end
	end
end

function OutfitterItemList_GetEquippableItems(pIncludeItemStats)
	-- If there's a cached copy just clear the IgnoreItem flags and return it
	-- (never used cached copy if the caller wants stats)
	
	if gOutfitter_EquippableItems
	and not gOutfitter_EquippableItems.NeedsUpdate
	and not pIncludeItemStats then
		OutfitterItemList_ResetIgnoreItemFlags(gOutfitter_EquippableItems);
		
		return gOutfitter_EquippableItems;
	end
	
	if not gOutfitter_EquippableItems
	or pIncludeItemStats then
		gOutfitter_EquippableItems = OutfitterItemList_New();
	end
	
	local	_, vPlayerClass = UnitClass("player");
	local	vStatDistribution = gOutfitter_StatDistribution[vPlayerClass];
	
	if not gOutfitter_EquippableItems.InventoryItems
	or pIncludeItemStats then
		gOutfitter_EquippableItems.InventoryItems = {};
		
		for _, vInventorySlot in Outfitter_cSlotNames do
			local	vItemInfo = Outfitter_GetInventoryItemInfo(vInventorySlot);
			
			if vItemInfo
			and vItemInfo.ItemSlotName
			and vItemInfo.Code ~= 0 then
				vItemInfo.SlotName = vInventorySlot;
				vItemInfo.Location = {SlotName = vInventorySlot};
				
				if pIncludeItemStats then	
					OutfitterItemList_GetItemStats(vItemInfo, vStatDistribution);
				end
				
				OutfitterItemList_AddItem(gOutfitter_EquippableItems, vItemInfo);
			end
		end
	else
		for vInventorySlot, vItem in gOutfitter_EquippableItems.InventoryItems do
			vItem.IgnoreItem = nil;
		end
	end
	
	local	vNumBags, vFirstBagIndex = Outfitter_GetNumBags();
	
	for vBagIndex = vFirstBagIndex, vNumBags do
		local		vBagItems = gOutfitter_EquippableItems.BagItems[vBagIndex];
		
		if not vBagItems
		or pIncludeItemStats then
			gOutfitter_EquippableItems.BagItems[vBagIndex] = {};
			
			local	vNumBagSlots = GetContainerNumSlots(vBagIndex);
			
			if vNumBagSlots > 0 then
				for vBagSlotIndex = 1, vNumBagSlots do
					local	vItemInfo = Outfitter_GetBagItemInfo(vBagIndex, vBagSlotIndex);
					
					if vItemInfo
					and vItemInfo.Code ~= 0
					and vItemInfo.ItemSlotName
					and Outfitter_CanEquipBagItem(vBagIndex, vBagSlotIndex)
					and not Outfitter_BagItemWillBind(vBagIndex, vBagSlotIndex) then
						vItemInfo.BagIndex = vBagIndex;
						vItemInfo.BagSlotIndex = vBagSlotIndex;
						vItemInfo.Location = {BagIndex = vBagIndex, BagSlotIndex = vBagSlotIndex};
						
						if pIncludeItemStats then	
							OutfitterItemList_GetItemStats(vItemInfo, vStatDistribution);
						end
						
						OutfitterItemList_AddItem(gOutfitter_EquippableItems, vItemInfo);
					end
				end -- for vBagSlotIndex
			end -- if vNumBagSlots > 0
		else -- if not BagItems
			for vBagSlotIndex, vItem in vBagItems do
				vItem.IgnoreItem = nil;
			end
		end -- if not BagItems
	end -- for vBagIndex
	
	gOutfitter_EquippableItems.NeedsUpdate = false;
	
	return gOutfitter_EquippableItems;
end

function OutfitterItemList_SwapLocations(pItemList, pLocation1, pLocation2)
	-- if pLocation1.BagIndex then
	-- 	Outfitter_TestMessage("OutfitterItemList_SwapLocations: Swapping bag "..pLocation1.BagIndex..", "..pLocation1.BagSlotIndex);
	-- elseif pLocation1.SlotName then
	-- 	Outfitter_TestMessage("OutfitterItemList_SwapLocations: Swapping slot "..pLocation1.SlotName);
	-- end
	-- if pLocation2.BagIndex then
	-- 	Outfitter_TestMessage("OutfitterItemList_SwapLocations: with bag "..pLocation2.BagIndex..", "..pLocation2.BagSlotIndex);
	-- elseif pLocation2.SlotName then
	-- 	Outfitter_TestMessage("OutfitterItemList_SwapLocations: with slot "..pLocation2.SlotName);
	-- end
end

function OutfitterItemList_SwapLocationWithInventorySlot(pItemList, pLocation, pSlotName)
	-- if pLocation.BagIndex then
	-- 	Outfitter_TestMessage("OutfitterItemList_SwapLocationWithInventorySlot: Swapping bag "..pLocation.BagIndex..", "..pLocation.BagSlotIndex.." with slot "..pSlotName);
	-- elseif pLocation.SlotName then
	-- 	Outfitter_TestMessage("OutfitterItemList_SwapLocationWithInventorySlot: Swapping slot "..pLocation.SlotName.." with slot "..pSlotName);
	-- end
end

function OutfitterItemList_SwapBagSlotWithInventorySlot(pItemList, pBagIndex, pBagSlotIndex, pSlotName)
	-- Outfitter_TestMessage("OutfitterItemList_SwapBagSlotWithInventorySlot: Swapping bag "..pBagIndex..", "..pBagSlotIndex.." with slot "..pSlotName);
end

function OutfitterItemList_FindItemOrAlt(pItemList, pOutfitItem, pMarkAsInUse, pAllowSubCodeWildcard)
	local	vItem, vIgnoredItem = OutfitterItemList_FindItem(pItemList, pOutfitItem, pMarkAsInUse, pAllowSubCodeWildcard);
	
	if vItem then
		return vItem;
	end
	
	-- See if there's an alias for the item if it wasn't found
	
	local	vAltCode = Outfitter_cItemAliases[pOutfitItem.Code];
	
	if not vAltCode then
		return nil, vIgnoredItem;
	end
	
	return OutfitterItemList_FindItem(pItemList, {Code = vAltCode}, pMarkAsInUse, true);
end

function OutfitterItemList_FindItem(pItemList, pOutfitItem, pMarkAsInUse, pAllowSubCodeWildcard)
	local	vItem, vIndex, vItemFamily, vIgnoredItem = OutfitterItemList_FindItemIndex(pItemList, pOutfitItem, pAllowSubCodeWildcard);
	
	if not vItem then
		return nil, vIgnoredItem;
	end
	
	if pMarkAsInUse then
		vItem.IgnoreItem = true;
	end
	
	return vItem;
end

function OutfitterItemList_FindAllItemsOrAlt(pItemList, pOutfitItem, pAllowSubCodeWildcard, rItems)
	local	vNumItems = OutfitterItemList_FindAllItems(pItemList, pOutfitItem, pAllowSubCodeWildcard, rItems);
	local	vAltCode = Outfitter_cItemAliases[pOutfitItem.Code];
	
	if vAltCode then
		vNumItems = vNumItems + OutfitterItemList_FindAllItems(pItemList, {Code = vAltCode}, true, rItems);
	end
	
	return vNumItems;
end

function OutfitterItemList_FindAllItems(pItemList, pOutfitItem, pAllowSubCodeWildcard, rItems)
	if not pItemList then
		return 0;
	end
	
	local	vItemFamily = pItemList.ItemsByCode[pOutfitItem.Code];
	
	if not vItemFamily then
		return 0;
	end
	
	local	vNumItemsFound = 0;
	
	for vIndex, vItem in vItemFamily do
		if (pAllowSubCodeWildcard and not pOutfitItem.SubCode)
		or vItem.SubCode == pOutfitItem.SubCode then
			table.insert(rItems, vItem);
			vNumItemsFound = vNumItemsFound + 1;
		end
	end
	
	return vNumItemsFound;
end

function OutfitterItemList_FindItemIndex(pItemList, pOutfitItem, pAllowSubCodeWildcard)
	if not pItemList then
		return nil, nil, nil, nil;
	end
	
	local	vItemFamily = pItemList.ItemsByCode[pOutfitItem.Code];
	
	if not vItemFamily then
		return nil, nil, nil, nil;
	end
	
	local	vBestMatch = nil;
	local	vBestMatchIndex = nil;
	local	vNumItemsFound = 0;
	local	vFoundIgnoredItem = nil;
	
	for vIndex, vItem in vItemFamily do
		if pAllowSubCodeWildcard
		and not pOutfitItem.SubCode then
			if vItem.IgnoreItem then
				vFoundIgnoredItem = vItem;
			else
				return vItem, vIndex, vItemFamily, nil;
			end
		
		--  If the subcode matches then check for an enchant match
		
		elseif vItem.SubCode == pOutfitItem.SubCode then
			-- If the enchant matches then we're all done
			
			if vItem.EnchantCode == pOutfitItem.EnchantCode then
				if vItem.IgnoreItem then
					vFoundIgnoredItem = vItem;
				else
					return vItem, vIndex, vItemFamily;
				end
			
			-- Otherwise save the match in case a better one can
			-- be found
			
			else
				if vItem.IgnoreItem then
					if not vFoundIgnoredItem then
						vFoundIgnoredItem = vItem;
					end
				else
					vBestMatch = vItem;
					vBestMatchIndex = vIndex;
					vNumItemsFound = vNumItemsFound + 1;
				end
			end
		end
	end
	
	-- Return the match if only one item was found
	
	if vNumItemsFound == 1
	and not vBestMatch.IgnoreItem then
		return vBestMatch, vBestMatchIndex, vItemFamily, nil;
	end
	
	return nil, nil, nil, vFoundIgnoredItem;
end
		
function OutfitterItemList_GetItemStats(pItem, pDistribution)
	if pItem.Stats then
		return pItem.Stats;
	end
	
	OutfitterTooltip:SetOwner(OutfitterFrame, "ANCHOR_BOTTOMRIGHT", 0, 0);
	
	if pItem.SlotName then
		local	vHasItem = OutfitterTooltip:SetInventoryItem("player", GetInventorySlotInfo(pItem.SlotName));
		
		if not vHasItem then
			OutfitterTooltip:Hide();
			return nil;
		end
	elseif pItem.BagIndex == -1 then
		OutfitterTooltip:SetInventoryItem("player", BankButtonIDToInvSlotID(pItem.BagSlotIndex));
	else
		OutfitterTooltip:SetBagItem(pItem.BagIndex, pItem.BagSlotIndex);
	end
	
	local	vStats = Outfitter_GetItemStatsFromTooltip(OutfitterTooltip, pDistribution);
	
	OutfitterTooltip:Hide();
	
	if not vStats then
		return nil;
	end
	
	pItem.Stats = vStats;
	
	return vStats;
end

function Outfitter_IsBankBagIndex(pBagIndex)
	return pBagIndex and (pBagIndex > NUM_BAG_SLOTS or pBagIndex < 0);
end

function OutfitterItemList_GetMissingItems(pEquippableItems, pOutfit)
	local	vMissingItems = nil;
	local	vBankedItems = nil;
	
	for vInventorySlot, vOutfitItem in pOutfit.Items do
		if vOutfitItem.Code ~= 0 then
			local	vItem = OutfitterItemList_FindItemOrAlt(pEquippableItems, vOutfitItem);
			
			if not vItem then
				if not vMissingItems then
					vMissingItems = {};
				end
				
				table.insert(vMissingItems, vOutfitItem);
			elseif Outfitter_IsBankBagIndex(vItem.Location.BagIndex) then
				if not vBankedItems then
					vBankedItems = {};
				end
				
				table.insert(vBankedItems, vOutfitItem);
			end
		end
	end
	
	return vMissingItems, vBankedItems;
end

function OutfitterItemList_CompiledUnusedItemsList(pEquippableItems)
	OutfitterItemList_ResetIgnoreItemFlags(pEquippableItems);
	
	for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
		for vOutfitIndex, vOutfit in vOutfits do
			for vInventorySlot, vOutfitItem in vOutfit.Items do
				if vOutfitItem.Code ~= 0 then
					local	vItem = OutfitterItemList_FindItemOrAlt(pEquippableItems, vOutfitItem, true);
					
					if vItem then
						vItem.UsedInOutfit = true;
					end
				end
			end
		end
	end
	
	local	vUnusedItems = nil;
	
	for vCode, vFamilyItems in pEquippableItems.ItemsByCode do
		for vIndex, vOutfitItem in vFamilyItems do
			if not vOutfitItem.UsedInOutfit
			and vOutfitItem.ItemSlotName ~= "AmmoSlot"
			and Outfitter_cIgnoredUnusedItems[vOutfitItem.Code] == nil then
				if not vUnusedItems then
					vUnusedItems = {};
				end
				
				table.insert(vUnusedItems, vOutfitItem);
			end
		end
	end
	
	pEquippableItems.UnusedItems = vUnusedItems;
end

function OutfitterItemList_ItemsAreSame(pEquippableItems, pItem1, pItem2)
	if not pItem1 then
		return pItem2 == nil;
	end
	
	if not pItem2 then
		return false;
	end
	
	if pItem1.Code == 0 then
		return pItem2.Code == 0;
	end
	
	if pItem1.Code ~= pItem2.Code
	or pItem1.SubCode ~= pItem2.SubCode then
		return false;
	end
	
	local	vItems = {};
	local	vNumItems = OutfitterItemList_FindAllItemsOrAlt(pEquippableItems, pItem1, nil, vItems);
	
	if vNumItems == 0 then
		-- Shouldn't ever get here
		
		Outfitter_ErrorMessage("OutfitterItemList_ItemsAreSame: Item not found");
		return false;
	elseif vNumItems == 1 then
		-- If there's only one of that item then the enchant code
		-- is disregarded so just make sure it's the same
		
		return true;
	else
		return pItem1.EnchantCode == pItem2.EnchantCode;
	end
end

function OutfitterItemList_InventorySlotContainsItem(pEquippableItems, pInventorySlot, pOutfitItem)
	-- Nil items are supposed to be ignored, so never claim the slot contains them
	
	if pOutfitItem == nil then
		return false, nil;
	end
	
	-- If the item specifies and empty slot check to see if the slot is actually empty
	
	if pOutfitItem.Code == 0 then
		return pEquippableItems.InventoryItems[pInventorySlot] == nil;
	end
	
	local	vItems = {};
	local	vNumItems = OutfitterItemList_FindAllItemsOrAlt(pEquippableItems, pOutfitItem, nil, vItems);
	
	if vNumItems == 0 then
		return false;
	elseif vNumItems == 1 then
		-- If there's only one of that item then the enchant code
		-- is disregarded so just make sure it's in the slot
		
		return vItems[1].SlotName == pInventorySlot, vItems[1];
	else
		-- See if one of the items is in the slot
		
		for vIndex, vItem in vItems do
			if vItem.SlotName == pInventorySlot then
				-- Must match the enchant code if there are multiple items
				-- in order to be considered a perfect match
				
				return vItem.EnchantCode == pOutfitItem.EnchantCode, vItem;
			end
		end
		
		-- No items in the slot
		
		return false, nil;
	end
end

function OutfitterQuickSlots_Open(pSlotName)
	local	vPaperDollSlotName = "Character"..pSlotName;
	
	-- Hide the tooltip so that it isn't in the way
	
	GameTooltip:Hide();
	
	-- Position the window
	
	if pSlotName == "MainHandSlot"
	or pSlotName == "SecondaryHandSlot"
	or pSlotName == "RangedSlot"
	or pSlotName == "AmmoSlot" then
		OutfitterQuickSlots:SetPoint("TOPLEFT", vPaperDollSlotName, "BOTTOMLEFT", 0, 0);
	else
		OutfitterQuickSlots:SetPoint("TOPLEFT", vPaperDollSlotName, "TOPRIGHT", 5, 6);
	end
	
	OutfitterQuickSlots.SlotName = pSlotName;
	
	-- Populate the items
	
	local	vItems = Outfitter_FindItemsInBagsForSlot(pSlotName);
	local	vNumSlots = 0;
	
	if vItems then
		for vItemInfoIndex, vItemInfo in vItems do
			if vNumSlots >= Outfitter_cMaxNumQuickSlots then
				break;
			end
			
			vNumSlots = vNumSlots + 1;
			OutfitterQuickSlots_SetSlotToBag(vNumSlots, vItemInfo.BagIndex, vItemInfo.BagSlotIndex);
		end
	end
	
	-- If the slot isn't empty, offer an empty slot to put the item in
	
	if vNumSlots < Outfitter_cMaxNumQuickSlots
	and not Outfitter_InventorySlotIsEmpty(pSlotName) then
		local	vBagSlotInfo = Outfitter_GetEmptyBagSlot();
		
		if vBagSlotInfo then
			vNumSlots = vNumSlots + 1;
			OutfitterQuickSlots_SetSlotToBag(vNumSlots, vBagSlotInfo.BagIndex, vBagSlotInfo.BagSlotIndex);
		end
	end
	
	-- Resize the window and show it
	
	OutfitterQuickSlots_SetNumSlots(vNumSlots);
	
	if vNumSlots == 0 then
		OutfitterQuickSlots:Hide();
	else
		OutfitterQuickSlots:Show();
	end
end

function OutfitterQuickSlots_Close()
	OutfitterQuickSlots:Hide();
end

function OutfitterQuickSlots_OnLoad()
	table.insert(UIMenus, this:GetName());
end

function OutfitterQuickSlots_OnShow()
end

function OutfitterQuickSlots_OnHide()
end

function OutfitterQuickSlots_OnEvent(pEvent)
end

function OutfitterQuickSlotItem_OnLoad()
	this.size = 1; -- one-slot container
end

function OutfitterQuickSlotItem_OnShow()
	this:RegisterEvent("BAG_UPDATE");
	this:RegisterEvent("BAG_UPDATE_COOLDOWN");
	this:RegisterEvent("ITEM_LOCK_CHANGED");
	this:RegisterEvent("UPDATE_INVENTORY_ALERTS");
end

function OutfitterQuickSlotItem_OnHide()
	this:UnregisterEvent("BAG_UPDATE");
	this:UnregisterEvent("BAG_UPDATE_COOLDOWN");
	this:UnregisterEvent("ITEM_LOCK_CHANGED");
	this:UnregisterEvent("UPDATE_INVENTORY_ALERTS");
end

function OutfitterQuickSlotItemButton_OnEnter(button)
	GameTooltip:SetOwner(button, "ANCHOR_RIGHT");
	
	local	vBagIndex = button:GetParent():GetID();
	local	vBagSlotIndex = button:GetID();
	
	local	hasItem, hasCooldown, repairCost;
	
	if vBagIndex == -1 then
		hasItem, hasCooldown, repairCost = GameTooltip:SetInventoryItem("player", BankButtonIDToInvSlotID(vBagSlotIndex));
	else
		hasCooldown, repairCost = GameTooltip:SetBagItem(vBagIndex, vBagSlotIndex);
	end
	
	if ( InRepairMode() and (repairCost and repairCost > 0) ) then
		GameTooltip:AddLine(TEXT(REPAIR_COST), "", 1, 1, 1);
		SetTooltipMoney(GameTooltip, repairCost);
		GameTooltip:Show();
	elseif ( this.readable or (IsControlKeyDown() and button.hasItem) ) then
		ShowInspectCursor();
	elseif ( MerchantFrame:IsVisible() and MerchantFrame.selectedTab == 1 ) then
		ShowContainerSellCursor(button:GetParent():GetID(),button:GetID());
	else
		ResetCursor();
	end
end

function OutfitterQuickSlots_SetNumSlots(pNumSlots)
	if pNumSlots > Outfitter_cMaxNumQuickSlots then
		pNumSlots = Outfitter_cMaxNumQuickSlots;
	end
	
	local	vBaseWidth = 11;
	local	vSlotWidth = 42;
	
	for vIndex = 1, pNumSlots do
		local	vSlotItem = getglobal("OutfitterQuickSlotsItem"..vIndex);
		
		vSlotItem:ClearAllPoints();
		
		if vIndex == 1 then
			vSlotItem:SetPoint("TOPLEFT", "OutfitterQuickSlots", "TOPLEFT", 6, -6);
		else
			vSlotItem:SetPoint("TOPLEFT", "OutfitterQuickSlotsItem"..(vIndex - 1), "TOPLEFT", vSlotWidth, 0);
		end
		
		vSlotItem:Show();
	end
	
	-- Hide the unused slots
	
	for vIndex = pNumSlots + 1, Outfitter_cMaxNumQuickSlots do
		local	vSlotItem = getglobal("OutfitterQuickSlotsItem"..vIndex);
		
		vSlotItem:Hide();
	end
	
	-- Size the frame
	
	OutfitterQuickSlots:SetWidth(vBaseWidth + vSlotWidth * pNumSlots);
	
	-- Fix the background
	
	if pNumSlots > 0 then
		for vIndex = 1, pNumSlots - 1 do
			getglobal("OutfitterQuickSlotsBack"..vIndex):Show();
		end
		
		for vIndex = pNumSlots, Outfitter_cMaxNumQuickSlots - 1 do
			getglobal("OutfitterQuickSlotsBack"..vIndex):Hide();
		end
		
		OutfitterQuickSlotsBackEnd:SetPoint("LEFT", "OutfitterQuickSlotsBack"..(pNumSlots - 1), "RIGHT", 0, 0);
	end
end

function OutfitterQuickSlots_SetSlotToBag(pQuickSlotIndex, pBagIndex, pBagSlotIndex)
	local	vQuickSlotItem = getglobal("OutfitterQuickSlotsItem"..pQuickSlotIndex);
	local	vQuickSlotItemButton = getglobal("OutfitterQuickSlotsItem"..pQuickSlotIndex.."Item1");
	
	vQuickSlotItem:SetID(pBagIndex);
	vQuickSlotItemButton:SetID(pBagSlotIndex);
	
	ContainerFrame_Update(vQuickSlotItem);
end

function Outfitter_RegisterEvent(pFrame, pEvent, pHandler)
	if not pFrame.EventHandlers then
		pFrame.EventHandlers = {};
	end
	
	pFrame.EventHandlers[pEvent] = pHandler;
	pFrame:RegisterEvent(pEvent);
end

function Outfitter_UnregisterEvent(pFrame, pEvent)
	if pFrame.EventHandlers then
		pFrame.EventHandlers[pEvent] = nil;
	end
	
	pFrame:UnregisterEvent(pEvent);
end

function Outfitter_SuspendEvent(pFrame, pEvent)
	if not pFrame.EventHandlers
	or not pFrame.EventHandlers[pEvent] then
		return;
	end

	pFrame:UnregisterEvent(pEvent);
end

function Outfitter_ResumeEvent(pFrame, pEvent)
	if not pFrame.EventHandlers
	or not pFrame.EventHandlers[pEvent] then
		return;
	end

	pFrame:RegisterEvent(pEvent);
end

function Outfitter_DispatchEvent(pFrame, pEvent)
	if not pFrame.EventHandlers then	
		return false;
	end
	
	local	vEventHandler = pFrame.EventHandlers[pEvent];
	
	if not vEventHandler then
		return false;
	end
	
	Outfitter_BeginEquipmentUpdate();
	vEventHandler(pEvent);
	Outfitter_EndEquipmentUpdate("Outfitter_DispatchEvent("..pEvent..")");
	
	return true;
end

function Outfitter_GetPlayerStat(pStatIndex)
	local	_, vEffectiveValue, vPosValue, vNegValue = UnitStat("player", pStatIndex);
	
	return vEffectiveValue - vPosValue - vNegValue, vPosValue + vNegValue;
end

function Outfitter_DepositOutfit(pOutfit, pUniqueItemsOnly)
	-- Deselect any outfits to avoid them from being updated when
	-- items get put away
	
	Outfitter_ClearSelection();
	
	-- Build a list of items for the outfit
	
	local	vEquippableItems = OutfitterItemList_GetEquippableItems();
	
	OutfitterItemList_ResetIgnoreItemFlags(vEquippableItems);
	
	-- Make a copy of the outfit
	
	local	vUnequipOutfit = Outfitter_NewEmptyOutfit();
	
	for vInventorySlot, vItem in pOutfit.Items do
		vUnequipOutfit.Items[vInventorySlot] = vItem;
	end
	
	-- Subtract out items from other outfits if unique is specified
	
	if pUniqueItemsOnly then
		for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
			for vOutfitIndex, vOutfit in vOutfits do
				if vOutfit ~= pOutfit then
					local	vMissingItems, vBankedItems = OutfitterItemList_GetMissingItems(vEquippableItems, vOutfit);
					
					-- Only subtract out items from outfits which aren't themselves partialy banked
					
					if vBankedItems == nil then
						Outfitter_SubtractOutfit(vUnequipOutfit, vOutfit, true);
					end
				end -- if vOutfit
			end -- for vOutfitIndex
		end -- for vCategoryID
	end -- if pUniqueItemsOnly
	
	-- Build the change list
	
	OutfitterItemList_ResetIgnoreItemFlags(vEquippableItems);
	
	local	vEquipmentChangeList = Outfitter_BuildUnequipChangeList(vUnequipOutfit, vEquippableItems);
	
	if not vEquipmentChangeList then
		return;
	end
	
	-- Eliminate items which are already banked
	
	local	vChangeIndex = 1;
	local	vNumChanges = table.getn(vEquipmentChangeList);
	
	while vChangeIndex <= vNumChanges do
		vEquipmentChange = vEquipmentChangeList[vChangeIndex];
		
		if Outfitter_IsBankBagIndex(vEquipmentChange.FromLocation.BagIndex) then
			table.remove(vEquipmentChangeList, vChangeIndex);
			vNumChanges = vNumChanges - 1;
		else
			vChangeIndex = vChangeIndex + 1;
		end
	end
	
	-- Get the list of empty bank slots
	
	local	vEmptyBankSlots = Outfitter_GetEmptyBankSlotList();
	
	-- Execute the changes
	
	Outfitter_ExecuteEquipmentChangeList2(vEquipmentChangeList, vEmptyBankSlots, Outfitter_cDepositBagsFullError, vExpectedEquippableItems);
end

function Outfitter_WithdrawOutfit(pOutfit)
	local	vEquippableItems = OutfitterItemList_GetEquippableItems();
	
	-- Build a list of items for the outfit
	
	OutfitterItemList_ResetIgnoreItemFlags(vEquippableItems);
	
	local	vEquipmentChangeList = Outfitter_BuildUnequipChangeList(pOutfit, vEquippableItems);
	
	if not vEquipmentChangeList then
		return;
	end
	
	-- Eliminate items which aren't in the bank
	
	local	vChangeIndex = 1;
	local	vNumChanges = table.getn(vEquipmentChangeList);
	
	while vChangeIndex <= vNumChanges do
		vEquipmentChange = vEquipmentChangeList[vChangeIndex];
		
		if not Outfitter_IsBankBagIndex(vEquipmentChange.FromLocation.BagIndex) then
			table.remove(vEquipmentChangeList, vChangeIndex);
			vNumChanges = vNumChanges - 1;
		else
			vChangeIndex = vChangeIndex + 1;
		end
	end
	
	-- Get the list of empty bag slots

	local	vEmptyBagSlots = Outfitter_GetEmptyBagSlotList();
	
	-- Execute the changes
	
	Outfitter_ExecuteEquipmentChangeList2(vEquipmentChangeList, vEmptyBagSlots, Outfitter_cWithdrawBagsFullError, vExpectedEquippableItems);
end

function Outfitter_TestOutfitCombinations()
	local	vEquippableItems = OutfitterItemList_GetEquippableItems(true);
	local	vFilterStats = {["FireResist"] = true};
	local	vOutfit = Outfitter_FindOutfitCombination(vEquippableItems, vFilterStats, Outfitter_OutfitTestEval, {});
end

function Outfitter_OutfitTestEval(pOpcode, pParams, pOutfit1, pOutfit2)
	if pOpcode == "INIT" then
		Outfitter_TestMessage("Outfitter_OutfitTestEval: INIT");
	elseif pOpcode == "COMPARE" then
		Outfitter_TestMessage("Outfitter_OutfitTestEval: COMPARE");
	end
end

function Outfitter_FindOutfitCombination(pEquippableItems, pFilterStats, pOutfitEvalFunc, pOutfitEvalParams)
	local	vSlotIterators = OutfitterSlotIterators_New(pEquippableItems, pFilterStats);
	
	Outfitter_DumpArray("vSlotIterators", vSlotIterators);
	
	local	vBestOutfit = nil;
	local	vNumIterations = 0;
	
	pOutfitEvalFunc("INIT", pOutfitEvalParams);
	
	while vSlotIterators:Increment() do
		local	vOutfit = vSlotIterators:GetOutfit();
		
		if pOutfitEvalFunc("COMPARE", pOutfitEvalParams, vBestOutfit, vOutfit) then
			vBestOutfit = vOutfit;
		end
		
		vNumIterations = vNumIterations + 1;
		
		if vNumIterations > 20 then
			return vBestOutfit;
		end
	end
	
	return vBestOutfit;
end

function Outfitter_ItemContainsStats(pItem, pFilterStats)
	for vStatID, _ in pFilterStats do
		if pItem.Stats[vStatID] then
			return true;
		end
	end
	
	return false;
end

function OutfitterSlotIterators_New(pEquippableItems, pFilterStats)
	local	vSlotIterators = {Slots = {}};
	local	vNumCombinations = 1;
	
	for vInventorySlot, vItems in pEquippableItems.ItemsBySlot do
		local	vNumItems = table.getn(vItems);
		
		if vInventorySlot ~= "AmmoSlot"
		and vNumItems > 0 then
			-- Filter the items by stat
			
			local	vFilteredItems = nil;
			
			if pFilterStats then
				vNumItems = 0;
				
				for vItemIndex, vItem in vItems do
					if Outfitter_ItemContainsStats(vItem, pFilterStats) then
						if not vFilteredItems then
							vFilteredItems = {};
						end
						
						table.insert(vFilteredItems, vItem);
						vNumItems = vNumItems + 1;
					end
				end
			else
				vFilteredItems = vItems;
			end
			
			-- Add the filtered list
			
			if vFilteredItems then
				table.insert(vSlotIterators.Slots, {ItemSlotName = vInventorySlot, Items = vItems, Index = 0, MaxIndex = vNumItems});
				
				vNumCombinations = vNumCombinations * (vNumItems + 1);
				
				Outfitter_TestMessage("OutfitterSlotIterators_New: "..vInventorySlot.." has "..vNumItems.." items. Combinations "..vNumCombinations);
			end
		end
	end
	
	vSlotIterators.Increment = OutfitterSlotIterators_Increment;
	vSlotIterators.GetOutfit = OutfitterSlotIterators_GetOutfit;
	vSlotIterators.NumCombinations = vNumCombinations;
	
	Outfitter_TestMessage("OutfitterSlotIterators_New: Total combinations "..vNumCombinations);
	
	return vSlotIterators;
end

function OutfitterSlotIterators_Increment(pSlotIterators)
	for vSlotIndex, vSlotIterator in pSlotIterators.Slots do
		vSlotIterator.Index = vSlotIterator.Index + 1;
		
		if vSlotIterator.Index <= vSlotIterator.MaxIndex then
			return true;
		end
		
		vSlotIterator.Index = 0;
	end
	
	return false; -- Couldn't increment
end

function OutfitterSlotIterators_GetOutfit(pSlotIterators)
	local	vOutfit = Outfitter_NewEmptyOutfit();

	for _, vItems in pSlotIterators.Slots do
		-- if vItems.Index > 0 then
		-- 	local	vItem = vItems.Items[vItems.Index];
		-- 	
		-- 	Outfitter_AddOutfitItem(vOutfit, vItems.ItemSlotName, vItem.Code, vItem.SubCode, vItem.Name, vItem.EnchantCode);
		-- end
	end
	
	return vOutfit;
end

function OutfitterStats_AddStatValue(pStats, pStat, pValue, pDistribution)
	if not pStats[pStat] then
		pStats[pStat] = pValue;
	else
		pStats[pStat] = pStats[pStat] + pValue;
	end
	
	if not pDistribution then
		return;
	end
	
	local	vStatDistribution = pDistribution[pStat];
	
	if not vStatDistribution then
		return;
	end
	
	for vSecondaryStat, vFactors in vStatDistribution do
		local	vSecondaryValue = pValue * vFactors.Coeff;
		
		if vFactors.Const then
			vSecondaryValue = vSecondaryValue + vFactors.Const;
		end
		
		if pStats[vSecondaryStat] then
			pStats[vSecondaryStat] = pStats[vSecondaryStat] + vSecondaryValue;
		else
			pStats[vSecondaryStat] = vSecondaryValue;
		end
	end
end

function OutfitterStats_SubtractStats(pStats, pStats2)
	for vStat, vValue in pStats2 do
		if pStats[vStat] then
			pStats[vStat] = pStats[vStat] - vValue;
		end
	end
end

function OutfitterStats_AddStats(pStats, pStats2)
	for vStat, vValue in pStats2 do
		if pStats[vStat] then
			pStats[vStat] = pStats[vStat] + vValue;
		else
			pStats[vStat] = vValue;
		end
	end
end

function OutfitterTankPoints_New()
	local	vTankPointData = {};
	local	_, vPlayerClass = UnitClass("player");
	local	vStatDistribution = gOutfitter_StatDistribution[vPlayerClass];
	
	if not vStatDistribution then
		Outfitter_ErrorMessage("Outfitter: Missing stat distribution data for "..vPlayerClass);
	end
	
	vTankPointData.PlayerLevel = UnitLevel("player");
	vTankPointData.StaminaFactor = 1.0; -- Warlocks with demonic embrace = 1.15
	
	-- Get the base stats
	
	vTankPointData.BaseStats = {};
	
	OutfitterStats_AddStatValue(vTankPointData.BaseStats, "Strength", UnitStat("player", 1), vStatDistribution);
	OutfitterStats_AddStatValue(vTankPointData.BaseStats, "Agility", UnitStat("player", 2), vStatDistribution);
	OutfitterStats_AddStatValue(vTankPointData.BaseStats, "Stamina", UnitStat("player", 3), vStatDistribution);
	OutfitterStats_AddStatValue(vTankPointData.BaseStats, "Intellect", UnitStat("player", 4), vStatDistribution);
	OutfitterStats_AddStatValue(vTankPointData.BaseStats, "Spirit", UnitStat("player", 5), vStatDistribution);
	
	OutfitterStats_AddStatValue(vTankPointData.BaseStats, "Health", UnitHealthMax("player"), vStatDistribution);
	
	vTankPointData.BaseStats.Health = vTankPointData.BaseStats.Health - vTankPointData.BaseStats.Stamina * 10;
	
	vTankPointData.BaseStats.Dodge = GetDodgeChance();
	vTankPointData.BaseStats.Parry = GetParryChance();
	vTankPointData.BaseStats.Block = GetBlockChance();
	
	local	vBaseDefense, vBuffDefense = UnitDefense("player");
	OutfitterStats_AddStatValue(vTankPointData.BaseStats, "Defense", vBaseDefense + vBuffDefense, vStatDistribution);
	
	-- Replace the armor with the current value since that already includes various factors
	
	local	vBaseArmor, vEffectiveArmor, vArmor, vArmorPosBuff, vArmorNegBuff = UnitArmor("player");
	vTankPointData.BaseStats.Armor = vEffectiveArmor;
	
	Outfitter_TestMessage("------------------------------------------");
	Outfitter_DumpArray("vTankPointData", vTankPointData);
	
	-- Subtract out the current outfit
	
	local	vCurrentOutfitStats = OutfitterTankPoints_GetCurrentOutfitStats(vStatDistribution);
	
	Outfitter_TestMessage("------------------------------------------");
	Outfitter_DumpArray("vCurrentOutfitStats", vCurrentOutfitStats);
	
	OutfitterStats_SubtractStats(vTankPointData.BaseStats, vCurrentOutfitStats);
	
	-- Calculate the buff stats (stuff from auras/spell buffs/whatever)
	
	vTankPointData.BuffStats = {};
	
	-- Reset the cumulative values
	
	OutfitterTankPoints_Reset(vTankPointData);
	
	Outfitter_TestMessage("------------------------------------------");
	Outfitter_DumpArray("vTankPointData", vTankPointData);
	
	Outfitter_TestMessage("------------------------------------------");
	return vTankPointData;
end

function OutfitterTankPoints_Reset(pTankPointData)
	pTankPointData.AdditionalStats = {};
end

function OutfitterTankPoints_GetTotalStat(pTankPointData, pStat)
	local	vTotalStat = pTankPointData.BaseStats[pStat];
	
	if not vTotalStat then
		vTotalStat = 0;
	end
	
	local	vAdditionalStat = pTankPointData.AdditionalStats[pStat];
	
	if vAdditionalStat then
		vTotalStat = vTotalStat + vAdditionalStat;
	end
	
	local	vBuffStat = pTankPointData.BuffStats[pStat];
	
	if vBuffStat then
		vTotalStat = vTotalStat + vBuffStat;
	end
	
	--
	
	return vTotalStat;
end

function OutfitterTankPoints_CalcTankPoints(pTankPointData, pStanceModifier)
	if not pStanceModifier then
		pStanceModifier = 1;
	end
	
	Outfitter_DumpArray("pTankPointData", pTankPointData);
	
	local	vEffectiveArmor = OutfitterTankPoints_GetTotalStat(pTankPointData, "Armor");

	Outfitter_TestMessage("Armor: "..vEffectiveArmor);
	
	local	vArmorReduction = vEffectiveArmor / ((85 * pTankPointData.PlayerLevel) + 400);
	
	vArmorReduction = vArmorReduction / (vArmorReduction + 1);
	
	local	vEffectiveHealth = OutfitterTankPoints_GetTotalStat(pTankPointData, "Health");
	
	Outfitter_TestMessage("Health: "..vEffectiveHealth);
	
	Outfitter_TestMessage("Stamina: "..OutfitterTankPoints_GetTotalStat(pTankPointData, "Stamina"));
	
	--
	
	local	vEffectiveDodge = OutfitterTankPoints_GetTotalStat(pTankPointData, "Dodge") * 0.01;
	local	vEffectiveParry = OutfitterTankPoints_GetTotalStat(pTankPointData, "Parry") * 0.01;
	local	vEffectiveBlock = OutfitterTankPoints_GetTotalStat(pTankPointData, "Block") * 0.01;
	local	vEffectiveDefense = OutfitterTankPoints_GetTotalStat(pTankPointData, "Defense");
	
	-- Add agility and defense to dodge
	
	-- defenseInputBox:GetNumber() * 0.04 + agiInputBox:GetNumber() * 0.05

	Outfitter_TestMessage("Dodge: "..vEffectiveDodge);
	Outfitter_TestMessage("Parry: "..vEffectiveParry);
	Outfitter_TestMessage("Block: "..vEffectiveBlock);
	Outfitter_TestMessage("Defense: "..vEffectiveDefense);
	
	local	vDefenseModifier = (vEffectiveDefense - pTankPointData.PlayerLevel * 5) * 0.04 * 0.01;
	
	Outfitter_TestMessage("Crit reduction: "..vDefenseModifier);
	
	local	vMobCrit = max(0, 0.05 - vDefenseModifier);
	local	vMobMiss = 0.05 + vDefenseModifier;
	local	vMobDPS = 1;
	
	local	vTotalReduction = 1 - (vMobCrit * 2 + (1 - vMobCrit - vMobMiss - vEffectiveDodge - vEffectiveParry)) * (1 - vArmorReduction) * pStanceModifier;
	
	Outfitter_TestMessage("Total reduction: "..vTotalReduction);
	
	local	vTankPoints = vEffectiveHealth / (vMobDPS * (1 - vTotalReduction));
	
	return vTankPoints;
	
	--[[
	Stats used in TankPoints calculation:
		Health
		Dodge
		Parry
		Block
		Defense
		Armor
	]]--
end

function OutfitterTankPoints_GetCurrentOutfitStats(pStatDistribution)
	local	vTotalStats = {};
	
	for _, vSlotName in Outfitter_cSlotNames do
		local	vStats = OutfitterItemList_GetItemStats({SlotName = vSlotName});
		
		if vStats then
			for vStat, vValue in vStats do
				OutfitterStats_AddStatValue(vTotalStats, vStat, vValue, pStatDistribution);
			end
		end
	end
	
	return vTotalStats;
end

function OutfitterTankPoints_Test()
	local	_, vPlayerClass = UnitClass("player");
	local	vStatDistribution = gOutfitter_StatDistribution[vPlayerClass];
	
	local	vTankPointData = OutfitterTankPoints_New();
	local	vStats = OutfitterTankPoints_GetCurrentOutfitStats(vStatDistribution);
	
	OutfitterStats_AddStats(vTankPointData.AdditionalStats, vStats);
	
	local	vTankPoints = OutfitterTankPoints_CalcTankPoints(vTankPointData);
	
	Outfitter_TestMessage("TankPoints = "..vTankPoints);
end

function Outfitter_TestAmmoSlot()
	local	vItemInfo = Outfitter_GetInventoryItemInfo("AmmoSlot");
	local	vSlotID = GetInventorySlotInfo("AmmoSlot");
	local	vItemLink = GetInventoryItemLink("player", vSlotID);
	
	Outfitter_TestMessage("SlotID: "..vSlotID);
	Outfitter_TestMessage("ItemLink: "..vItemLink);
	
	Outfitter_DumpArray("vItemInfo", vItemInfo);
end
