OUTFITDISPLAYFRAME_VERSION = "0.5.8";
OUTFITDISPLAYFRAME_NAME = "Outfit Display Frame";
OUTFITDISPLAYFRAME_TITLE = OUTFITDISPLAYFRAME_NAME.." v"..OUTFITDISPLAYFRAME_VERSION;
OUTFITDISPLAYFRAME_BANKDELAY = 0.5;

local PlayerSlotNames = {
   [1] = { name = "HeadSlot", tooltip = HEADSLOT };
   [2] = { name = "NeckSlot", tooltip = NECKSLOT },
   [3] = { name = "ShoulderSlot", tooltip = SHOULDERSLOT },
   [4] = { name = "BackSlot", tooltip = BACKSLOT },
   [5] = { name = "ChestSlot", tooltip = CHESTSLOT },
   [6] = { name = "ShirtSlot", tooltip = SHIRTSLOT },
   [7] = { name = "TabardSlot", tooltip = TABARDSLOT },
   [8] = { name = "WristSlot", tooltip = WRISTSLOT },
   [9] = { name = "HandsSlot", tooltip = HANDSSLOT },
   [10] = { name = "WaistSlot", tooltip = WAISTSLOT },
   [11] = { name = "LegsSlot", tooltip = LEGSSLOT },
   [12] = { name = "FeetSlot", tooltip = FEETSLOT },
   [13] = { name = "Finger0Slot", tooltip = FINGER0SLOT },
   [14] = { name = "Finger1Slot", tooltip = FINGER1SLOT },
   [15] = { name = "Trinket0Slot", tooltip = TRINKET0SLOT },
   [16] = { name = "Trinket1Slot", tooltip = TRINKET1SLOT },
   [17] = { name = "MainHandSlot", tooltip = MAINHANDSLOT },
   [18] = { name = "SecondaryHandSlot", tooltip = SECONDARYHANDSLOT },
   [19] = { name = "RangedSlot", tooltip = RANGEDSLOT },
}

local SavedPickupContainerItem = nil;
local SavedPickupInventoryItem = nil;

local LastOffSource = nil;
local PerformSlowerSwap = false;
local BankIsOpen = false;

local function tonil(val)
   if ( not val ) then
      return "nil";
   else
      return val;
   end
end

local function Print(msg, r, g, b)
   if ( DEFAULT_CHAT_FRAME ) then
      if ( not r ) then
    DEFAULT_CHAT_FRAME:AddMessage(msg);
      else
    DEFAULT_CHAT_FRAME:AddMessage(msg, r, g, b);
      end
   end
end

-- Based on code in QuickMountEquip
local function SafeHookFunction(func, newfunc)
   local oldValue = getglobal(func);
   if ( oldValue ~= getglobal(newfunc) ) then
      setglobal(func, getglobal(newfunc));
      return true;
   end
   return false;
end

-- this really ought to be in a library somewhere
local function SplitLink(link)
   local _,_, color, item, name = string.find(link, "|c(%x+)|Hitem:(%d+:%d+:%d+:%d+)|h%[(.-)%]|h|r");
   return color, item, name;
end

function OutfitDisplayFrame_OnLoad()
   local temp = PickupContainerItem;
   if ( SafeHookFunction("PickupContainerItem", "OutfitDisplayFrame_PickupContainerItem") ) then
      SavedPickupContainerItem = temp;
   end

         temp = PickupInventoryItem;
   if ( SafeHookFunction("PickupInventoryItem", "OutfitDisplayFrame_PickupInventoryItem") ) then
      SavedPickupInventoryItem = temp;
   end

   local parent = this:GetName();
   for i=1,19,1 do
      local slotName = PlayerSlotNames[i].name;
      local button = getglobal(parent..slotName);
      button.tooltip = PlayerSlotNames[i].tooltip;
      OutfitDisplayItemButton_Draw(button);
      PlayerSlotNames[i].id = GetInventorySlotInfo(slotName);
   end

   -- ITEM_LOCK_CHANGED gets invoked a lot -- let's optimize it
   this:RegisterEvent("PLAYER_ENTERING_WORLD");
   this:RegisterEvent("PLAYER_LEAVING_WORLD");

   -- ideas borrowed from "WeaponQuickSwap - by CapnBry"
   this:RegisterEvent("BANKFRAME_OPENED");
   this:RegisterEvent("BANKFRAME_CLOSED");

   Print(OUTFITDISPLAYFRAME_TITLE.." loaded.");
end

local function DressUpItem(model, thing)
   if ( not model or not thing or thing == "" ) then
      return;
   end
   local item = gsub(thing, "(%d+).*", "%1", 1);
   model:TryOn(item);
end

local BankFrameClosedTime = 0;

function OutfitDisplayFrame_OnEvent(...)
   if (arg[1] == "ITEM_LOCK_CHANGED") and not arg[2] then
      OutfitDisplayFrame_ExecuteSwapIteration();
   elseif ( arg[1] == "BANKFRAME_OPENED" or arg[1] == "BANKFRAME_CLOSED" ) then
      BankFrameIsOpen = ( arg[1] == "BANKFRAME_OPENED" );
      local frame = getglobal(this:GetName().."MessageUpdateFrame");
      if ( frame ) then
	 BankFrameClosedTime = OUTFITDISPLAYFRAME_BANKDELAY;
	 frame:Show();
      end
   elseif ( arg[1] == "PLAYER_ENTERING_WORLD" ) then
      this:RegisterEvent("ITEM_LOCK_CHANGED");
   elseif ( arg[1] == "PLAYER_LEAVING_WORLD" ) then
      this:UnregisterEvent("ITEM_LOCK_CHANGED");
   end
