Outfitter.EquipmentUpdateCount = 0

function Outfitter:DebugEquipmentChangeList(pEquipmentChangeList)
	self:DebugMark()
	self:DebugTable(pEquipmentChangeList, "ChangeList")
end

function Outfitter:NewEquipmentChange(pInventorySlot, pItemName)
	local vSlotID = self.cSlotIDs[pInventorySlot]
	local vEquipmentChange = {SlotName = pInventorySlot, SlotID = vSlotID, ItemName = pItemName, UniqueGemTotals = {}}
	
	self:SubtractInventoryUniqueGems(vSlotID, vEquipmentChange.UniqueGemTotals)
	
	return vEquipmentChange
end

function Outfitter:ShowEquipError(pOutfitItem, pIgnoredItem, pInventorySlot)
	if self.Settings.DisableEquipErrors then
		return
	end
	
	local vSlotDisplayName = self.cSlotDisplayNames[pInventorySlot]
	
	if not vSlotDisplayName then
		vSlotDisplayName = pInventorySlot or "nil"
	end
	
	if pOutfitItem.Name then
		if pIgnoredItem then
			self:ErrorMessage(format(self.cItemAlreadyUsedError, self:GenerateItemLink(pOutfitItem), vSlotDisplayName))
			self:DebugMessage("item already used for slot %s", vSlotDisplayName)
		else
			self:ErrorMessage(format(self.cItemNotFoundError, self:GenerateItemLink(pOutfitItem), vSlotDisplayName))
			self:DebugMessage("can't find item for slot %s", vSlotDisplayName)
		end
	else
		self:ErrorMessage(format(self.cItemNotFoundError, "unknown", vSlotDisplayName))
	end
end

function Outfitter:SubtractInventoryUniqueGems(pSlotID, pUniqueGemTotals)
	if not self.TempGemCodes then
		self.TempGemCodes = {}
	end
	
	self.TempGemCodes[1], self.TempGemCodes[2], self.TempGemCodes[3], self.TempGemCodes[4] = GetInventoryItemGems(pSlotID)
	
	for vGemIndex = 1, 4 do
		local vGemItemCode = self.TempGemCodes[vGemIndex]
		local vUniqueGemCode = vGemItemCode and self.cUniqueGemItemIDs[vGemItemCode]
		
		if vUniqueGemCode then
			pUniqueGemTotals[vUniqueGemCode] = (pUniqueGemTotals[vUniqueGemCode] or 0) - 1
		end
	end
end	

function Outfitter:AddLocationUniqueGems(pLocation, pUniqueGemTotals)
	if not self.TempGemCodes then
		self.TempGemCodes = {}
	end
	
	if pLocation.BagIndex then
		self.TempGemCodes[1], self.TempGemCodes[2], self.TempGemCodes[3], self.TempGemCodes[4] = GetContainerItemGems(pLocation.BagIndex, pLocation.BagSlotIndex)
	else
		self.TempGemCodes[1], self.TempGemCodes[2], self.TempGemCodes[3], self.TempGemCodes[4] = GetInventoryItemGems(pLocation.SlotID)
	end
	
	for vGemIndex = 1, 4 do
		local vGemItemCode = self.TempGemCodes[vGemIndex]
		local vUniqueGemCode = vGemItemCode and self.cUniqueGemItemIDs[vGemItemCode]
		
		if vUniqueGemCode then
			pUniqueGemTotals[vUniqueGemCode] = (pUniqueGemTotals[vUniqueGemCode] or 0) + 1
		end
	end
end	

function Outfitter:FindEquipmentChangeForSlot(pEquipmentChangeList, pSlotName)
	for vChangeIndex, vEquipmentChange in ipairs(pEquipmentChangeList) do
		if not vEquipmentChange.TGFix and vEquipmentChange.SlotName == pSlotName then
			return vChangeIndex, vEquipmentChange
		end
	end
	
	return nil, nil
end

function Outfitter:PickupItemLocation(pItemLocation)
	if pItemLocation == nil then
		self:ErrorMessage("nil location in PickupItemLocation")
		return
	end
	
	if pItemLocation.BagIndex then
		if CT_oldPickupContainerItem then
			CT_oldPickupContainerItem(pItemLocation.BagIndex, pItemLocation.BagSlotIndex)
		else
			PickupContainerItem(pItemLocation.BagIndex, pItemLocation.BagSlotIndex)
		end
	elseif pItemLocation.SlotName then
		PickupInventoryItem(self.cSlotIDs[pItemLocation.SlotName])
	else
		self:ErrorMessage("Unknown location in PickupItemLocation")
		return
	end
end

function Outfitter:BuildUnequipChangeList(pOutfit, pInventoryCache)
	self.EquipmentChangeList = self:RecycleTable(self.EquipmentChangeList)
	
	local vEquipmentChangeList = self.EquipmentChangeList
	local vItems = pOutfit:GetItems()
	
	for vInventorySlot, vOutfitItem in pairs(vItems) do
		local vItem, vIgnoredItem = pInventoryCache:FindItemOrAlt(vOutfitItem, true)
		
		if vItem then
			table.insert(vEquipmentChangeList, {FromLocation = vItem.Location, Item = vItem})
		end
	end -- for
	
	return vEquipmentChangeList
end

