EQUIPMENTMANAGER_INVENTORYSLOTS = {};
EQUIPMENTMANAGER_BAGSLOTS = {};

local _isAtBank = false;
local SLOT_LOCKED = -1;
local SLOT_EMPTY = -2;

local EQUIP_ITEM = 1;
local UNEQUIP_ITEM = 2;
local SWAP_ITEM = 3;

for i = KEYRING_CONTAINER, NUM_BAG_SLOTS do
	EQUIPMENTMANAGER_BAGSLOTS[i] = {};
end

EquipmentManager = CreateFrame("FRAME");

local workTable = {};
function EquipmentManager_UpdateFreeBagSpace ()
	local bagSlots = EQUIPMENTMANAGER_BAGSLOTS;
	
	for i = BANK_CONTAINER, NUM_BAG_SLOTS + GetNumBankSlots() do
		wipe(workTable);
		local _, bagType = GetContainerNumFreeSlots(i);
		if ( GetContainerFreeSlots(i, workTable) ) then
			if (not bagSlots[i]) then
				bagSlots[i] = {};
			end
			
			-- Reset all EMPTY bag slots
			for index, flag in next, bagSlots[i] do
				if (flag == SLOT_EMPTY) then
					bagSlots[i][index] = nil;
				end
			end
			
			for index, slot in next, workTable do
				if ( bagSlots[i] and not bagSlots[i][slot] and bagType == 0 ) then -- Don't overwrite locked slots, don't reset empty slots to empty, only use normal bags
					bagSlots[i][slot] = SLOT_EMPTY;
				end
			end
		else
			bagSlots[i] = nil;
		end
	end
end

local function _EquipmentManager_BagsFullError()
	UIErrorsFrame:AddMessage(EQUIPMENT_MANAGER_BAGS_FULL, 1.0, 0.1, 0.1, 1.0);
end

function EquipmentManager_OnEvent (self, event, ...)
	if ( event == "WEAR_EQUIPMENT_SET" ) then
		local setName = ...;
		EquipmentManager_EquipSet(setName);
	elseif ( event == "ITEM_UNLOCKED" ) then
		local arg1, arg2 = ...; -- inventory slot or bag and slot
		
		if ( not arg2 ) then
			EQUIPMENTMANAGER_INVENTORYSLOTS[arg1] = nil;
		elseif (EQUIPMENTMANAGER_BAGSLOTS[arg1]) then
			EQUIPMENTMANAGER_BAGSLOTS[arg1][arg2] = nil;
		end

	elseif ( event == "BANKFRAME_OPENED" ) then
		_isAtBank = true;
	elseif ( event == "BANKFRAME_CLOSED" ) then
		_isAtBank = false;
	end
end

EquipmentManager:SetScript("OnEvent", EquipmentManager_OnEvent);
EquipmentManager:RegisterEvent("WEAR_EQUIPMENT_SET");
EquipmentManager:RegisterEvent("ITEM_UNLOCKED");
EquipmentManager:RegisterEvent("BANKFRAME_OPENED");
EquipmentManager:RegisterEvent("BANKFRAME_CLOSED");

function EquipmentManager_EquipItemByLocation (location, invSlot)
	local player, bank, bags, voidStorage, slot, bag = EquipmentManager_UnpackLocation(location);
		
	ClearCursor();	
	
	if ( not bags and slot == invSlot ) then --We're trying to reequip an equipped item in the same spot, ignore it.		
		return nil;
	end
	
	local currentItemID = GetInventoryItemID("player", invSlot);

	local action = {};
	action.type = (currentItemID and SWAP_ITEM) or EQUIP_ITEM;
	action.invSlot = invSlot;
	action.player = player;
	action.bank = bank;
	action.bags = bags;
	action.slot = slot;
	action.bag = bag;
	
	return action;
end

function EquipmentManager_EquipContainerItem (action)
	ClearCursor();
	
	PickupContainerItem(action.bag, action.slot);
	
	if ( not CursorHasItem() ) then
		return false;
	end
	
	if ( not CursorCanGoInSlot(action.invSlot) ) then
		return false;
	elseif ( IsInventoryItemLocked(action.invSlot) ) then
		return false;
	end
	
	PickupInventoryItem(action.invSlot);
	
	EQUIPMENTMANAGER_BAGSLOTS[action.bag][action.slot] = action.invSlot;
	EQUIPMENTMANAGER_INVENTORYSLOTS[action.invSlot] = SLOT_LOCKED;
	
	return true;