end

function OutfitDisplayFrame_MessageUpdate(elapsed)
   BankFrameClosedTime = BankFrameClosedTime - elapsed;
   if ( BankFrameClosedTime <= 0 ) then
      this:Hide();
      OutfitDisplayFrame_UpdateMessage(this:GetParent());
      BankFrameClosedTime = 0;
   end
end

local function GetSlotButton(button, slotName)
   local parent = button:GetParent():GetName();
   return getglobal(parent..slotName);
end

local function IsItemOneHanded(item)
   if ( item ) then
      local _,_,_,_,_,_,_,bodyslot = GetItemInfo("item:"..item);
      if ( bodyslot ==  "INVTYPE_2HWEAPON" or bodyslot == INVTYPE_2HWEAPON ) then
	 return false;
      end
   end
   return true;
end

function OutfitDisplayFrame_CursorCanGoInSlot(button)
   if ( button.forced ) then
      return false;
   end
   local secondary = GetSlotButton(button, "SecondaryHandSlot");
   local mainbutton = GetSlotButton(button, "MainHandSlot");
   if ( button == secondary and mainbutton and mainbutton.item ) then
      return IsItemOneHanded(mainbutton.item);
   end
   return CursorCanGoInSlot(button:GetID())
end

-- much hackery to know what actually is in the cursor when somebody drops it on us
local OD_Track_Bag = nil;
local OD_Track_Slot = nil;

-- whatever we think the user picked up, apply it to our stuff
local function AcceptCursorItem(button)
   local parent = button:GetParent();
   local pname = parent:GetName();
   if ( not OutfitDisplayFrame_CursorCanGoInSlot(button) ) then
      button = nil;
      for i=1,19,1 do
	 local temp = getglobal(pname..PlayerSlotNames[i].name);
	 if ( temp and OutfitDisplayFrame_CursorCanGoInSlot(temp) ) then
	    button = temp;
	 end
      end
   end

   if ( button ) then
      local link, texture;
      if ( OD_Track_Bag ) then
	 link = GetContainerItemLink(OD_Track_Bag, OD_Track_Slot);
	 texture = GetContainerItemInfo(OD_Track_Bag, OD_Track_Slot);
      else
	 link = GetInventoryItemLink("player", OD_Track_Slot);
	 texture = GetInventoryItemTexture("player", OD_Track_Slot);
      end
      button.color, button.item, button.name = SplitLink(link);
      button.texture = texture;
      button.used = true;
      button.empty = nil;
      button.missing = false;
      if ( OD_Track_Bag and (OD_Track_Bag == BANK_CONTAINER or
			     OD_Track_Bag > NUM_BAG_SLOTS) ) then
	 button.banked = true;
      end
      OutfitDisplayItemButton_Change(button);
   end

   -- clear the cursor item
   if ( OD_Track_Bag ) then
      SavedPickupContainerItem(OD_Track_Bag, OD_Track_Slot);
   elseif ( OD_Track_Slot ) then
      SavedPickupInventoryItem(OD_Track_Slot);
   end
   OD_Track_Bag = nil;
   OD_Track_Slot = nil;
end

-- figure out what the user picked up, so that if they drop it on us we can deal with it
function OutfitDisplayFrame_TrackItemPickup(bag, slot)
   OD_Track_Bag = bag;
   OD_Track_Slot = slot;
end

function OutfitDisplayFrame_PickupContainerItem(bag, slot)
   if ( SavedPickupContainerItem ) then
      SavedPickupContainerItem(bag, slot);
   end
   OutfitDisplayFrame_TrackItemPickup(bag, slot);
end

function OutfitDisplayFrame_PickupInventoryItem(slot)
   if ( SavedPickupInventoryItem ) then
      SavedPickupInventoryItem(slot);
   end
   OutfitDisplayFrame_TrackItemPickup(nil, slot);
end

-- handle making the items in the outfit pane do nice things
function OutfitDisplayItemButton_OnEnter()
   if ( GameTooltip.finished ) then
      return;
   end
   GameTooltip.finished = 1;
   GameTooltip:SetOwner(this, "ANCHOR_RIGHT");
   if (this.item) then
      local link = "item:"..this.item;
      if ( GetItemInfo(link) ) then
         GameTooltip:SetHyperlink(link);
      elseif ( this.name ) then
         GameTooltip:SetText(this.name);
      elseif ( this.tooltip ) then
         GameTooltip:SetText(this.tooltip);
      else
         GameTooltip:SetText("<unlinkable item>", 1, 0, 0);
      end
   elseif ( this.tooltip ) then
      GameTooltip:AddLine(this.tooltip);
   else
      local parentlen = string.len(this:GetParent():GetName())+1;
      local slotName = strsub(this:GetName(), parentlen);
      slotName = strsub(slotName, 1, string.len(slotName) - 4);
      this.tooltip = slotName;
      GameTooltip:AddLine(this.tooltip);
   end
   GameTooltip:AddLine(OUTFITDISPLAYFRAME_ALTCLICK,.8,.8,.8,1);
   GameTooltip:Show();
end

function OutfitDisplayItemButton_OnEvent(event)
   if ( event == "CURSOR_UPDATE" ) then
      if ( not this.forced ) then
	 if ((this.CursorCanGoInSlot and this.CursorCanGoInSlot(this)) or
	     OutfitDisplayFrame_CursorCanGoInSlot(this)) then
	    this:LockHighlight();
	 else
	    this:UnlockHighlight();
	 end
      end
   end