function Outfitter:BuildEquipmentChangeList(pOutfit, pInventoryCache)
	self.EquipmentChangeList = self:RecycleTable(self.EquipmentChangeList)
	
	local vEquipmentChangeList = self.EquipmentChangeList
	
	pInventoryCache:ResetIgnoreItemFlags()
	
	-- Remove items which are already in the correct slot from the outfit and from the
	-- equippable items list
	
	local vItems = pOutfit:GetItems()
	
	for vInventorySlot, vOutfitItem in pairs(vItems) do
		local vContainsItem, vItem = pInventoryCache:InventorySlotContainsItem(vInventorySlot, vOutfitItem)
		
		if vContainsItem then
			pOutfit:RemoveItem(vInventorySlot)
			
			if vItem then
				vItem.IgnoreItem = true
			end
		end
	end
	
	-- WoW has a bug with dual-spec where you can end up with dual 2H weapons equipped even
	-- though you may no longer have Titan's Grip.  To correct this, I detect the situation here
	-- and insert an unequip operation for the 2H'er in the OH slot to restore the equipment
	-- to a valid state.
	
	local vHas2HGlitch
	local vSecondaryHandItem = pInventoryCache.InventoryItems.SecondaryHandSlot
	
	if not self.CanDualWield2H
	and vSecondaryHandItem
	and vSecondaryHandItem.InvType == "INVTYPE_2HWEAPON" then
		vHas2HGlitch = true
		
		local vEquipmentChange = self:NewEquipmentChange("SecondaryHandSlot", vSecondaryHandItem.Name)
		
		vEquipmentChange.TGFix = true
		
		table.insert(vEquipmentChangeList, vEquipmentChange)
	end
	
	-- Scan the outfit using the Outfitter.cSlotNames array as an index so that changes
	-- are executed in the specified order.  The order is designed so that items with
	-- durability values are unequipped first, followed by other items such as cloaks and rings
	-- which have no durability.  This makes unequipping before death (falling, raid/party wipe) more useful
	-- since it'll reduce the cost of repairs
	
	for _, vInventorySlot in ipairs(self.cSlotNames) do
		self:AddSlotToChangeList(pOutfit, pInventoryCache, vInventorySlot, vHas2HGlitch, vEquipmentChangeList)
	end -- for
	
	if #vEquipmentChangeList == 0 then
		return nil
	end
	
	self:AdjustGemSwaps(vEquipmentChangeList)
	self:OptimizeEquipmentChangeList(vEquipmentChangeList)
	
	return vEquipmentChangeList
end

function Outfitter:AddSlotToChangeList(pOutfit, pInventoryCache, pInventorySlot, pHas2HGlitch, pEquipmentChangeList)
	local vOutfitItem = pOutfit:GetItem(pInventorySlot)
	
	if not vOutfitItem then
		return -- Exit if the outfit doesn't want this slot modified
	end
	
	local vSlotID = self.cSlotIDs[pInventorySlot]
	
	local vCurrentItemCode,
	      vCurrentItemEnchantCode,
	      vCurrentItemJewelCode1,
	      vCurrentItemJewelCode2,
	      vCurrentItemJewelCode3,
	      vCurrentItemJewelCode4,
	      vCurrentItemSubCode,
	      vCurrentItemUniqueID,
	      vCurrentItemLinkLevel,
	      vCurrentItemReforgeID,
	      vCurrentItemName = self:GetSlotIDLinkInfo(vSlotID)
	
	local vEquipmentChange = self:NewEquipmentChange(pInventorySlot, vOutfitItem.Name)
	
	--
	
	if pHas2HGlitch and pInventorySlot == "SecondaryHandSlot" then
		vCurrentItemCode = nil -- Pretend there's nothing in the OH since we've queued an unequip for it
	end
	
	-- Empty the slot if it's supposed to be blank
	
	if vOutfitItem.Code == 0 or vOutfitItem.Code == nil then
		if not vCurrentItemCode then
			return -- Nothing to do if the slot is supposed to be empty and already is
		end
	else
		-- Find the item
		
		local vItem, vIgnoredItem = pInventoryCache:FindItemOrAlt(vOutfitItem, true)
		
		-- If the item wasn't found then show an appropriate error message and leave
		
		if not vItem then
			self:ShowEquipError(vOutfitItem, vIgnoredItem, pInventorySlot)
			return
		end
		
		-- Add the unique-equipped gem counts for the item being equipped
		
		self:AddLocationUniqueGems(vItem.Location, vEquipmentChange.UniqueGemTotals)
		
		-- Update the change with info for the item being equipped
		
		pOutfit:GetItem(pInventorySlot).MetaSlotName = vItem.MetaSlotName -- This may be obsolete, a remnant from my transition to including the MetaSlotName field
		
		vEquipmentChange.ItemMetaSlotName = vItem.MetaSlotName
		vEquipmentChange.ItemLocation = vItem.Location
		
		-- Treat the item as a 1H if they can dual wield it
		
		if vEquipmentChange.ItemMetaSlotName == "TwoHandSlot"
		and not self:ItemUsesBothWeaponSlots(vOutfitItem) then
			vEquipmentChange.ItemMetaSlotName = "Weapon0Slot"
		end
	end -- else vOutfitItem.Code == 0 or vOutfitItem.Code == nil
	
	-- Insert the change
	
	table.insert(pEquipmentChangeList, vEquipmentChange)
end

function Outfitter:GetGemChangeRank(pChange, pGemCountOffsets)
	local vHasNegative, vHasPositive
	
	for vGemID, vGemCount in pairs(pChange.UniqueGemTotals) do
		if pGemCountOffsets then
			vGemCount = vGemCount + (pGemCountOffsets[vGemID] or 0)
		end
		
		if vGemCount < 0 then
			vHasNegative = true
			
			if vHasPositive then
				break
			end
		elseif vGemCount > 0 then
			vHasPositive = true
			
			if vHasNegative then
				break
			end
		end
	end
	
	if vHasNegative and vHasPositive then
		return 2
	elseif vHasPositive then	
		return 3
	end
	
	-- No changes or only negative changes are rank 1
	
	return 1
end