end

function EquipmentManager_EquipInventoryItem (action)
	ClearCursor();
	PickupInventoryItem(action.slot);
	if ( not CursorCanGoInSlot(action.invSlot) ) then
		return false;
	elseif ( IsInventoryItemLocked(action.invSlot) ) then
		return false;
	end
	PickupInventoryItem(action.invSlot);
	EQUIPMENTMANAGER_INVENTORYSLOTS[action.slot] = SLOT_LOCKED;
	EQUIPMENTMANAGER_INVENTORYSLOTS[action.invSlot] = SLOT_LOCKED;
	
	return true;
end

function EquipmentManager_UnpackLocation (location) -- Use me, I'm here to be used.
	if ( location < 0 ) then -- Thanks Seerah!
		return false, false, false, 0;
	end
	
	local player = (bit.band(location, ITEM_INVENTORY_LOCATION_PLAYER) ~= 0);
	local bank = (bit.band(location, ITEM_INVENTORY_LOCATION_BANK) ~= 0);
	local bags = (bit.band(location, ITEM_INVENTORY_LOCATION_BAGS) ~= 0);
	local voidStorage = (bit.band(location, ITEM_INVENTORY_LOCATION_VOIDSTORAGE) ~= 0);

	if ( player ) then
		location = location - ITEM_INVENTORY_LOCATION_PLAYER;
	elseif ( bank ) then
		location = location - ITEM_INVENTORY_LOCATION_BANK;
	elseif ( voidStorage ) then
		location = location - ITEM_INVENTORY_LOCATION_VOIDSTORAGE;
	end
	
	if ( bags ) then
		location = location - ITEM_INVENTORY_LOCATION_BAGS;
		local bag = bit.rshift(location, ITEM_INVENTORY_BAG_BIT_OFFSET);
		local slot = location - bit.lshift(bag, ITEM_INVENTORY_BAG_BIT_OFFSET);	
		
		if ( bank ) then
			bag = bag + ITEM_INVENTORY_BANK_BAG_OFFSET;
		end
		return player, bank, bags, voidStorage, slot, bag
	else
		return player, bank, bags, voidStorage, location
	end
end

function EquipmentManager_UnequipItemInSlot (invSlot)		
	local itemID = GetInventoryItemID("player", invSlot);
	if ( not itemID ) then
		return nil; -- Slot was empty already;
	end
	
	local action = {};
	action.type = UNEQUIP_ITEM;
	action.invSlot = invSlot;
	
	return action;
end

function EquipmentManager_PutItemInInventory (action)
	if ( not CursorHasItem() ) then
		return;
	end
		
	EquipmentManager_UpdateFreeBagSpace();
	
	local bagSlots = EQUIPMENTMANAGER_BAGSLOTS;
	
	local firstSlot;
	for slot, flag in next, bagSlots[0] do
		if ( flag == SLOT_EMPTY ) then
			firstSlot = min(firstSlot or slot, slot);
		end
	end
	
	if ( firstSlot ) then
		if ( action ) then
			action.bag = 0;
			action.slot = firstSlot;
		end
		
		bagSlots[0][firstSlot] = SLOT_LOCKED;
		PutItemInBackpack();
		return true;
	end
	
	for bag = 1, NUM_BAG_SLOTS do
		if ( bagSlots[bag] ) then
			for slot, flag in next, bagSlots[bag] do
				if ( flag == SLOT_EMPTY ) then
					firstSlot = min(firstSlot or slot, slot);
				end
			end
			if ( firstSlot ) then
				bagSlots[bag][firstSlot] = SLOT_LOCKED;
				PutItemInBag(bag + CONTAINER_BAG_OFFSET);
				
				if ( action ) then
					action.bag = bag;
					action.slot = firstSlot;
				end
				return true;
			end
		end
	end
	
	if ( _isAtBank ) then
		for slot, flag in next, bagSlots[BANK_CONTAINER] do
			if ( flag == SLOT_EMPTY ) then
				firstSlot = min(firstSlot or slot, slot);
			end
		end
		if ( firstSlot ) then
			bagSlots[BANK_CONTAINER][firstSlot] = SLOT_LOCKED;
			PickupInventoryItem(firstSlot + BANK_CONTAINER_INVENTORY_OFFSET);
			
			if ( action ) then
				action.bag = BANK_CONTAINER;
				action.slot = firstSlot;
			end
			return true;
		else
			for bag = NUM_BAG_SLOTS + 1, NUM_BAG_SLOTS + GetNumBankSlots() do
				if ( bagSlots[bag] ) then
					for slot, flag in next, bagSlots[bag] do
						if ( flag == SLOT_EMPTY ) then
							firstSlot = min(firstSlot or slot, slot);
						end
					end
					if ( firstSlot ) then
						bagSlots[bag][firstSlot] = SLOT_LOCKED;
						PickupContainerItem(bag, firstSlot);
						
						if ( action ) then
							action.bag = bag;
							action.slot = firstSlot;
						end
						return true;
					end
				end
			end
		end
	end
	
	ClearCursor();
	_EquipmentManager_BagsFullError();