end

function OutfitDisplayItemButton_OnClick(clicked, ignoreModifiers)
   if ( clicked == "LeftButton" ) then
      if( not ignoreModifiers or ignoreModifiers == 0 ) then
	 if ( IsShiftKeyDown() ) then
	    if ( this.item and ChatFrameEditBox:IsVisible() ) then
	       local color = this.color;
	       if ( not color ) then
		  color = "ffffffff";
	       end
	       local link = "|c"..color.."|Hitem"..this.item.."|h["..this.name.."]|h|r";
	       ChatFrameEditBox:Insert(link);
	       return;
	    end
	 elseif ( IsAltKeyDown() ) then
	    OutfitDisplayItemButton_Clear(this, false, false);
	    OutfitDisplayItemButton_Change(this);
	    return;
	 end
      end
      -- fall through for drags and non-modified clicks
      if ( CursorHasItem() ) then
	 AcceptCursorItem(this);
      end
   end
end

function OutfitDisplayItemButton_OnLoad()
   local parentlen = string.len(this:GetParent():GetName())+1;
   local slotName = strsub(this:GetName(), parentlen);
   local id;
   local textureName;
   id, textureName = GetInventorySlotInfo(slotName);
   this:SetID(id);
   this.backgroundTextureName = textureName;
   SetItemButtonTexture(this, this.backgroundTextureName);
   this:RegisterForDrag("LeftButton");
   this:RegisterForClicks("LeftButtonUp", "RightButtonUp");
   this:RegisterEvent("CURSOR_UPDATE");
end

function OutfitDisplayItemButton_Draw(button)
   if ( button.texture ) then
      SetItemButtonTexture(button, button.texture);
   else
      SetItemButtonTexture(button, button.backgroundTextureName);
   end
   if ( button.missing ) then
      SetItemButtonTextureVertexColor(button, 1.0, 0.1, 0.1);
   elseif ( button.banked ) then
      SetItemButtonTextureVertexColor(button, 0.1, 0.1, 1.0);
   else
      SetItemButtonTextureVertexColor(button, 1.0, 1.0, 1.0);
   end
   local checkbox = getglobal(button:GetName().."CheckBox");
   if (checkbox) then
      local pname = button:GetParent():GetName();
      local showhelm = getglobal(pname.."ShowHelm");
      local showcloak = getglobal(pname.."ShowCloak");
      if ( button.forced ) then
	 checkbox:SetChecked(true);
	 checkbox:Disable();
	 checkbox:Show();
      elseif ( button.empty or not button.used ) then
	 if ( checkbox.slotName == "HeadSlot" ) then
	    showhelm:Hide();
	    showhelm:SetChecked(false);
	 elseif ( checkbox.slotName == "BackSlot" ) then
	    showcloak:Hide();
	    showcloak:SetChecked(false);
	 end
	 checkbox:Enable();
	 checkbox:SetChecked(button.empty);
	 checkbox:Show();
      elseif ( button.used ) then
	 checkbox:Hide();
	 if ( checkbox.slotName == "HeadSlot" ) then
	    showhelm:Show();
	 elseif ( checkbox.slotName == "BackSlot" ) then
	    showcloak:Show();
	 end
      end
   end
end

function OutfitDisplayItemButton_Change(button)
   OutfitDisplayItemButton_Draw(button);
   -- handle two handed weapons
   local parent = button:GetParent();
   local pname = parent:GetName();
   local parentlen = string.len(pname)+1;
   local slotName = strsub(button:GetName(), parentlen);
   if ( slotName == "MainHandSlot" ) then
      local secondary = getglobal(pname.."SecondaryHandSlot");
      if ( not button.used and secondary.forced ) then
	 OutfitDisplayItemButton_Clear(secondary, false, false);
      elseif ( button.used and not IsItemOneHanded(button.item) ) then
	 OutfitDisplayItemButton_Clear(secondary, true);
	 secondary.forced = true;
      end
      OutfitDisplayItemButton_Draw(secondary);
   end
   OutfitDisplayFrame_UpdateMessage(parent);
   OutfitDisplayFrame_UpdateModel(parent, button);
   if( parent.OutfitChanged ) then
      parent.OutfitChanged( button );
   end
end

function OutfitDisplayItemButton_Clear(button, empty, used)
   button.name = nil;
   button.item = nil;
   button.texture = nil;
   button.color = nil;
   button.missing = nil;
   button.banked = nil;
   button.forced = nil;
   button.empty = empty;
   button.used = used or empty;
end

-- override ShowHelm and ShowCloak
function OutfitDisplayOverrideBox_OnLoad()
   local parentlen = string.len(this:GetParent():GetName())+1;
   this.slotName = strsub(this:GetName(), parentlen);
end

function OutfitDisplayOverrideBox_OnEnter()
   GameTooltip:SetOwner(this, "ANCHOR_RIGHT");
   local text;
   if ( this.slotName == "ShowHelm" ) then
      text = OUTFITDISPLAYFRAME_OVERRIDEHELM;
   else
      text = OUTFITDISPLAYFRAME_OVERRIDECLOAK;
   end
   GameTooltip:SetText(text, nil, nil, nil, nil, 1);
   GameTooltip:Show();
end

-- check box handling
function OutfitDisplayCheckBox_OnEnter()
   GameTooltip:SetOwner(this, "ANCHOR_RIGHT");
   GameTooltip:SetText(OUTFITDISPLAYFRAME_USECHECKBOX, nil, nil, nil, nil, 1);
   GameTooltip:Show();