function Outfitter:AdjustGemSwaps(pEquipmentChangeList)
	-- Calculate the sort rank for each change
	
	for vIndex, vChange in ipairs(pEquipmentChangeList) do
		vChange.GemChangeRank = self:GetGemChangeRank(vChange)
		vChange.GemChangeRank2 = Outfitter.cSlotOrder[vChange.SlotName]
	end
	
	local vPreviousRank1Count, vPreviousRank2Count
	
	while true do
		-- Sort the by rank
		
		table.sort(pEquipmentChangeList, Outfitter.CompareGemsChangeRank)
		
		-- Sum up the gem counts in rank 1 entries and re-assign ranks
		
		self.GemSortStartCounts = self:RecycleTable(self.GemSortStartCounts)
		
		local vRank1Count, vRank2Count = 0, 0
		
		for vIndex, vChange in ipairs(pEquipmentChangeList) do
			-- Rank 1 doesn't change, just total up the counts
			-- and preserve the ordering
			
			if vChange.GemChangeRank == 1 then
				-- Total the counts
				
				for vGemID, vGemCount in pairs(vChange.UniqueGemTotals) do
					self.GemSortStartCounts[vGemID] = (self.GemSortStartCounts[vGemID] or 0) + vGemCount
				end
				
				-- Preserve the ordering
				
				vChange.GemRank2 = vIndex
				
				vRank1Count = vRank1Count + 1
			elseif vChange.GemChangeRank == 2 then
				-- If there's nothing in rank 1 then we're done
				
				if vRank1Count == 0 then
					return
				end
				
				-- Calculate a new rank using the offsets
				
				vChange.GemChangeRank = self:GetGemChangeRank(vChange, self.GemSortStartCounts)
				
				if vChange.GemChangeRank == 1 then
					vRank1Count = vRank1Count + 1
					vChange.GemChangeRank2 = vIndex
				elseif vChange.GemChangeRank == 2 then
					vRank2Count = vRank2Count + 1
				else  -- 3
					vChange.GemChangeRank2 = vIndex
				end
				
			else -- rank 3
				-- If there's nothing in rank 1 or 2 we're done
				
				if vRank1Count == 0
				or vRank2Count == 0 then
					return
				end
				
				-- Preserve the ordering
				
				vChange.GemRank2 = vIndex
			end
		end
		
		-- If the counts don't change we're done, otherwise
		-- do it again
		
		if vPreviousRank1Count == vRank1Count
		and vPreviousRank2Count == vRank2Count then
			break
		end
		
		vPreviousRank1Count = vRank1Count
		vPreviousRank2Count = vRank2Count
	end -- while
end

function Outfitter.CompareGemsChangeRank(pChange1, pChange2)
	if pChange1.GemChangeRank ~= pChange2.GemChangeRank then
		return pChange1.GemChangeRank < pChange2.GemChangeRank
	end
	
	return pChange1.GemChangeRank2 < pChange2.GemChangeRank2
end

function Outfitter:AdjustGemSwaps2(pEquipmentChangeList)
	local vNumChanges = #pEquipmentChangeList
	local vPassCount = 0
	
	for vPassNumber = 1, 4 do
		local vDidRelocate
		
		self.SwapGemTotals = self:RecycleTable(self.SwapGemTotals)
		
		for vChangeIndex = 1, vNumChanges do
			local vEquipmentChange = pEquipmentChangeList[vChangeIndex]
			local vDestIndex
			
			-- Check each unique gem and move the change up until
			-- it compensates for increases
			
			for vUniqueGemID, vGemTotal in pairs(vEquipmentChange.UniqueGemTotals) do
				local vSwapGemTotal = self.SwapGemTotals[vUniqueGemID] or 0
				
				-- If this swap will decrease the count and the current count
				-- shows a net increase, move this swap up to be before the first swap
				-- that increased the count beyond zero
				
				if vGemTotal < 0 and vSwapGemTotal > 0 then
					local vSwapGemTotal2 = vSwapGemTotal
					
					-- Work backwards to find the point at which the count
					-- went positive
					
					for vChangeIndex2 = vChangeIndex - 1, 1, -1 do
						local vEquipmentChange2 = pEquipmentChangeList[vChangeIndex2]
						local vGemTotal2 = vEquipmentChange2.UniqueGemTotals[vUniqueGemID]
						
						if vGemTotal2 then
							vSwapGemTotal2 = vSwapGemTotal2 - vGemTotal2
							
							if vSwapGemTotal2 <= 0 then
								if not vDestIndex or vChangeIndex2 < vDestIndex then
									vDestIndex = vChangeIndex2
								end
								
								break
							end
						end
					end -- for vChangeIndex2
				end -- if vGemTotal < 0
				
				self.SwapGemTotals[vUniqueGemID] = vSwapGemTotal + vGemTotal
			end -- for vUniqueGemID
			
			if vDestIndex then
				pEquipmentChangeList[vChangeIndex] = pEquipmentChangeList[vDestIndex]
				pEquipmentChangeList[vDestIndex] = vEquipmentChange
				
				vDidRelocate = true
			end
		end -- for vChangeIndex
		
		if not vDidRelocate then
			break
		end
	end -- for vPassNumber
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
			if self.Debug.EquipmentChanges then
				self:DebugMessage("FixSlotSwapChange: Rearranging so that %s can move to %s", pSlotName1, pSlotName2)
			end
			
			-- Swap change 1 and change 2 around
			
			pEquipmentList[pChangeIndex1] = pEquipmentChange2
			pEquipmentList[pChangeIndex2] = pEquipmentChange1
			
			-- Insert a change to empty slot 2
			
			local vEmptySlot2 = self:NewEquipmentChange(pEquipmentChange2.SlotName)
			table.insert(pEquipmentList, pChangeIndex1, vEmptySlot2)

			pEquipmentChange1.Optimized = true
			pEquipmentChange2.Optimized = true
			vEmptySlot2.Optimized = true
		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
			
			if self.Debug.EquipmentChanges then
				self:DebugMessage("FixSlotSwapChange: Emptying %s so that %s can move there", pSlotName2, pSlotName1)
			end
			
			pEquipmentChange1.SlotName = pSlotName2
			pEquipmentChange1.SlotID = pEquipmentChange2.SlotID
			pEquipmentChange1.ItemLocation = nil

			pEquipmentChange1.Optimized = true
			pEquipmentChange2.Optimized = true
		end
		
	-- Slot 2 is moving to slot 1
	
	else
		if pEquipmentChange2.ItemLocation then
			if self.Debug.EquipmentChanges then
				self:DebugMessage("FixSlotSwapChange: Rearranging so that %s can move to %s", pSlotName2, pSlotName1)
			end
			
			-- Insert a change to empty slot 1 first
			
			local vEmptySlot1 = self:NewEquipmentChange(pEquipmentChange1.SlotName)
			table.insert(pEquipmentList, pChangeIndex1, vEmptySlot1)

			pEquipmentChange1.Optimized = true
			pEquipmentChange2.Optimized = true
			vEmptySlot1.Optimized = true
		else
			if self.Debug.EquipmentChanges then
				self:DebugMessage("FixSlotSwapChange: Emptying %s so that %s can move there", pSlotName1, pSlotName2)
			end
			
			-- 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

			pEquipmentChange1.Optimized = true
			pEquipmentChange2.Optimized = true
		end
	end