end

function EquipmentManager_GetItemInfoByLocation (location)
	local player, bank, bags, voidStorage, slot, bag = EquipmentManager_UnpackLocation(location);
	if ( not player and not bank and not bags and not voidStorage ) then -- Invalid location
		return;
	end

	local id, name, textureName, count, durability, maxDurability, invType, locked, start, duration, enable, setTooltip, gem1, gem2, gem3, _;
	if ( voidStorage ) then
		id, textureName = GetVoidItemInfo(slot);
		setTooltip = function () GameTooltip:SetVoidItem(slot) end;
	elseif ( not bags ) then -- and (player or bank) 
		id = GetInventoryItemID("player", slot);
		name, _, _, _, _, _, _, _, invType, textureName = GetItemInfo(id);
		if ( textureName ) then
			count = GetInventoryItemCount("player", slot);
			durability, maxDurability = GetInventoryItemDurability(slot);
			start, duration, enable = GetInventoryItemCooldown("player", slot);
		end
		
		setTooltip = function () GameTooltip:SetInventoryItem("player", slot) end;
		gem1, gem2, gem3 = GetInventoryItemGems(slot);
	else -- bags
		id = GetContainerItemID(bag, slot);
		name, _, _, _, _, _, _, _, invType = GetItemInfo(id);
		textureName, count, locked = GetContainerItemInfo(bag, slot);
		start, duration, enable = GetContainerItemCooldown(bag, slot);
		
		durability, maxDurability = GetContainerItemDurability(bag, slot);
		
		setTooltip = function () GameTooltip:SetBagItem(bag, slot); end;
		gem1, gem2, gem3 = GetContainerItemGems(bag, slot);
	end
	
	return id, name, textureName, count, durability, maxDurability, invType, locked, start, duration, enable, setTooltip, gem1, gem2, gem3;
end

function EquipmentManager_EquipSet (name)
	if ( EquipmentSetContainsLockedItems(name) or UnitCastingInfo("player") ) then
		UIErrorsFrame:AddMessage(ERR_CLIENT_LOCKED_OUT, 1.0, 0.1, 0.1, 1.0);
		return;
	end
	
	UseEquipmentSet(name);
end

function EquipmentManager_RunAction (action)
	if ( UnitAffectingCombat("player") and not INVSLOTS_EQUIPABLE_IN_COMBAT[action.invSlot] ) then
		return true;
	end
	
	EquipmentManager_UpdateFreeBagSpace();
	
	action.run = true;
	if ( action.type == EQUIP_ITEM or action.type == SWAP_ITEM ) then
		if ( not action.bags ) then
			return EquipmentManager_EquipInventoryItem(action);
		else
			local hasItem = action.invSlot and GetInventoryItemID("player", action.invSlot);
			local pending = EquipmentManager_EquipContainerItem(action);
			
			if ( pending and not hasItem ) then
				EQUIPMENTMANAGER_BAGSLOTS[action.bag][action.slot] = SLOT_EMPTY;
			end
			
			return pending;
		end
	elseif ( action.type == UNEQUIP_ITEM ) then
		ClearCursor();
		
		if ( IsInventoryItemLocked(action.invSlot) ) then
			return;
		else
			PickupInventoryItem(action.invSlot);
			return EquipmentManager_PutItemInInventory(action);
		end
	end
end