end

function OutfitDisplayCheckBox_OnLoad()
   local parentlen = string.len(this:GetParent():GetName())+1;
   local name = strsub(this:GetName(), parentlen);
   this.slotName = strsub(name, 1, -9);
end

function OutfitDisplayCheckBox_OnClick()
   local pname = this:GetParent():GetName();
   local button = getglobal(pname..this.slotName);
   OutfitDisplayItemButton_Clear(button, this:GetChecked());
   OutfitDisplayItemButton_Change(button);
end

local function UpdateModel_ThisSlot(pname, model, idx)
   local slotName = PlayerSlotNames[idx].name;
   local what = getglobal(pname..slotName);
   local item = nil;
   if ( what and what.used ) then
      if ( not what.empty ) then
	 item = what.item;
      end
   else
      local slot = PlayerSlotNames[idx].id;
      local link = GetInventoryItemLink("player", slot);
      if ( link ) then
	 _, item, _ = SplitLink(link);
      end
   end
   if ( item ) then
      DressUpItem(model, item);
   end
end

function OutfitDisplayFrame_UpdateModel(frame, button)
   local pname = frame:GetName();
   local model = getglobal(pname.."Model");
   local empty = false;
   if ( not model ) then
      return;
   end
   if ( button and button.used ) then
      if ( button.empty and not button.forced ) then
	 empty = true;
      end
   else
      for i=1,19,1 do
	 local what = getglobal(pname..PlayerSlotNames[i].name);
	 if ( what and what.empty ) then
	    empty = true;
	 end
      end
   end
   if ( empty ) then
      model:Undress();
   end
   for i=19,1,-1 do
      UpdateModel_ThisSlot(pname, model, i);
   end
end

function OutfitDisplayFrame_SetOutfit(frame, outfit)
   if not outfit then
      return;
   end
   local pname = frame:GetName();
   for i=1,19,1 do
      local slotName = PlayerSlotNames[i].name;
      local button = getglobal(pname..slotName);
      if ( button and outfit[slotName] and outfit[slotName].used ) then
	 OutfitDisplayItemButton_Clear(button);
	 if ( outfit[slotName].used ) then
	    button.name = outfit[slotName].name;
	    button.item = outfit[slotName].item;
	    button.texture = outfit[slotName].texture;
	    button.color = outfit[slotName].color;
	    button.missing = outfit[slotName].missing;
	    button.banked = outfit[slotName].banked;
	    button.forced = outfit[slotName].forced;
	    button.empty = outfit[slotName].empty;
	    button.used = true;
	 else
	    OutfitDisplayItemButton_Clear(button, true, true);
	 end
	 OutfitDisplayItemButton_Draw(button);
      end
   end
   if ( outfit["Options"] ) then
      local showhelm = getglobal(pname.."ShowHelm");
      local showcloak = getglobal(pname.."ShowCloak");
      showhelm:SetChecked(outfit["Options"].helm);
      showcloak:SetChecked(outfit["Options"].cloak);
   end
   OutfitDisplayFrame_UpdateModel(frame);
   OutfitDisplayFrame_UpdateMessage(frame, outfit);
end

function OutfitDisplayFrame_GetOutfit(frame, puthere)
   local pname = frame:GetName();
   local outfit = puthere or nil;
   local showhelm = getglobal(pname.."ShowHelm");
   local showcloak = getglobal(pname.."ShowCloak");
   local setOptions = false;
   if not outfit then
      outfit = {};
   end
   outfit["Options"] = {};
   for i=1,19,1 do
      local slotName = PlayerSlotNames[i].name;
      local button = getglobal(pname..slotName);

      if ( button and button.used ) then
	 outfit[slotName] = {};
	 if ( not button.empty ) then
	    outfit[slotName].name = button.name;
	    outfit[slotName].item = button.item;
	    outfit[slotName].texture = button.texture;
	    outfit[slotName].color = button.color;
	    outfit[slotName].missing = button.missing;
	    outfit[slotName].banked = button.banked;
	    outfit[slotName].forced = button.forced;
	 end
	 outfit[slotName].empty = button.empty;
	 outfit[slotName].used = true;
	 if ( not button.empty ) then
	    if ( slotName == "HeadSlot" ) then
	       if ( showhelm and showhelm:GetChecked() ) then
		  outfit["Options"].helm = true;
		  setOptions = true;
	       end
	    elseif ( slotName == "BackSlot" ) then
	       if ( showcloak and showcloak:GetChecked() ) then
		  outfit["Options"].cloak = true;
		  setOptions = true;
	       end
	    end
	 end
      elseif ( outfit and outfit.used ) then
	 outfit[slotName] = nil;
      end
   end
   if ( not setOptions ) then
      outfit["Options"] = nil;
   end
   if ( outfit ) then
      -- check for two-handed weapon
      local mainhand = outfit["MainHandSlot"];
      if ( mainhand and not IsItemOneHanded(mainhand.item) ) then
	 outfit["SecondaryHandSlot"] = {};
	 outfit["SecondaryHandSlot"].forced = true;
	 outfit["SecondaryHandSlot"].empty = true;
	 outfit["SecondaryHandSlot"].used = true;
      end
   end
   return outfit;
end

function OutfitDisplayFrame_GetItemInfo(bag, slot)
   local link;
   local c, i, n;
   if ( bag ) then
      link = GetContainerItemLink (bag,slot);
   else
      link = GetInventoryItemLink("player", slot);
   end
   if (link) then
      c, i, n = SplitLink(link);
   end
   return c, i, n;