end

Outfitter.SlotSwapList =
{
	Finger0Slot = "Finger1Slot",
	Trinket0Slot = "Trinket1Slot",
	MainHandSlot = "SecondaryHandSlot",
}

function Outfitter:OptimizeEquipmentChangeList(pEquipmentChangeList)
	self.DidOptimizeSlot = self:RecycleTable(self.DidOptimizeSlot)
	local vDidSlot = self.DidOptimizeSlot
	
	local vChangeIndex = 1
	local vNumChanges = #pEquipmentChangeList
	
	while vChangeIndex <= vNumChanges do
		local vEquipmentChange = pEquipmentChangeList[vChangeIndex]
		
		-- Do nothing if this change has already been worked on
		
		if vEquipmentChange.Optimized then
		
		-- If this is the change for the Titan's Grip fix, put it at the very start
		-- so it clears the state before any mainhand/offhand swaps happen
		
		elseif vEquipmentChange.TGFix then
			if vChangeIndex ~= 1 then
				if self.Debug.EquipmentChanges then
					self:DebugMessage("OptimizeEquipmentChangeList: Moving TGFix to the front of the list")
				end
				
				table.remove(pEquipmentChangeList, vChangeIndex)
				table.insert(pEquipmentChangeList, 1, vEquipmentChange)
			end
			
		elseif vEquipmentChange.SlotName == "SecondaryHandSlot" then
			local vChangeIndex2, vEquipmentChange2 = self:FindEquipmentChangeForSlot(pEquipmentChangeList, "MainHandSlot")
			
			-- If equipping an offhand item, make sure it's after equipping
			-- a mainhand item or it will fail when the current mainhand
			-- is a two-hander
			
			if vEquipmentChange.ItemLocation ~= nil then
				if vChangeIndex2 and vChangeIndex2 > vChangeIndex then
					if self.Debug.EquipmentChanges then
						self:DebugMessage("OptimizeEquipmentChangeList: Moving offhand swap to be after mainhand")
					end
					
					table.remove(pEquipmentChangeList, vChangeIndex)
					table.insert(pEquipmentChangeList, vChangeIndex2, vEquipmentChange)
					
					vChangeIndex = vChangeIndex - 1
				end
			
			-- If the off-hand slot is being emptied, then do that before handling the
			-- main hand slot (fixes Titan's Grip problem with the Argent Lance)
			
			elseif vEquipmentChange.ItemLocation == nil then
				if vChangeIndex2 and vChangeIndex2 < vChangeIndex then
					if self.Debug.EquipmentChanges then
						self:DebugMessage("OptimizeEquipmentChangeList: Moving offhand swap to be before mainhand")
					end
					
					table.remove(pEquipmentChangeList, vChangeIndex)
					table.insert(pEquipmentChangeList, vChangeIndex2, vEquipmentChange)
				end
			end
		
		-- If a two-hand weapon is being equipped, remove the change event
		-- for removing the offhand slot
		
		elseif vEquipmentChange.ItemMetaSlotName == "TwoHandSlot" then
			local vChangeIndex2, vEquipmentChange2 = self:FindEquipmentChangeForSlot(pEquipmentChangeList, "SecondaryHandSlot")
			
			-- If there's a change for the offhand slot, remove it
			
			if vChangeIndex2 then
				if self.Debug.EquipmentChanges then
					self:DebugMessage("OptimizeEquipmentChangeList: Removing offhand swap because of 2H being equipped")
				end
				
				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
			
			if self.Debug.EquipmentChanges then
				self:DebugMessage("OptimizeEquipmentChangeList: Emptying offhand before 2H equip")
			end
			
			table.insert(pEquipmentChangeList, vChangeIndex, self:NewEquipmentChange("SecondaryHandSlot", nil))
		
		-- Otherwise see if the change needs to be re-arranged so that slot
		-- swapping works correctly (trinkets, rings, or weapon slots being swapped)
		
		else
			local vSwapSlot = self.SlotSwapList[vEquipmentChange.SlotName]
			
			if vSwapSlot and not vDidSlot[vEquipmentChange.SlotName] then
				local vChangeIndex2, vEquipmentChange2 = self:FindEquipmentChangeForSlot(pEquipmentChangeList, vSwapSlot)
				
				if vChangeIndex2 then
					self:FixSlotSwapChange(pEquipmentChangeList, vChangeIndex, vEquipmentChange, vEquipmentChange.SlotName, vChangeIndex2, vEquipmentChange2, vSwapSlot)
				end
				
				vDidSlot[vEquipmentChange.SlotName] = true
				
				vNumChanges = #pEquipmentChangeList
			end
		end
		
		--
		
		vChangeIndex = vChangeIndex + 1
	end
end

function Outfitter:ExecuteEquipmentChangeList(pEquipmentChangeList, pEmptyBagSlots, pExpectedInventoryCache)
	local vSaved_EnableSFX
	
	if not self.Settings.EnableEquipSounds then
		vSaved_EnableSFX = GetCVar("Sound_EnableSFX")
		SetCVar("Sound_EnableSFX", "0")
	end
	
	for vChangeIndex, vEquipmentChange in ipairs(pEquipmentChangeList) do
		local vSwapItems, vEmptyThenEquip
		
		-- Determine if an in-place swap is possible (or even necessary)
		
		if vEquipmentChange.ItemLocation then
			vSwapItems = true
			
			-- If the items are for different bag types, check to see if we need to
			-- separate the swap operation into Empty then Equip
			
			local vEquippedItemBagType = self:GetSlotIDItemBagType(vEquipmentChange.SlotID)
			local vNewItemBagType = self:GetItemLocationBagType(vEquipmentChange.ItemLocation)
			
			-- If the item being equipped is in a specialty bag already and the
			-- current item can't go in that bag then we have to EmptyThenEquip
			
			local vNewItemInBagType = vEquipmentChange.ItemLocation.BagIndex and self:GetBagType(vEquipmentChange.ItemLocation.BagIndex)
			
			if vEquippedItemBagType
			and vNewItemInBagType
			and vNewItemInBagType ~= 0
			and bit.band(vNewItemInBagType, vEquippedItemBagType) == 0 then
				vEmptyThenEquip = true
			
			-- Otherwise, if the item being unequipped has a specialty slot available, then we have to EmptyThenEquip
			
			elseif vEquippedItemBagType and self:FindEmptySpecialtyBagSlot(vEquippedItemBagType, pEmptyBagSlots) then
				vEmptyThenEquip = true
			end
		end
		
		-- Swap the item in-place with the new item
		
		if vSwapItems then
			if vEmptyThenEquip then
				self:UnequipSlotID(vEquipmentChange.SlotID, pEmptyBagSlots, pExpectedInventoryCache)
			end
			
			ClearCursor() -- Make sure nothing is already being held
			
			self:PickupItemLocation(vEquipmentChange.ItemLocation)
			EquipCursorItem(vEquipmentChange.SlotID)
			
			if pExpectedInventoryCache then
				pExpectedInventoryCache:SwapLocationWithInventorySlot(vEquipmentChange.ItemLocation, vEquipmentChange.SlotName)
			end
		
		-- Remove the item
		
		else
			self:UnequipSlotID(vEquipmentChange.SlotID, pEmptyBagSlots, pExpectedInventoryCache)
		end
	end
	
	ClearCursor() -- Make sure we leave nothing on the cursor
	
	if vSaved_EnableSFX then
		SetCVar("Sound_EnableSFX", vSaved_EnableSFX)
	end
end

function Outfitter:ExecuteEquipmentChangeList2(pEquipmentChangeList, pEmptySlots, pBagsFullErrorFormat, pExpectedInventoryCache)
	local vSaved_EnableSFX
	
	if not self.Settings.EnableEquipSounds then
		vSaved_EnableSFX = GetCVar("Sound_EnableSFX")
		SetCVar("Sound_EnableSFX", "0")
	end
	
	for vChangeIndex, vEquipmentChange in ipairs(pEquipmentChangeList) do
		if vEquipmentChange.ToLocation then
			ClearCursor() -- Make sure nothing is already being held
			
			self:PickupItemLocation(vEquipmentChange.FromLocation)
			EquipCursorItem(vEquipmentChange.SlotID)
			
			if pExpectedInventoryCache then
				pExpectedInventoryCache:SwapLocationWithInventorySlot(vEquipmentChange.ToLocation, vEquipmentChange.SlotName)
			end
		else
			-- Remove the item
			
			self:MoveLocationToEmptyBagSlot(vEquipmentChange.FromLocation, pEmptySlots, pBagsFullErrorFormat, pExpectedInventoryCache)
		end
	end
	
	ClearCursor() -- Make sure we leave nothing on the cursor
	
	if vSaved_EnableSFX then
		SetCVar("Sound_EnableSFX", vSaved_EnableSFX)
	end
end

function Outfitter:FindEmptySpecialtyBagSlot(pItemBagType, pEmptyBagSlots)
	if not pEmptyBagSlots
	or not pItemBagType then
		return
	end
	
	for vSlotInfoIndex, vSlotInfo in ipairs(pEmptyBagSlots) do
		if bit.band(vSlotInfo.BagType, pItemBagType) ~= 0 then
			return vSlotInfo, vSlotInfoIndex
		end
	end
end

function Outfitter:UnequipSlotID(pSlotID, pEmptyBagSlots, pExpectedInventoryCache)
	self:MoveLocationToEmptyBagSlot(pSlotID, pEmptyBagSlots, self.cBagsFullError, pExpectedInventoryCache)
end
	
function Outfitter:MoveLocationToEmptyBagSlot(pLocation, pEmptyBagSlots, pErrorMessage, pExpectedInventoryCache)
	local vItemBagType
	
	if type(pLocation) == "table" then
		vItemBagType = self:GetItemLocationBagType(pLocation)
	else
		vItemBagType = self:GetSlotIDItemBagType(pLocation)
	end
	
	if not vItemBagType then
		return -- Location is already empty
	end
	
	-- Find a bag slot for it
	
	local vEmptySlotInfo
	
	if pEmptyBagSlots then
		-- First try for a specialized bag
		
		if vItemBagType
		and vItemBagType ~= self.cGeneralBagType then
			local vSlotInfoIndex
			
			vEmptySlotInfo, vSlotInfoIndex = self:FindEmptySpecialtyBagSlot(vItemBagType, pEmptyBagSlots)
			
			if vEmptySlotInfo then
				table.remove(pEmptyBagSlots, vSlotInfoIndex)
			end
		end

		-- Try for a general purpose bag
		
		if not vEmptySlotInfo then
			for vSlotInfoIndex, vSlotInfo in ipairs(pEmptyBagSlots) do
				if vSlotInfo.BagType == self.cGeneralBagType then
					vEmptySlotInfo = vSlotInfo
					table.remove(pEmptyBagSlots, vSlotInfoIndex)
					break
				end
			end
		end
	end
	
	--
	
	if not vEmptySlotInfo then
		local vItemLink
		if type(pLocation) == "table" then
			vItemLink = self:GetItemLocationLink(pLocation)
		else
			vItemLink = self:GetInventorySlotIDLink(pLocation)
		end
		self:ErrorMessage(format(pErrorMessage, tostring(vItemLink)))
	else
		ClearCursor() -- Make sure they aren't holding anything already or things get wonky
		
		if type(pLocation) == "table" then
			self:PickupItemLocation(pLocation)
		else
			PickupInventoryItem(pLocation)
		end
		
		self:PickupItemLocation(vEmptySlotInfo)
		
		if pExpectedInventoryCache then
			if type(pLocation) == "table" then
				pExpectedInventoryCache:SwapLocations(pLocation, vEmptySlotInfo)
			else
				pExpectedInventoryCache:SwapLocationWithInventorySlot(pLocation, self.cSlotIDToInventorySlot[pLocation])
			end
		end
	end
end

function Outfitter:BeginEquipmentUpdate()
	self.EquipmentUpdateCount = self.EquipmentUpdateCount + 1
end

function Outfitter:EndEquipmentUpdate(pCallerName, pUpdateNow)
	self.EquipmentUpdateCount = self.EquipmentUpdateCount - 1
	
	if self.EquipmentUpdateCount == 0 then
		if pUpdateNow then
			if self.Debug.EquipmentChanges then
				self:DebugMessage("EndEquipmentUpdate: updating now")
			end
			self:UpdateEquippedItems()
		else
			self:ScheduleEquipmentUpdate()
		end
		
		self:Update(false)
	end
end

function Outfitter:UpdateEquippedItems()
	if not self.EquippedNeedsUpdate then
		if self.Debug.EquipmentChanges then
			self:DebugMessage("UpdateEquippedItems: Nothing to update")
		end
		return
	end
	
	if self.Debug.EquipmentChanges then
		self:DebugMessage("UpdateEquippedItems: updating equipment")
	end
	
	-- Delay all changes until they're alive or not casting a spell
	
	if self.IsDead
	or not self.InterruptCasting and (self.IsCasting or self.IsChanneling) then
		if self.Debug.EquipmentChanges then
			self:DebugMessage("UpdateEquippedItems: deferring while casting")
		end
		
		return
	end
	
	self.InterruptCasting = nil
	
	local vCurrentTime = GetTime()
	
	if vCurrentTime - self.LastEquipmentUpdateTime < self.cMinEquipmentUpdateInterval then
		if self.Debug.EquipmentChanges then
			self:DebugMessage("UpdateEquippedItems: deferring for min interval")
		end
		
		self:ScheduleEquipmentUpdate()
		return
	end
	
	self.LastEquipmentUpdateTime = vCurrentTime
	
	self.EquippedNeedsUpdate = false
	
	-- Compile the outfit
	
	local vInventoryCache = self:GetInventoryCache()
	local vCompiledOutfit = self:GetCompiledOutfit()
	
	-- When in combat delay the outfit change until
	-- combat ends
	
	if self.InCombat or self.MaybeInCombat then
		self.EquippedNeedsUpdate = true
		self.MaybeInCombat = false
		self:ScheduleEquipmentUpdate()
		return
	end
	
	-- Equip it
	
	local vEquipmentChangeList = self:BuildEquipmentChangeList(vCompiledOutfit, vInventoryCache)
	
	if vEquipmentChangeList then
		-- local vExpectedInventoryCache = self:New(self._InventoryCache)
	
		if self.Debug.EquipmentChanges then
			self:DebugMessage("UpdateEquippedItems: Executing change list")
			self:DebugTable(vEquipmentChangeList, "ChangeList")
		end
		
		self:ExecuteEquipmentChangeList(vEquipmentChangeList, self:GetEmptyBagSlotList(), vExpectedInventoryCache)
	else
		if self.Debug.EquipmentChanges then
			self:DebugMessage("UpdateEquippedItems: No change list generated")
		end
	end
	
	-- Update the outfit we're expecting to see on the player
	
	local vItems = vCompiledOutfit:GetItems()
	
	for vInventorySlot, vItem in pairs(vItems) do
		self.ExpectedOutfit:SetItem(vInventorySlot, vItem)
	end
	
	if self.Debug.EquipmentChanges then
		self:DebugOutfitTable(vCompiledOutfit, "CompiledOutfit")
	end
	
	self.MaybeInCombat = false
	
	self:ScheduleEquipmentUpdate()
end

function Outfitter:DebugOutfitTable(pOutfit, pPrefix)
	local vPrefix = pPrefix or pOutfit.Name or "unnamed"
	local vItems = pOutfit:GetItems()
	
	if not vItems then
		self:DebugMessage("%s: No items", vPrefix)
		return
	end
	
	for vInventorySlot, vItem in pairs(vItems) do
		if vItem.Code == 0 then
			self:DebugMessage("%s.%s: Empty slot", vPrefix, vInventorySlot)
		else
			self:DebugMessage("%s.%s: %s", vPrefix, vInventorySlot, tostring(vItem.Link))
		end
	end
end

function Outfitter:ScheduleEquipmentUpdate()
	if not self.EquippedNeedsUpdate then
		return
	end
	
	local vElapsed = GetTime() - self.LastEquipmentUpdateTime
	local vDelay = self.cMinEquipmentUpdateInterval - vElapsed
	
	 if vDelay < 0.05 then
		vDelay = 0.05
	end
	
	if self.Debug.EquipmentChanges then
		self:DebugMessage("ScheduleEquipmentUpdate: updating in %d seconds", vDelay)
	end
	
	self.SchedulerLib:ScheduleUniqueTask(vDelay, self.UpdateEquippedItems, self)
end

----------------------------------------
-- Outfitter.OutfitStack
----------------------------------------

function Outfitter.OutfitStack:Initialize()
	self:RestoreSavedStack()
end

function Outfitter.OutfitStack:RestoreSavedStack()
	if not gOutfitter_Settings.LastOutfitStack then
		gOutfitter_Settings.LastOutfitStack = {}
	end
	
	for vIndex, vOutfit in ipairs(gOutfitter_Settings.LastOutfitStack) do
		if vOutfit.Name then
			vOutfit = Outfitter:FindOutfitByName(vOutfit.Name)
		else
			setmetatable(vOutfit, Outfitter._OutfitMetaTable)
		end
		
		if vOutfit and vOutfit:GetItems() then
			table.insert(self.Outfits, vOutfit)
		end
	end
	
	Outfitter.ExpectedOutfit = Outfitter:GetCompiledOutfit()
	
	Outfitter:UpdateTemporaryOutfit(Outfitter:GetNewItemsOutfit(Outfitter.ExpectedOutfit))
	
	if gOutfitter_Settings.Options.ShowStackContents then
		self:DebugOutfitStack("Restore saved stack")
	end
end

function Outfitter.OutfitStack:AddOutfit(pOutfit, pLayerID)
	local vFound, vIndex = self:FindOutfit(pOutfit)
	
	-- If it's already the topmost outfit just ignore the request
	
	if vFound and vIndex == #self.Outfits then
		return
	end
	
	-- If it's already on then remove it from the stack
	-- so it can be added to the end
	
	if vFound then
		table.remove(self.Outfits, vIndex)
		table.remove(gOutfitter_Settings.LastOutfitStack, vIndex)
		
		for vLayerID, vLayerIndex in pairs(gOutfitter_Settings.LayerIndex) do
			if vIndex < vLayerIndex then
				gOutfitter_Settings.LayerIndex[vLayerID] = vLayerIndex - 1
			end
		end
		
		Outfitter:DispatchOutfitEvent("UNWEAR_OUTFIT", pOutfit:GetName(), pOutfit)
	end
	
	-- Figure out the position to insert at
	
	local vStackLength = #self.Outfits
	local vInsertIndex = vStackLength + 1
	
	local vLayerIndex = gOutfitter_Settings.LayerIndex[pLayerID]
	
	if vLayerIndex then
		vInsertIndex = vLayerIndex
	end
	
	if pLayerID then
		gOutfitter_Settings.LayerIndex[pLayerID] = vInsertIndex
	end
	
	-- Adjust the layer indices
	
	for vLayerID, vLayerIndex in pairs(gOutfitter_Settings.LayerIndex) do
		if vInsertIndex < vLayerIndex then
			gOutfitter_Settings.LayerIndex[vLayerID] = vLayerIndex + 1
		end
	end
	
	-- Add the outfit
	
	table.insert(self.Outfits, vInsertIndex, pOutfit)
	
	if pOutfit:GetName() then
		table.insert(gOutfitter_Settings.LastOutfitStack, vInsertIndex, {Name = pOutfit:GetName()})
	else
		table.insert(gOutfitter_Settings.LastOutfitStack, vInsertIndex, pOutfit)
	end
	
	Outfitter.DisplayIsDirty = true
	
	if gOutfitter_Settings.Options.ShowStackContents then
		self:DebugOutfitStack("Add outfit")
	end
	
	if vFound then
		self:CollapseTemporaryOutfits()
	end
	
	Outfitter:DispatchOutfitEvent("WEAR_OUTFIT", pOutfit:GetName(), pOutfit)
end

function Outfitter.OutfitStack:RemoveOutfit(pOutfit)
	local vFound, vIndex = self:FindOutfit(pOutfit)
	
	if not vFound then
		return false
	end
	
	-- Remove the outfit
	
	table.remove(self.Outfits, vIndex)
	table.remove(gOutfitter_Settings.LastOutfitStack, vIndex)
	
	self:CollapseTemporaryOutfits()
			
	for vLayerID, vLayerIndex in pairs(gOutfitter_Settings.LayerIndex) do
		if vIndex < vLayerIndex then
			gOutfitter_Settings.LayerIndex[vLayerID] = vLayerIndex - 1
		end
	end
	
	Outfitter.DisplayIsDirty = true
	
	if gOutfitter_Settings.Options.ShowStackContents then
		self:DebugOutfitStack("Remove outfit")
	end
	
	return true
end

function Outfitter.OutfitStack:FindOutfit(pOutfit)
	for vIndex, vOutfit in ipairs(self.Outfits) do
		if vOutfit == pOutfit then
			return true, vIndex
		end
	end
	
	return false, nil
end

function Outfitter.OutfitStack:FindOutfitByCategory(pCategoryID)
	for vIndex, vOutfit in ipairs(self.Outfits) do
		if vOutfit.CategoryID == pCategoryID then
			return true, vIndex
		end
	end
	
	return false, nil
end

function Outfitter.OutfitStack:Clear()
	for vIndex, vOutfit in ipairs(self.Outfits) do
		Outfitter:DispatchOutfitEvent("UNWEAR_OUTFIT", vOutfit:GetName(), vOutfit)
	end
	
	Outfitter:EraseTable(self.Outfits)
	
	gOutfitter_Settings.LastOutfitStack = Outfitter:RecycleTable(gOutfitter_Settings.LastOutfitStack)
	gOutfitter_Settings.LayerIndex = Outfitter:RecycleTable(gOutfitter_Settings.LayerIndex)
	Outfitter.DisplayIsDirty = true
	
	if gOutfitter_Settings.Options.ShowStackContents then
		Outfitter:DebugMessage("Outfitter stack cleared")
	end
end

function Outfitter.OutfitStack:ClearCategory(pCategoryID)
	local vIndex = 1
	local vStackLength = #self.Outfits
	local vChanged = false
	
	while vIndex <= vStackLength do
		local vOutfit = self.Outfits[vIndex]
		
		if vOutfit
		and vOutfit.CategoryID == pCategoryID then
			-- Remove the outfit from the stack
			
			table.remove(self.Outfits, vIndex)
			table.remove(gOutfitter_Settings.LastOutfitStack, vIndex)
			
			vStackLength = vStackLength - 1
			vChanged = true
			
			-- Adjust the layer indices
			
			for vLayerID, vLayerIndex in pairs(gOutfitter_Settings.LayerIndex) do
				if vIndex < vLayerIndex then
					gOutfitter_Settings.LayerIndex[vLayerID] = vLayerIndex - 1
				end
			end
			
			Outfitter:DispatchOutfitEvent("UNWEAR_OUTFIT", vOutfit:GetName(), vOutfit)
		else
			vIndex = vIndex + 1
		end
	end
	
	self:CollapseTemporaryOutfits()
	
	if vChanged then
		if gOutfitter_Settings.Options.ShowStackContents then
			self:DebugOutfitStack("Clear category "..pCategoryID)
		end
		
		Outfitter.DisplayIsDirty = true
	end
end

function Outfitter.OutfitStack:GetTemporaryOutfit()
	local vStackSize = #self.Outfits
	
	if vStackSize == 0 then
		return nil
	end
	
	local vOutfit = self.Outfits[vStackSize]
	
	if vOutfit:GetName() then
		return nil
	end
	
	return vOutfit
end

function Outfitter.OutfitStack:CollapseTemporaryOutfits()
	local vIndex = 1
	local vStackLength = #self.Outfits
	local vTemporaryOutfit1 = nil
	
	while vIndex <= vStackLength do
		local vOutfit = self.Outfits[vIndex]
		
		if vOutfit
		and vOutfit:GetName() == nil then
			if vTemporaryOutfit1 then
				-- Copy the items up
				
				local vTemporaryItems = vTemporaryOutfit1:GetItems()
				
				for vInventorySlot, vItem in pairs(vTemporaryItems) do
					if not vOutfit:SlotIsEnabled(vInventorySlot) then
						vOutfit:SetItem(vInventorySlot, vItem)
					end
				end
				
				-- Remove the lower temp outfit
				
				table.remove(self.Outfits, vIndex - 1)
				vStackLength = vStackLength - 1
			else
				vIndex = vIndex + 1
			end
			
			vTemporaryOutfit1 = vOutfit
		else
			vTemporaryOutfit1 = nil
			vIndex = vIndex + 1
		end
	end
end

function Outfitter.OutfitStack:IsTopmostOutfit(pOutfit)
	local vStackLength = #self.Outfits
	
	if vStackLength == 0 then
		return false
	end
	
	return self.Outfits[vStackLength] == pOutfit
end

function Outfitter.OutfitStack:UpdateOutfitDisplay()
	local vShowHelm, vShowCloak, vShowTitleID
	
	for vIndex, vOutfit in ipairs(self.Outfits) do
		if vOutfit.ShowHelm ~= nil then
			vShowHelm = vOutfit.ShowHelm
		end
		
		if vOutfit.ShowCloak ~= nil then
			vShowCloak = vOutfit.ShowCloak
		end
		
		if vOutfit.ShowTitleID ~= nil then
			vShowTitleID = vOutfit.ShowTitleID
		end
	end -- for
	
	if vShowHelm == true then
		ShowHelm("1")
	elseif vShowHelm == false then
		ShowHelm("0")
	end
	
	if vShowCloak == true then
		ShowCloak("1")
	elseif vShowCloak == false then
		ShowCloak("0")
	end
	
	if vShowTitleID ~= nil
	and Outfitter.HasHWEvent then
		SetCurrentTitle(vShowTitleID)
	end
end

function Outfitter:TagOutfitLayer(pOutfit, pLayerID)
	local vFound, vIndex = self.OutfitStack:FindOutfit(pOutfit)
	
	if not vFound then
		return
	end
	
	gOutfitter_Settings.LayerIndex[pLayerID] = vIndex
end

function Outfitter.OutfitStack:DebugOutfitStack(pOperation)
	Outfitter:DebugMessage("Outfitter Stack Contents: "..pOperation)
	
	for vIndex, vOutfit in ipairs(self.Outfits) do
		if vOutfit:GetName() then
			Outfitter:DebugMessage("Slot "..vIndex..": "..vOutfit:GetName())
		else
			Outfitter:DebugMessage("Slot "..vIndex..": Temporaray outfit")
		end
	end
	
	Outfitter:DebugTable(gOutfitter_Settings.LayerIndex, "LayerIndex")
end

function Outfitter.OutfitStack:GetCurrentOutfitInfo()
	local vStackLength = #self.Outfits
	
	if vStackLength == 0 then
		return "", nil
	end
	
	local vOutfit = self.Outfits[vStackLength]
	
	if vOutfit and vOutfit:GetName() then
		return vOutfit:GetName(), vOutfit
	else
		return Outfitter.cCustom, vOutfit
	end
end

function Outfitter:GemTest()
	local vStartEnchantID, vEndEnchantID = 3518, 3533
	
	for vEnchantID = vStartEnchantID, vEndEnchantID, 3 do
		DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffa335ee|Hitem:43590:0:%d:%d:%d:0:0:0:0|h[Polar Vest]|h|r",
			vEnchantID,
			vEnchantID + 1,
			vEnchantID + 2))
	end
end