end

-- get a snapshot of the current state of the player
function OutfitDisplayFrame_GetBagInfo()
   local contents = {};
   local empties = {};

   local numSlots = 0;
   -- check each of the bags on the player
   for bag=0, NUM_BAG_FRAMES do
      -- get the number of slots in the bag (0 if no bag)
      numSlots = GetContainerNumSlots(bag);
      if (numSlots > 0) then
	 -- check each slot in the bag
	 for slot=1, numSlots do
	    local c, i, n = OutfitDisplayFrame_GetItemInfo(bag, slot);
	    if ( i ) then
	       contents[i] = {};
	       contents[i].bag = bag;
	       contents[i].slot = slot;
	       contents[i].color = c;
	       contents[i].name = n;
	    else
	       local what = {};
	       what.bag = bag;
	       what.slot = slot;
	       tinsert(empties, what);
	    end
	 end
      end
   end

   for idx=1,19,1 do
      local slot = PlayerSlotNames[idx].id;
      local c, i, n = OutfitDisplayFrame_GetItemInfo(nil, slot);
      if ( i ) then
	 contents[i] = {};
	 contents[i].slot = slot;
	 contents[i].color = c;
	 contents[i].name = n;
      end
   end

   return contents, empties;
end

-- look in a particular bag
local function CheckThisBag(bag, id, skipcount)
   -- get the number of slots in the bag (0 if no bag)
   local numSlots = GetContainerNumSlots(bag);
   if (numSlots > 0) then
      -- check each slot in the bag
      for slot=1, numSlots do
	 local c, i, n = OutfitDisplayFrame_GetItemInfo(bag, slot);
	 if ( i and id == i ) then
	    if ( skipcount == 0 ) then
	       return slot, skipcount;
	    end
	    skipcount = skipcount - 1;
	 end
      end
   end
   return nil, skipcount;
end

-- look for the outfit anywhere we can find it
local function FindThisItem(id, skipcount)
   if ( not id ) then
      return nil,nil,nil;
   end
   local skipcount = skipcount or 0;
   -- check each of the bags on the player
   for bag=0, NUM_BAG_FRAMES do
      local slot;
      slot, skipcount = CheckThisBag(bag, id, skipcount);
      if ( slot ) then
	 return bag, slot, false;
      end
   end

   for idx=1,19,1 do
      local slot = PlayerSlotNames[idx].id;
      local c, i, n = OutfitDisplayFrame_GetItemInfo(nil, slot);
      if ( (id and i and id == i) or (name and n and name == n) ) then
	 if ( skipcount == 0 ) then
	    return nil, slot, false;
	 end
	 skipcount = skipcount - 1;
      end
   end

   -- look in the bank
   -- Geez, this is ugly
   local bankbags = { BANK_CONTAINER, 5, 6, 7, 8, 9, 10 };
   for b=1,7 do
      local slot;
      slot, skipcount = CheckThisBag(bankbags[b], id, skipcount);
      if ( slot ) then
	 return bankbags[b], slot, true;
      end
   end
   -- return nil, nil, nil;
end

-- look for the outfit anywhere we can find it
-- we should look in the bank someday
function OutfitDisplayFrame_FindOutfit(outfit)
   -- look for every item, if we find everything return a table of locations
   -- otherwise don't return anything
   if ( not outfit ) then
      return nil;
   end
   local spots = { };
   for slotName in outfit do
      if ( outfit[slotName].use and not outfit[slotName].empty ) then
	 local bag, slot = FindThisItem( outfit[slotName].item );
	 if ( not bag and not slot ) then
	    spots = nil;
	    return nil;
	 else
	    spots[slotName] = { };
	    spots[slotName].bag = bag;
	    spots[slotName].slot = slot;
	 end
      end
   end
   return spots;
end

function OutfitDisplayFrame_CanFindOutfit(outfit)
   if ( not outfit ) then
      return false;
   end
   for slotName in outfit do
      if ( outfit[slotName].used and not outfit[slotName].empty ) then
	 local bag, slot = FindThisItem( outfit[slotName].item );
	 if ( not bag and not slot ) then
	    return false;
	 end
      end
   end
   return true;
end

local function CheckSwitchWillFail(frame, outfit)
   local contents, empties = OutfitDisplayFrame_GetBagInfo();
   local numfree = table.getn(empties);
   local needfree = 0;
   local missing = false;
   local banked = false;
   for i=1,19,1 do
      local slotName = PlayerSlotNames[i].name;
      local check;
      if ( outfit ) then
	 check = outfit[slotName];
      else
	 check = getglobal(frame:GetName()..slotName);
      end
      if ( check and check.used ) then
	 if ( not check.empty ) then
	    local bag, slot, inbank = FindThisItem( check.item );
	    if ( not bag and not slot ) then
	       -- pretend it's still in the bank and we just can't get it
	       if ( check.banked ) then
		  check.banked = true;
		  banked = true;
	       else
		  missing = true;
	       end
	       check.missing = not check.banked;
	    else
	       check.missing = nil;
	       check.banked = inbank;
	    end
	 else
	    local slot = PlayerSlotNames[i].id;
	    if ( OutfitDisplayFrame_GetItemInfo(nil, slot) ) then
	       needfree = needfree + 1;
	    end
	 end
      end
   end
   if ( missing ) then
      return OUTFITDISPLAYFRAME_ITEMSNOTFOUND;
   end
   if ( banked and not BankIsOpen ) then
      return OUTFITDISPLAYFRAME_ITEMSINBANK;
   end
   if ( needfree > numfree ) then
      return OUTFITDISPLAYFRAME_NOTENOUGHFREESPACE;
   end
   return nil;
end

function OutfitDisplayFrame_SwitchWillFail(frame, outfit)
   if ( not outfit ) then
      return OUTFITDISPLAYFRAME_INVALIDOUTFIT;
   end
   if ( not next(outfit) ) then
      return OUTFITDISPLAYFRAME_EMPTYOUTFIT;
   end
   -- if we're swapping, fail early
   if ( OutfitDisplayFrame_IsSwapping() ) then
      return OUTFITDISPLAYFRAME_TOOFASTMSG;
   end
   return CheckSwitchWillFail(frame, outfit);
end

function OutfitDisplayFrame_IsWearing(outfit)
   if ( not outfit ) then
      return false;
   end
   for slotName in outfit do
      if ( outfit[slotName].used and not outfit[slotName].empty ) then
         local slot = GetInventorySlotInfo(slotName);
         local link = GetInventoryItemLink("player", slot);
         local foundit = false;
	 if ( link ) then
	    local color, item, name = SplitLink(link);
	    if ( item == outfit[slotName].item ) then
	       foundit = true;
	    end
	 end
	 if ( not foundit ) then
	    return false;
	 end
      end
   end
   return true;
end

function OutfitDisplayFrame_UpdateMessage(frame, outfit)
   if ( frame ) then
      local msg;
      if ( outfit ) then
	 msg = CheckSwitchWillFail(frame, outfit);
      end
      local messages = getglobal(frame:GetName().."Message");
      if ( msg ) then
	 messages:SetText(msg);
	 messages:SetTextColor(1, 1, 1);
	 messages:Show();
      else
	 messages:SetText("");
	 messages:Hide();
      end
   end
end

function OutfitDisplayFrame_GetSlotContents(slotName, returnEmpty)
   if ( slotName ~= "Options" ) then
      local slot = GetInventorySlotInfo(slotName);
      local link = GetInventoryItemLink("player", slot);
      if ( link ) then
	 local contents = {};
	 local color, item, name = SplitLink(link);
	 contents.item = item;
	 contents.name = name;
	 contents.color = color;
	 contents.texture = texture;
	 contents.used = true;
	 local bag, slot = FindThisItem( item );
	 contents.bag = bag;
	 contents.slot = slot;
	 return contents;
      end
   end
   -- return nil;
end

function OutfitDisplayFrame_GetWearing(check)
   local wearing = {};
   if ( check ) then
      for slotName in check do
	 wearing[slotName] = OutfitDisplayFrame_GetSlotContents(slotName);
      end
      -- make sure we pick up the off hand if we're going to put something
      -- two-handed in the main hand slot
      local mainhand = check["MainHandSlot"];
      local offhand = wearing["SecondaryHandSlot"];
      if ( not offhand and mainhand and not IsItemOneHanded(mainhand.item) ) then
	 wearing["SecondaryHandSlot"] = OutfitDisplayFrame_GetSlotContents("SecondaryHandSlot");
      end
   else
      for i=1,19,1 do
	 local slotName = PlayerSlotNames[i].name;
	 wearing[slotName] = OutfitDisplayFrame_GetSlotContents(slotName);
      end
   end
   if ( not ShowingCloak() or not ShowingHelm() ) then
      wearing["Options"] = {}
      wearing["Options"].cloak = not(not ShowingCloak());
      wearing["Options"].helm = not(not ShowingHelm());
   end
   return wearing;
end

function OutfitDisplayFrame_FindLastEmptyBagSlot(skipcount, bag_affinity, slot_affinity)
   skipcount = skipcount or 0;

   -- try to put the item in the requested affinity, if possible
   if bag_affinity and slot_affinity and 
      not GetContainerItemInfo(bag_affinity, slot_affinity) then
      return bag_affinity, slot_affinity;
   end

   -- if we couldn't get the bag and slot we wanted, just try the same bag
   if bag_affinity then
      for j=GetContainerNumSlots(bag_affinity),1,-1 do
	 if not GetContainerItemInfo(bag_affinity,j) then
	    if skipcount == 0 then return bag_affinity,j; end
	    skipcount = skipcount - 1;
	 end
      end
   end

   -- no affinity, check all bags
   for i=NUM_BAG_FRAMES,0,-1 do
      -- but skip any bag we already have affinity for (because it might have 
      -- already modified skipcount)
      if bag_affinity ~= i then
	 -- Make sure this isn't a quiver, those won't hold shit
	 local bagName = GetBagName(i);
	 if ( bagName ) then
	    local texture = GetInventoryItemTexture("player",
						    ContainerIDToInventoryID(i));
	    
	    if ( string.find(texture, "INV_Misc_Bag_%d") and not string.find(bagName, AMMOSLOT) ) then
	       for j=GetContainerNumSlots(i),1,-1 do
		  if not GetContainerItemInfo(i,j) then
		     if skipcount == 0 then return i,j; end
		     skipcount = skipcount - 1;
		  end  -- if empty
	       end  -- for slots
	    end  -- if normal bag
	 end -- if there is a bag
      end -- if not affinity bag
   end  -- for bags

   -- not found return nil,nil implicitly
end

-- okay, let's switch to the specified outfit
-- code liberally borrowed from "WeaponQuickSwap - by CapnBry"

-- list functions
local function swapentry_print(entry)
   if ( DEFAULT_CHAT_FRAME ) then
      local msg = "Entry "..tonil(entry.sb)..","..tonil(entry.si)..","..tonil(entry.db)..","..tonil(entry.di);
      DEFAULT_CHAT_FRAME:AddMessage(msg, 1.0,0,0);
   end      
end

local function swaplist_print(list)
   while ( list ) do
      swapentry_print(list);
      list = list.next;
   end
end

local function swaplist_push(list, sb, si, db, di, helm, cloak)
   list = { next = list, sb = sb, si = si, db = db, di = di,
      helm = helm, cloak = cloak };
   return list;
end

local function swaplist_popfirst(list)
   if not list then return; end
   list = list.next;
   return list;
end

-- Unit variable to hold the stack of swaps
local outfitswap = nil;

function OutfitDisplayFrame_IsSwapping()
   if ( outfitswap or OutfitDisplayFrame_AnyItemLocked() ) then
      return true;
   else
      return false;
   end
end

-- First we do everything except the main hand and secondary hand
-- Then we do the hard stuff

function OutfitDisplayFrame_ItemIsLocked(bag, slot)
   if not bag and not slot then
      return false;
   end

   if not bag then
      return IsInventoryItemLocked(slot);
   else
      local _,_,locked = GetContainerItemInfo(bag,slot);
      return locked;
   end
end

function OutfitDisplayFrame_AnyItemLocked()
-- Checks all the bags and the equipped slots to see if any are still locked
   for i=0,NUM_BAG_FRAMES do
      for j=1,GetContainerNumSlots(i) do
	 local _,_,locked = GetContainerItemInfo(i,j);
	 if ( locked ) then
	    return true;
	 end
      end
   end
   for i=1,19,1 do
      if ( IsInventoryItemLocked(PlayerSlotNames[i].id) ) then
	 return true;
      end
   end
   return false;
end

function OutfitDisplayFrame_ExecuteSwapIteration()
   if not outfitswap then
      PerformSlowerSwap = false;
      return;
   end

   if OutfitDisplayFrame_ItemIsLocked(outfitswap.sb, outfitswap.si) or
      OutfitDisplayFrame_ItemIsLocked(outfitswap.db, outfitswap.di) then
      return;
   end

   if not outfitswap.sb then
      SavedPickupInventoryItem(outfitswap.si);
   else
      SavedPickupContainerItem(outfitswap.sb, outfitswap.si);
   end

   if not outfitswap.db then
      if not outfitswap.di then
	 PutItemInBackpack();
      else
	 SavedPickupInventoryItem(outfitswap.di);
      end
   else
      SavedPickupContainerItem(outfitswap.db, outfitswap.di);
   end

   if ( outfitswap.cloak ~= nil ) then
      ShowCloak(outfitswap.cloak);
   elseif ( outfitswap.helm ~= nil ) then
      ShowHelm(outfitswap.helm);
   end

   outfitswap = swaplist_popfirst(outfitswap);
   if ( outfitswap and not PerformSlowerSwap ) then
      return OutfitDisplayFrame_ExecuteSwapIteration();
   end
end

function OutfitDisplayFrame_SwitchOne(outfit, index, skipcount, showhelm, showcloak)
   local slotName = PlayerSlotNames[index].name;
   if ( outfit and outfit[slotName] ) then
      local invslot = GetInventorySlotInfo(slotName);
      if ( outfit[slotName].empty ) then
	 local bag, slot = OutfitDisplayFrame_FindLastEmptyBagSlot(skipcount);
	 outfitswap = swaplist_push(outfitswap, nil, invslot, bag, slot);
	 skipcount = skipcount + 1;
      else
	 local helmflag, cloakflag;
	 if ( slotName == "HeadSlot" ) then
	    helmflag = showhelm;
	 elseif ( slotName == "BackSlot" ) then
	    cloakflag = showcloak;
	 end
	 local bag, slot = FindThisItem( outfit[slotName].item );
	 -- either we couldn't find it, or it's where it should be
	 if ( bag ~= nil or slot ~= invslot ) then
	    outfitswap = swaplist_push(outfitswap, bag, slot, nil, invslot, helmflag, cloakflag);
	 end
      end
   end
   return skipcount;
end

local function OnSwapError(error)
   outfitswap = nil;
   if ( UIErrorsFrame ) then
      UIErrorsFrame:AddMessage(error, 1.0, 0.1, 0.1, 1.0, UIERRORS_HOLD_TIME);
   end
   return;
end

function OutfitDisplayFrame_SwitchOutfit(outfit)
   if ( CursorHasItem() or OutfitDisplayFrame_IsSwapping() ) then
      return OnSwapError(OUTFITDISPLAYFRAME_TOOFASTMSG); 
   end
   if ( not outfit ) then
      return OnSwapError(OUTFITDISPLAYFRAME_INVALIDOUTFIT);
   end

   -- might not need to swap slowly
   PerformSlowerSwap = false;

   -- need to check to see that we have enough room
   local old = OutfitDisplayFrame_GetWearing(outfit);

   local showcloak;
   local showhelm;
   if ( outfit["Options"] ) then
      showhelm = outfit["Options"].helm;
      showcloak = outfit["Options"].cloak;
   end

   -- do everything except weapon slots
   local skipcount = 0;
   for i=1,16,1 do
      skipcount = OutfitDisplayFrame_SwitchOne(outfit, i, skipcount, showhelm, showcloak);
   end
   OutfitDisplayFrame_SwitchOne(outfit, 19, skipcount);

   if ( not outfit["MainHandSlot"] and not outfit["SecondaryHandSlot"] ) then
      OutfitDisplayFrame_ExecuteSwapIteration(); 
      return old;
   end

   local mainhandslot = GetInventorySlotInfo("MainHandSlot");
   local secondaryslot = GetInventorySlotInfo("SecondaryHandSlot");
   local mainhand = outfit["MainHandSlot"];
   local offhand = outfit["SecondaryHandSlot"];
   -- now do hands
   local m_sb;
   local m_si;
   local o_sb;
   local o_si;
   local m_ok = not mainhand or not mainhand.item;
   local o_ok = not offhand or not offhand.item;

   if ( mainhand and mainhand.item ) then
      m_sb, m_si = FindThisItem(mainhand.item);
      m_ok = ( not m_sb and m_si == mainhandslot );
   end
   if ( offhand and offhand.item ) then
      local multiples = 0;
      if ( mainhand and mainhand.item == offhand.item ) then
	 multiples = 1;
      end
      o_sb, o_si = FindThisItem( offhand.item, multiples);
      o_ok = ( not o_sb and o_si == secondaryslot );
   end

   if ( not m_ok ) then
      -- do we need two of these?
      if ( o_ok and not m_sb and m_si == secondaryslot ) then
	 m_sb, m_si = FindThisItem( mainhand.item, 1);
      end
   end

   -- moving from bags
   if ( m_sb and o_sb ) then
      -- insert them backwards, since they get popped off
      -- main hand has to get done first in case it's currently
      -- a two hander
      outfitswap = swaplist_push(outfitswap, o_sb, o_si, nil, secondaryslot);
      outfitswap = swaplist_push(outfitswap, m_sb, m_si, nil, mainhandslot);
   elseif ( not m_sb and m_si == secondaryslot and not o_sb and o_si == mainhandslot ) then
      outfitswap = swaplist_push(outfitswap, nil, mainhandslot, nil, secondaryslot);
   else
      -- Install main hand
      if not m_ok then
	 -- if nothing going to the main hand
	 if ( not m_sb and not m_si ) then
	    -- and the main is not going to the off: put it in a bag
	    if not ( not o_sb and o_si == mainhandslot) then
	       local bb, bi = OutfitDisplayFrame_FindLastEmptyBagSlot(skipcount);
	       if not (bb and bi) then 
		  return OnSwapError(OUTFITDISPLAYFRAME_NOTENOUGHFREESPACE); 
	       end
	       skipcount = skipcount + 1;
	       outfitswap = swaplist_push(outfitswap, nil, mainhandslot, bb, bi);
	       -- when moving A,"" -> "",B where A is a 2h, the offhand
	       -- doesn't lock properly, so work around it by swapping
	       -- slowly (only one swap per lock notify)
	       PerformSlowerSwap = not IsItemOneHanded(mainhand.item);
	    end
	 else
	    outfitswap = swaplist_push(outfitswap, m_sb, m_si, nil, mainhandslot);
	 end
      end
  
      -- Load offhand if not already there
      if not o_ok then
	 if ( not o_sb and not o_si ) then
	    if not (not m_sb and m_si == secondaryslot) then
	       local bb, bi;
	       if LastOffSource then
		  bb, bi = OutfitDisplayFrame_FindLastEmptyBagSlot(skipcount, 
								   LastOffSource.bag, LastOffSource.slot);
	       else
		  bb, bi = OutfitDisplayFrame_FindLastEmptyBagSlot(skipcount);
	       end
	       
	       if not (bb and bi) then 
		  return OnSwapError(OUTFITDISPLAYFRAME_NOTENOUGHFREESPACE); 
	       end
	       skipcount = skipcount + 1;
	       outfitswap = swaplist_push(outfitswap, nil, secondaryslot, bb, bi);
	    end
	 else
	    -- if the main hand weapon is coming from the offhand slot
	    -- we need to fix up its source to be where the offhand is 
	    -- GOING to be after the bag->off swap
	    if outfitswap and ( not m_sb and m_si == secondaryslot) then
	       outfitswap.sb = o_sb;
	       outfitswap.si = o_si;
	       -- don't set o_sb, o_si they're tested later
	    end
	    
	    outfitswap = swaplist_push(outfitswap, o_sb, o_si, nil, secondaryslot);
	 end
      end

-- Special Case: Moving off to main, and not main to off
-- This is because maybe the main hand weapon is main only
      if (not m_sb and m_si == secondaryslot) and not ( not o_sb and o_si == mainhandslot) then
	 local bb, bi = OutfitDisplayFrame_FindLastEmptyBagSlot(skipcount);
	 if not (bb and bi) then 
	    return OnSwapError(OUTFITDISPLAYFRAME_NOTENOUGHFREESPACE); 
	 end
	 skipcount = skipcount + 1;
	 outfitswap = swaplist_push(outfitswap, nil, mainhandslot, bb, bi);
      end

      -- Same thing for off hand
      if (not o_sb and o_si == mainhandslot) and not (not m_sb and m_si == secondaryslot) then
	 local bb, bi = OutfitDisplayFrame_FindLastEmptyBagSlot(skipcount);
	 if not (bb and bi) then 
	    return OnSwapError(OUTFITDISPLAYFRAME_NOTENOUGHFREESPACE); 
	 end
	 skipcount = skipcount + 1;
	 outfitswap = swaplist_push(outfitswap, nil, mainhandslot, bb, bi);
      end

      if o_sb then 
	 LastOffSource = { bag = o_sb, slot = o_si }; 
      end
   end   
   -- Start moving
   OutfitDisplayFrame_ExecuteSwapIteration(); 
   return old;
end
