-- FishingBuddy
--
-- Everything you wanted support for in your fishing endeavors

-- Information for the stylin' fisherman
local POLES = {
   ["Fishing Pole"] = "6256:0:0:0",
   ["Strong Fishing Pole"] = "6365:0:0:0",
   ["Darkwood Fishing Pole"] = "6366:0:0:0",
   ["Big Iron Fishing Pole"] = "6367:0:0:0",
   ["Blump Family Fishing Pole"] = "12225:0:0:0",
   ["Nat Pagle's Extreme Angler FC-5000"] = "19022:0:0:0",
   ["Arcanite Fishing Pole"] = "19970:0:0:0",
   -- yeah, so you can't really use these (for now :-)
   ["Dwarven Fishing Pole"] = "3567:0:0:0",
   ["Goblin Fishing Pole"] = "4598:0:0:0",
}

local FISHINGLURES = {
   ["6533:0:0:0"] = "Aquadynamic Fish Attractor",	-- 100 for 5 mins
   ["7307:0:0:0"] = "Flesh Eating Worm",		-- 75 for 10 mins
   ["6532:0:0:0"] = "Bright Baubles",			-- 75 for 10 mins
   ["6811:0:0:0"] = "Aquadynamic Fish Lens",		-- 50 for 10 mins
   ["6530:0:0:0"] = "Nightcrawlers",			-- 50 for 10 mins
   ["6529:0:0:0"] = "Shiny Bauble",			-- 25 for 10 mins
}

FishingBuddy.OPTIONS = {
   { ["name"] = "ShowNewFishies",
      ["text"] = FishingBuddy.CONFIG_SHOWNEWFISHIES_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_SHOWNEWFISHIES_INFO,
      ["default"] = 1 },
   { ["name"] = "WatchFishies",
      ["text"] = FishingBuddy.CONFIG_FISHWATCH_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_FISHWATCH_INFO,
      ["default"] = 1 },
   { ["name"] = "WatchCurrentSkill",
      ["text"] = FishingBuddy.CONFIG_FISHWATCHSKILL_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_FISHWATCHSKILL_INFO,
      ["default"] = 1 },
   { ["name"] = "WatchCurrentZone",
      ["text"] = FishingBuddy.CONFIG_FISHWATCHZONE_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_FISHWATCHZONE_INFO,
      ["default"] = 0 },
   { ["name"] = "WatchOnlyWhenFishing",
      ["text"] = FishingBuddy.CONFIG_FISHWATCHONLY_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_FISHWATCHONLY_INFO,
      ["default"] = 1 },
   { ["name"] = "WatchFishPercent",
      ["text"] = FishingBuddy.CONFIG_FISHWATCHPERCENT_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_FISHWATCHPERCENT_INFO,
      ["default"] = 1 },
   { ["name"] = "SortByPercent",
      ["text"] = FishingBuddy.CONFIG_SORTBYPERCENT_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_SORTBYPERCENT_INFO,
      ["default"] = 1 },
   { ["name"] = "SuitUpFirst",
      ["text"] = FishingBuddy.CONFIG_SUITUPFIRST_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_SUITUPFIRST_INFO,
      ["default"] = 0 },
   { ["name"] = "EasyCast",
      ["text"] = FishingBuddy.CONFIG_EASYCAST_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_EASYCAST_INFO,
      ["default"] = 1 },
   { ["name"] = "FastCast",
      ["text"] = FishingBuddy.CONFIG_FASTCAST_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_FASTCAST_INFO,
      ["default"] = 1 },
   { ["name"] = "EasyLures",
      ["text"] = FishingBuddy.CONFIG_EASYLURES_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_EASYLURES_INFO,
      ["default"] = 0 },
   { ["name"] = "STVTimer",
      ["text"] = FishingBuddy.CONFIG_STVTIMER_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_STVTIMER_INFO,
      ["default"] = 0 },
   { ["name"] = "UseButtonHole",
      ["text"] = FishingBuddy.CONFIG_USEBUTTONHOLE_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_USEBUTTONHOLE_INFO,
      ["default"] = 0,
      ["check"] = function () return ButtonHole ~= nil; end,
      ["checkfail"] = 0 },
   { ["name"] = "MinimapButtonVisible",
      ["text"] = FishingBuddy.CONFIG_MINIMAPBUTTON_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_MINIMAPBUTTON_INFO,
      ["default"] = 1,
      ["check"] = function () return not FishingBuddy.UseButtonHole(); end,
      ["checkfail"] = 1 },
   { ["name"] = "EnhanceFishingSounds",
      ["text"] = FishingBuddy.CONFIG_ENHANCESOUNDS_ONOFF,
      ["tooltip"] = FishingBuddy.CONFIG_ENHANCESOUNDS_INFO,
      ["default"] = 1 },
}

FishingBuddy.ByFishie = nil;
FishingBuddy.SortedFishies = nil;

local ActionStartTime = 0;
local ActionDoubleTime = 0;
local ACTIONDOWNWAIT = 0.2;
local ACTIONDOUBLEWAIT = 0.4;
local SavedAddMessage = nil;

FishingBuddy.SavedToggleMinimap = nil;

local StartedFishing = nil;
local TestingLures = false;
local CastingNow = false;
local AddingLure = false;

local FishingSpellID = nil;
local FishingSkillName = nil;

local gotSetupDone = false;
local playerName = nil;
local realmName = nil;

FishingBuddy.currentFishies = {};

local DEFAULT_MINIMAP_POSITION = 256;

-- Fill in the player name and realm
FishingBuddy.SetupNameInfo = function()
   playerName = UnitName("player");
   realmName = GetRealmName();
   return playerName, realmName;
end

FishingBuddy.GetWasWearing = function()
   if FishingBuddy_Player["WasWearing"] then
      return FishingBuddy_Player["WasWearing"];
   end
end

FishingBuddy.SetWasWearing = function(outfit)
   FishingBuddy_Player["WasWearing"] = outfit;
end

FishingBuddy.GetOutfitItem = function(slotName)
   if (FishingBuddy_Player["Outfit"]) then
      return FishingBuddy_Player["Outfit"][slotName];
   end
end

FishingBuddy.GetOutfit = function()
   return FishingBuddy_Player["Outfit"];
end

FishingBuddy.SetOutfit = function(outfit)
   FishingBuddy_Player["Outfit"] = outfit;
end

local function GetSetting(setting)
   if ( not FishingBuddy_Player or
        not FishingBuddy_Player["Settings"] ) then
      return;
   end
   return FishingBuddy_Player["Settings"][setting];
end
FishingBuddy.GetSetting = GetSetting;

local function SetSetting(setting, value)
   FishingBuddy_Player["Settings"][setting] = value;
end
FishingBuddy.SetSetting = SetSetting;

-- handle option keys for enabling casting
local key_actions = {
   [FishingBuddy.KEYS_NONE] = function() return true; end,
   [FishingBuddy.KEYS_SHIFT] = function() return IsShiftKeyDown(); end,
   [FishingBuddy.KEYS_CTRL] = function() return IsControlKeyDown(); end,
   [FishingBuddy.KEYS_ALT] = function() return IsAltKeyDown(); end,
}
local function CastingKeys()
   local setting = GetSetting("EasyCastKeys");
   if ( key_actions[setting] ) then
      return key_actions[setting]();
   else
      return true;
   end
end

-- get our current fishing skill level
local lastSkillIndex = nil;
FishingBuddy.GetCurrentSkill = function()
   if ( lastSkillIndex ) then
      local name, _, _, rank, _, modifier = GetSkillLineInfo(lastSkillIndex);
      if ( name == FishingBuddy.GetFishingSkillName() )then
	 return rank, modifier;
      end
   end
   local n = GetNumSkillLines();
   for i=1,n do
      local name, _, _, rank, _, modifier = GetSkillLineInfo(i);
      if ( name == FishingBuddy.GetFishingSkillName() ) then
	 lastSkillIndex = i;
	 return rank, modifier;
      end
   end
   return 0, 0;
end

FishingBuddy.SetZoneLevel = function(zone, subzone, fishid)
   local skill, mods = FishingBuddy.GetCurrentSkill();
   local fs = FishingBuddy_Info["FishingSkill"];
   if ( not fs[zone] ) then
      fs[zone] = {};
   end
   local skillcheck = skill + mods;
   if ( skillcheck > 0 ) then
      if ( not fs[zone][subzone] or skillcheck < fs[zone][subzone] ) then
         fs[zone][subzone] = skillcheck;
      end
      if ( fishie ) then
         if ( not FishingBuddy_Info["Fishies"][fishid].level or
              skillcheck < FishingBuddy_Info["Fishies"][fishid].level ) then
            FishingBuddy_Info["Fishies"][fishid].level = skillcheck;
            FishingBuddy_Info["Fishies"][fishid].skill = skill;
            FishingBuddy_Info["Fishies"][fishid].mods = mods;
         end
      end
   end
end

local OldBindingKey;
local function UpdateBindings(onoff)
   if ( onoff ) then
      if ( not OldKeyBinding ) then
	 OldKeyBinding = GetBindingKey("TURNORACTION");
	 if ( OldKeyBinding ) then
	    SetBinding(OldKeyBinding, "FISHINGBUDDY_PERFORMCAST");
	 else
	    FishingBuddy.UIError(FishingBuddy.TOOMANYFISHERMEN);
	 end
      end
   else
      if ( OldKeyBinding ) then
	 SetBinding(OldKeyBinding, "TURNORACTION");
	 OldKeyBinding = nil;
      end
   end
end
FishingBuddy.UpdateBindings = UpdateBindings;

-- Shamelessly stolen from TackleBox
local function IsFishingPole()
   -- Get the main hand item texture
   local slot = GetInventorySlotInfo("MainHandSlot");
   local itemTexture = GetInventoryItemTexture("player", slot);
   -- If there is infact an item in the main hand, and it's texture
   -- that matches the fishing pole texture, then we have a fishing pole
   if ( itemTexture and string.find(itemTexture, "INV_Fishingpole") ) then
      local link = GetInventoryItemLink("player", slot);
	  local _, id, _ = FishingBuddy.SplitLink(link);
      -- Make sure it's not "Nat Pagle's Fish Terminator"
      return (id ~= 19944);
   end
   return false;
end
FishingBuddy.IsFishingPole = IsFishingPole;

-- override the error message method (need an object as the first arg)
local function UIError_AddMessage( o, msg, a, r, g, b, hold )
   -- If we should be hiding the can't use item error, then do so
   if ( TestingLures and msg == ERR_CANT_USE_ITEM  ) then
      -- We have the can't use item error, so abort
      return;
   end
   --Call the original
   local obj = SavedAddMessage.obj;
   local method = SavedAddMessage.method;
   return method( obj, msg, a, r, g, b, hold );
end
FishingBuddy.AddMessage = UIError_AddMessage;

local function HijackCheck()
   return ( GetSetting("EasyCast") == 1 and
	    CastingKeys() and IsFishingPole() );
end

local SavedWFOnMouseUp;
local SavedWFOnMouseDown;
-- handle mouse up and mouse down in the WorldFrame so that we can steal
-- the hardware events to implement 'Easy Cast'
-- Thanks to the Cosmos team for figuring this one out -- I didn't realize
-- that the mouse handler in the WorldFrame got everything first!
local function WF_OnMouseDown()
   -- Only steal 'right clicks'
   if ( arg1 == "RightButton" and HijackCheck() ) then
      FishingBuddy.ActionStartCheck();
   end
   if ( SavedWFOnMouseDown ) then
      SavedWFOnMouseDown();
   end
end

-- disable click to move if we're fishing
local resetClickToMove = false;
local function WF_OnMouseUp()
   local retval;
   if ( SavedWFOnMouseUp ) then
      retval = SavedWFOnMouseUp();
   end
   -- Only steal 'right clicks'
   if ( arg1 == "RightButton" ) then
      -- we fail if there are multiple modifier keys
      if ( IsAltKeyDown() and ( IsShiftKeyDown() or IsControlKeyDown() ) ) then
	 return retval;
      end
      if ( IsShiftKeyDown() and IsControlKeyDown() ) then
	 return retval;
      end
      if ( HijackCheck() ) then
	 FishingBuddy.StopCastingCheck();
      elseif ( resetClickToMove ) then
	 -- Re-enable Click-to-Move if we changed it
	 SetCVar("autointeract", "1");
	 resetClickToMove = nil;
      end
   end
   return retval;
end

-- Find things in our inventory
local function FindItem(id)
   if ( id ) then
      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 link = GetContainerItemLink (bag,slot);
	       if (link) then
		  local c, i, n = FishingBuddy.SplitLink(link);
		  local check = string.gsub(i, "^(%d+):(%d+):(%d+):(%d+)$", "%1:%2:0:0");
		  if ( check == id ) then
		     return bag, slot;
		  end
	       end
	    end
	 end
      end
   end
   return nil, nil;
end

local function SafeHookMethod(object, method, newmethod)
   local oldValue = object[method];
   if ( oldValue ~= getglobal(newmethod) ) then
      object[method] = newmethod;
      return true;
   end
   return false;
end

local function SafeHookScript(frame, handlername, newscript)
   local oldValue = frame:GetScript(handlername);
   frame:SetScript(handlername, newscript);
   return oldValue;
end

-- Something else that should be in a library
FishingBuddy.SplitLink = function(link)
   if ( link ) then
      local _,_, color, item, name = string.find(link, "|c(%x+)|Hitem:(%d+:%d+:%d+:%d+)|h%[(.-)%]|h|r");
      return color, item, name;
   end
end

FishingBuddy.SplitFishLink = function(link)
   if ( link ) then
      local _,_, color, id, name = string.find(link, "|c(%x+)|Hitem:(%d+):%d+:%d+:%d+|h%[(.-)%]|h|r");
      return color, tonumber(id), name;
   end
end

-- handle the vagaries of zones and subzones
FishingBuddy.GetZoneInfo = function()
   local zone = GetRealZoneText();
   local subzone = GetSubZoneText();
   if ( not zone or zone == "" ) then
      zone = FishingBuddy.UNKNOWN;
   end
   if ( not subzone or subzone == "" ) then
      subzone = zone;
   end
   return zone, subzone;
end

FishingBuddy.AddFishie = function(color, id, name, zone, subzone, texture, quantity, quality)
   if ( not FishingBuddy_Info["Fishies"][id] ) then
      FishingBuddy_Info["Fishies"][id] = { };
      FishingBuddy_Info["Fishies"][id].name = name;
      FishingBuddy_Info["Fishies"][id].texture = texture;
      FishingBuddy_Info["Fishies"][id].quality = quality;
      if ( color ~= "ffffffff" ) then
	 FishingBuddy_Info["Fishies"][id].color = color;
      end
      if ( FishingBuddy.SortedFishies ) then
	 tinsert(FishingBuddy.SortedFishies, { text = name, id = id });
	 FishingBuddy.FishSort(FishingBuddy.SortedFishies, true);
      end
   end

   if ( not zone ) then
      zone = FishingBuddy.UNKNOWN;
   end
   if ( not subzone ) then
      subzone = zone;
   end

   if ( not FishingBuddy.currentFishies[subzone] ) then
      FishingBuddy.currentFishies[subzone] = {};
   end
   if ( not FishingBuddy.currentFishies[subzone][id] ) then
      FishingBuddy.currentFishies[subzone][id] = quantity;
   else
      FishingBuddy.currentFishies[subzone][id] = FishingBuddy.currentFishies[subzone][id] + quantity;
   end

   if( not FishingBuddy_Info["FishingHoles"][zone] ) then
      FishingBuddy_Info["FishingHoles"][zone] = { };
      tinsert(FishingBuddy.SortedZones, zone);
      table.sort(FishingBuddy.SortedZones);
   end
   if( not FishingBuddy_Info["FishingHoles"][zone][subzone] ) then
      FishingBuddy_Info["FishingHoles"][zone][subzone] = { };
      FishingBuddy.SortedByZone[zone] = {};
      tinsert(FishingBuddy.SortedByZone[zone], subzone);
      table.sort(FishingBuddy.SortedByZone[zone]);
      tinsert(FishingBuddy.SortedSubZones, subzone);
      table.sort(FishingBuddy.SortedSubZones);
   end
   local fh = FishingBuddy_Info["FishingHoles"][zone];
   if ( not fh[subzone][id] ) then
      fh[subzone][id] = quantity;
      if ( GetSetting("ShowNewFishies") == 1 ) then
	 FishingBuddy.Print(FishingBuddy.ADDFISHIEMSG, name, subzone);
      end
   else
      fh[subzone][id] = fh[subzone][id] + quantity;
   end

   if ( FishingBuddy.ByFishie ) then
      if ( not FishingBuddy.ByFishie[id] ) then
	 FishingBuddy.ByFishie[id] = {};
      end
      if ( not FishingBuddy.ByFishie[id][subzone] ) then
	 FishingBuddy.ByFishie[id][subzone] = quantity;
      else
	 FishingBuddy.ByFishie[id][subzone] = FishingBuddy.ByFishie[id][subzone] + quantity;
      end
   end
   FishingBuddy.Locations.DataChanged(zone, subzone, fishie);
   FishingBuddy.WatchUpdate();
end

FishingBuddy.GetFishie = function(fishid)
   if( FishingBuddy_Info["Fishies"][fishid] ) then
      return string.format("%d:0:0:0", fishid),
      FishingBuddy_Info["Fishies"][fishid].texture,
      FishingBuddy_Info["Fishies"][fishid].color,
      FishingBuddy_Info["Fishies"][fishid].quantity,
      FishingBuddy_Info["Fishies"][fishid].quality,
      FishingBuddy_Info["Fishies"][fishid].name;
   end
end

local function ImportFish()
   local oldShowNewFishies = GetSetting("ShowNewFishies");
   SetSetting("ShowNewFishies");
   if ( FishInfo2DataSub and FishingBuddy_Info["FishInfo2"] == 0 ) then
      for toon in FishInfo2DataSub do
	 if ( not string.find(toon, "|User$") ) then
	    for fishie in FishInfo2DataSub[toon] do
	       local texture = FishInfo2DataSub[toon][fishie]["texture"];
	       local quality = FishInfo2DataSub[toon][fishie]["rarity"];
	       local fz = FishInfo2DataSub[toon][fishie]["zones"];
	       local link = FishInfo2DataSub[toon][fishie]["link"];
	       if ( link ) then
		  local _,_,id = string.find(link, "^(%d+):");
		  for zone in fz do
		     for subzone in fz[zone] do
			local quantity = fz[zone][subzone];
			FishingBuddy.AddFishie("ffffffff", id, fishie, zone, subzone, texture, quantity, quality)
		     end
		  end
	       end
	    end
	 end
      end
      FishingBuddy_Info["FishInfo2"] = 1;
      FishingBuddy.Print(FishingBuddy.IMPORTMSG, "FishInfo2");
--   elseif ( imppfishinfoDB and FishingBuddy_Info["ImppDBLoaded"] == 0 ) then
--      for subzone in imppfishinfoDB do
--	 for item in imppfishinfoDB[subzone]["itemnames"] do
--	    local quantity = imppfishinfoDB[subzone]["itemnames"][item].number;
--	    local texture = imppfishinfoDB[subzone]["itemnames"][item].texture;
--	    local quality = imppfishinfoDB[subzone]["itemnames"][item].quality;
--	    FishingBuddy.AddFishie(color, id, name, subzone, texture, item, quantity, quality)
--	 end
--      end
--      FishingBuddy_Info["ImppDBLoaded"] = 1;
--      FishingBuddy.Print(FishingBuddy.IMPORTMSG, "Impp's fishinfo");
--   elseif ( DataFishDB and FishingBuddy_Info["DataFish"] == 0 ) then
--      for fishie in DataFishDB["catches"] do
--	 for zone in DataFishDB["catches"][fishie]["locnames"] do
--	    for subzone in DataFishDB["catches"][fishie]["locnames"][zone] do
--	       local dfl = DataFishDB["locations"][subzone]["itemnames"][fishie];
--	       local quantity = dfl["number"];
--	       local texture = dfl["texture"];
--	       local quality = dfl["quality"];
--	       FishingBuddy.AddFishie(color, id, name, zone, subzone, texture, quantity, quality)
--	    end
--	 end
--      end
--      FishingBuddy_Info["DataFish"] = 1;
--      FishingBuddy.Print(FishingBuddy.IMPORTMSG, "DataFish");
   else
      FishingBuddy.Message(FishingBuddy.NOIMPORTMSG);
   end
   SetSetting("ShowNewFishies", oldShowNewFishies);
end

local function FindSpellID(thisone)
   local id = 1;
   local spellTexture = GetSpellTexture(id, BOOKTYPE_SPELL);
   while (spellTexture) do
      if (spellTexture and spellTexture == thisone) then
	 return id;
      end
      id = id + 1;
      spellTexture = GetSpellTexture(id, BOOKTYPE_SPELL);
   end
   return nil;
end

local function GetFishingSpellID()
   if ( not FishingSpellID or not FishingSkillName) then
      FishingSpellID = FindSpellID(FishingBuddy.FISHINGTEXTURE);
   end
   if ( FishingSpellID and not FishingSkillName ) then
      FishingSkillName = GetSpellName(FishingSpellID, BOOKTYPE_SPELL);
   end
end

FishingBuddy.GetFishingSkillName = function()
   GetFishingSpellID();
   if ( not FishingSkillName ) then
      return FishingBuddy.FISHINGSKILL;
   else
      return FishingSkillName;
   end

end

local function InvokeFishing()
   GetFishingSpellID();
   if ( FishingSpellID ) then
      CastSpell(FishingSpellID, BOOKTYPE_SPELL);
   end
end

-- do everything we think is necessary when we start fishing
-- even if we didn't do the switch to a fishing pole
local function StartFishingMode()
   if ( not StartedFishing ) then
      StartedFishing = GetTime();
      FishingBuddy.EnhanceFishingSounds(true);
      FishingBuddy.WatchUpdate();
   end
end
FishingBuddy.StartFishingMode = StartFishingMode;

local function StopFishingMode()
   if ( StartedFishing ) then
      FishingBuddy.EnhanceFishingSounds(false);
      FishingBuddy.WatchUpdate();
      StartedFishing = nil;
   end
   if ( resetClickToMove ) then
      -- Re-enable Click-to-Move if we changed it
      SetCVar("autointeract", "1");
      resetClickToMove = nil;
   end
end
FishingBuddy.StopFishingMode = StopFishingMode;

local function FishingMode()
   if ( IsFishingPole() ) then
      StartFishingMode();
   else
      StopFishingMode();
   end
end
FishingBuddy.FishingMode = FishingMode;

local function OnFishingBobber()
   if ( GameTooltip:IsVisible() ) then
      local text = getglobal("GameTooltipTextLeft1");
      if ( text ) then
	 text = text:GetText();
	 -- let a partial match work (for translations)
	 return ( text and string.find(text, FishingBuddy.BOBBER_NAME ) );
      end
   end
   return false;
end

-- Easy lures (borrowed from Mugendai's excellent code)
local function UpdateLure()
   -- if the pole has an enchantment, we can assume it's got a lure on it (so far, anyway)
   if ( ( GetSetting("EasyLures") == 1 ) and not GetWeaponEnchantInfo() ) then
      -- trap message handler if we haven't already
      if ( not SavedAddMessage ) then
	 FishingBuddy.TrapUIErrors();
      end

      --Check for a lure in the bags, starting with the highest power one
      local bag, slot;
      for lure in FISHINGLURES do
	 --If we find this lure in the bag, then use it
	 bag, slot = FindItem(lure);
	 if (bag and slot) then
	    --We need to temporarily disable the cant use item error, incase this item is too high of a level to use
	    TestingLures = true;
	    --Temporarily disable error message sounds, incase this item is too high of a level to use
	    local soundState = GetCVar("EnableErrorSpeech");
	    if ( soundState == "1" ) then
	       SetCVar("EnableErrorSpeech", 0);
	    end
	    --Try to use the item
	    UseContainerItem(bag, slot);
	    -- re-enable the can't use item error message
	    TestingLures = false;
	    -- re-enable error message sounds
	    if ( soundState == "1" ) then
	       SetCVar("EnableErrorSpeech", 1);
	    end
	    -- if we have a targeting cursor, then we succesfully used the lure
	    if ( SpellIsTargeting() ) then
	       -- apply the lure to the fishing pole
	       local mainhandslot = GetInventorySlotInfo("MainHandSlot");
	       PickupInventoryItem(mainhandslot);
	       AddingLure = true;
	       return true;
	    end
	 end
      end
   end
   return false;
end

FishingBuddy.ActionStartCheck = function()
   -- Disable Click-to-Move if we're fishing
   if ( GetCVar("autointeract") == "1" ) then
      resetClickToMove = true;
      SetCVar("autointeract", "0");
   end
   -- Don't interrupt putting on a lure, or if we're not fastcasting
   if ( AddingLure ) then
      ActionStartTime = 0;
   else
      ActionStartTime = GetTime();
   end
end

-- add the double click checking in based on the code is Cosmos
FishingBuddy.StopCastingCheck = function()
   if ( not ActionStartTime ) then
      ActionStartTime = 0;
   end
   if ( not ActionDoubleTime ) then
      ActionDoubleTime = 0;
   end
   local time = GetTime();
   local pressTime = time - ActionStartTime;
   local doubleTime = time - ActionDoubleTime;
   -- if we're not putting on a lure and the casting modifiers are good
   if ( not SpellIsTargeting() ) then
      -- if the click was "short" enough that we're going to treat it
      -- as a cast
      if ( ActionStartTime > 0 and ACTIONDOWNWAIT >= pressTime) then
	 -- if we're already casting, enforce double click timing
	 if ( GetSetting("FastCast") ~= 1 and CastingNow and
	     ( ActionDoubleTime == 0 or ACTIONDOUBLEWAIT < doubleTime ) ) then
	    ActionDoubleTime = GetTime();
	 else
	    ActionDoubleTime = 0;
	    -- rebind the mouse event
	    UpdateBindings(true);
	 end
      end
   end
end

FishingBuddy.PerformCast = function()
   -- We're stealing the mouse-up hardware event, make sure we exit MouseLook
   if ( IsMouselooking() ) then
      MouselookStop();
   end
   -- reset the TURNORACTION binding so that everything else works 'right'
   UpdateBindings(false);
   -- put on a lure if we need to
   if ( not UpdateLure() ) then
      InvokeFishing();
   end
end

FishingBuddy.TrapUIErrors = function()
   local temp = {};
   temp.obj= UIErrorsFrame;
   temp.method = UIErrorsFrame["AddMessage"];
   if ( SafeHookMethod(UIErrorsFrame, "AddMessage", FishingBuddy.AddMessage) ) then
      SavedAddMessage = temp;
   end
end

FishingBuddy.TrapWorldMouse = function()
   SavedWFOnMouseUp = SafeHookScript(WorldFrame, "OnMouseUp", WF_OnMouseUp);
   SavedWFOnMouseDown = SafeHookScript(WorldFrame, "OnMouseDown", WF_OnMouseDown);
end

-- User interface handling

FishingBuddy.Commands = {};
FishingBuddy.Commands[FishingBuddy.SWITCH] = {};
FishingBuddy.Commands[FishingBuddy.SWITCH].func = function()
					FishingBuddy.OutfitManager.Switch();
					return true;
				     end;
FishingBuddy.Commands[FishingBuddy.CURRENT] = {};
FishingBuddy.Commands[FishingBuddy.CURRENT].help = FishingBuddy.CURRENT_HELP;
FishingBuddy.Commands[FishingBuddy.CURRENT].func =
   function(what)
      if ( what and what == FishingBuddy.RESET) then
	 FishingBuddy.currentFishies = {};
	 FishingMode();
	 return true;
      end
   end;
FishingBuddy.Commands[FishingBuddy.IMPORT] = {};
FishingBuddy.Commands[FishingBuddy.IMPORT].help = FishingBuddy.IMPORT_HELP;
FishingBuddy.Commands[FishingBuddy.IMPORT].func =
   function()
      ImportFish();
      return true;
   end;
FishingBuddy.Commands[FishingBuddy.CLEANUP] = {};
FishingBuddy.Commands[FishingBuddy.CLEANUP].help = FishingBuddy.CLEANUP_HELP;
FishingBuddy.Commands[FishingBuddy.CLEANUP].func =
   function(what)
      local tabs = { "Settings", "Outfit", "WasWearing" };
      local rnames = {};
      local onames = {};
      if ( not what or what == FishingBuddy.CHECK ) then
	 if ( FishingBuddy_Info["Settings"] ) then
	    for name in FishingBuddy_Info["Settings"] do
	       tinsert(onames, name);
	    end
	 end
	 if ( FishingBuddy_Info[realmName] ) then
	    local fs = FishingBuddy_Info[realmName]["Settings"];
	    if ( fs ) then
	       for name,info in fs do
		  tinsert(rnames, name);
	       end
	    end
	 end
	 if ( next(rnames) or next(onames) ) then
	    if ( next(rnames) ) then
	       FishingBuddy.Print(FishingBuddy.CLEANUP_WILLMSG, realmName,
				  FishingBuddy.EnglishList(rnames));
	    end
	    if ( next(onames) ) then
	       FishingBuddy.Print(FishingBuddy.CLEANUP_WILLMSG,
				  FishingBuddy.NOREALM,
				  FishingBuddy.EnglishList(onames));
	    end
	 else
	    FishingBuddy.Message(FishingBuddy.CLEANUP_NONEMSG);
	 end
	 return true;
      else
	 if ( what == FishingBuddy.NOW ) then
	    if ( FishingBuddy_Info["Settings"] ) then
	       for name in FishingBuddy_Info["Settings"] do
		  tinsert(onames, name);
	       end
	    end
	    if ( FishingBuddy_Info[realmName] ) then
	       local fs = FishingBuddy_Info[realmName]["Settings"];
	       if ( fs ) then
		  for name,info in fs do
		     tinsert(rnames, name);
		  end
	       end
	    end
	 else
	    if ( FishingBuddy_Info["Settings"] and FishingBuddy_Info["Settings"][what] ) then
	       tinsert(onames, what);
	    elseif ( FishingBuddy_Info[realmName] and
	             FishingBuddy_Info[realmName]["Settings"] and
	             FishingBuddy_Info[realmName]["Settings"][what] ) then
	       tinsert(rnames, what);
	    else
	       FishingBuddy.Print(FishingBuddy.CLEANUP_NOOLDMSG, what);
	       return true;
	    end
	 end
	 if ( next(rnames) or next(onames) ) then
	    if ( next(rnames) ) then
	       for _,name in rnames do
		  for _,tab in tabs do
		     if ( FishingBuddy_Info[realmName] and FishingBuddy_Info[realmName][tab] ) then
			FishingBuddy_Info[realmName][tab][name] = nil;
			if ( next(FishingBuddy_Info[realmName][tab]) == nil ) then
			   FishingBuddy_Info[realmName][tab] = nil;
			end
		     end
		  end
	       end
	       if ( FishingBuddy_Info[realmName] and next(FishingBuddy_Info[realmName]) == nil ) then
		  FishingBuddy_Info[realmName] = nil;
	       end
	       FishingBuddy.Print(FishingBuddy.CLEANUP_DONEMSG, realmName,
				  FishingBuddy.EnglishList(rnames));
	    end
	    if ( next(onames) ) then
	       for _,name in onames do
		  for _,tab in tabs do
		     if ( FishingBuddy_Info[tab] ) then
			FishingBuddy_Info[tab][name] = nil;
			if ( next(FishingBuddy_Info[tab]) ) then
			   FishingBuddy_Info[tab] = nil;
			end
		     end
		  end
	       end
	       FishingBuddy.Print(FishingBuddy.CLEANUP_DONEMSG,
				  FishingBuddy.NOREALM,
				  FishingBuddy.EnglishList(onames));
	    end
	 else
	    FishingBuddy.Message(FishingBuddy.CLEANUP_NONEMSG);
	 end
	 return true;
      end
   end;

local function nextarg(msg, pattern)
   if ( not msg or not pattern ) then
      return nil, nil;
   end
   local s,e = string.find(msg, pattern);
   if ( s ) then
      local word = strsub(msg, s, e);
      msg = strsub(msg, e+1);
      return word, msg;
   end
   return nil, msg;
end

FishingBuddy.Command = function(msg)
   if ( not msg ) then
      return;
   end
   if ( FishingBuddy.IsLoaded() ) then
      -- collect arguments (whee, lua string manipulation)
      local cmd;
      cmd, msg = nextarg(msg, "[%w]+");

      -- the empty string gives us no args at all
      if ( not cmd ) then
	 -- toggle window
	 if ( FishingBuddyFrame:IsVisible() ) then
	    HideUIPanel(FishingBuddyFrame);
	 else
	    ShowUIPanel(FishingBuddyFrame);
	 end
      elseif ( cmd == FishingBuddy.HELP or cmd == "help" ) then
	 FishingBuddy.Output(FishingBuddy.WINDOW_TITLE);
	 FishingBuddy.PrintHelp(FishingBuddy.HELPMSG);
      else
	 local command = FishingBuddy.Commands[cmd];
	 if ( command ) then
	    local args = {};
	    local goodargs = true;
	    if ( command.args ) then
	       for _,pat in command.args do
		  local w, msg = nextarg(msg, pat);
		  if ( not w ) then
		     goodargs = false;
		     break;
		  end
		  tinsert(args, w);
	       end
	    else
	       local a;
	       while ( msg ) do
		  a, msg = nextarg(msg, "[%w]+");
		  if ( not a ) then
		     break;
		  end
		  tinsert(args, a);
	       end
	    end
	    if ( not goodargs or not command.func(unpack(args)) ) then
	       if ( command.help ) then
		  FishingBuddy.PrintHelp(command.help);
	       else
		  FishingBuddy.Debug("command failed");
	       end
	    end
	 else
	    FishingBuddy.Command("help");
	 end
      end
   else
      FishingBuddy.Error(FishingBuddy.FAILEDINIT);
   end
end

FishingBuddy.OnLoad = function()
   this:RegisterEvent("PLAYER_LOGIN");
   this:RegisterEvent("PLAYER_LOGOUT");
   this:RegisterEvent("VARIABLES_LOADED");

   this:RegisterEvent("ITEM_LOCK_CHANGED");
   this:RegisterEvent("LOOT_OPENED");
   this:RegisterEvent("ZONE_CHANGED");
   this:RegisterEvent("MINIMAP_ZONE_CHANGED");

   this:RegisterEvent("SPELLS_CHANGED");
   this:RegisterEvent("SPELLCAST_CHANNEL_START");
   this:RegisterEvent("SPELLCAST_CHANNEL_STOP");
   this:RegisterEvent("SPELLCAST_START");
   this:RegisterEvent("SPELLCAST_STOP");
   this:RegisterEvent("SPELLCAST_INTERRUPTED");
   this:RegisterEvent("SPELLCAST_FAILED");

   this:RegisterEvent("SKILL_LINES_CHANGED");

   -- Set up command
   SlashCmdList["fishingbuddy"] = FishingBuddy.Command;
   SLASH_fishingbuddy1 = "/fishingbuddy";
   SLASH_fishingbuddy2 = "/fb";

   FishingBuddy.Output(FishingBuddy.WINDOW_TITLE.." loaded");
end

FishingBuddy.OnEvent = function()
   local needUpdate = false;
   if ( event == "VARIABLES_LOADED" ) then
      FishingBuddy.Initialize();
   elseif ( event == "PLAYER_LOGIN" ) then
      -- set up outfit stuff
      FishingBuddy.OutfitManager.Initialize();
      FishingMode();
   elseif ( event == "PLAYER_LOGOUT" ) then
      -- reset the fishing sounds, if we need to
      StopFishingMode();
      FishingBuddy.SavePlayerInfo();
   elseif ( event == "ITEM_LOCK_CHANGED" ) then
      FishingMode();
   elseif ( event == "LOOT_OPENED" ) then
      if ( IsFishingLoot()) then
	 local zone, subzone = FishingBuddy.GetZoneInfo();
	 for index = 1, GetNumLootItems(), 1 do
	    if (LootSlotIsItem(index)) then
	       local texture, fishie, quantity, quality = GetLootSlotInfo(index);
	       local link = GetLootSlotLink(index);
	       local color, id, name = FishingBuddy.SplitFishLink(link);
	       FishingBuddy.AddFishie(color, id, name, zone, subzone, texture, quantity, quality);
	       if ( FishingBuddy.AddTracking ) then
		  FishingBuddy.AddTracking(id, name);
	       end
	       FishingBuddy.SetZoneLevel(zone, subzone, id);
	    end
	 end
      end
   elseif ( event == "ZONE_CHANGED" or event == "MINIMAP_ZONE_CHANGED" ) then
      if ( not FishingBuddy.IsLoaded() ) then
	 return;
      end
      FishingBuddy.currentFishies = {};
      needUpdate = true;
   elseif ( event == "SKILL_LINES_CHANGED" ) then
      if ( GetSetting("WatchCurrentSkill") == 1 ) then
	 needUpdate = true;
      end
   elseif ( event == "SPELLS_CHANGED" ) then
      -- Fishing might have moved, go look again
      FishingSpellID = nil;
   else
      -- Mugendai is one sharp cookie (shamelessly stealing from TackleBox again)
      -- Keep up with whether or not we are casting
      if ( event == "SPELLCAST_CHANNEL_START" ) then
	 CastingNow = true;
      elseif ( event == "SPELLCAST_CHANNEL_STOP" ) then
	 CastingNow = false;
      else
	 -- Keep up with whether we are adding a lure (or casting a normal spell) or not
	 if ( event == "SPELLCAST_START" ) then
	    -- AddingLure = true;
	 elseif ( event == "SPELLCAST_STOP" ) then
	    if ( AddingLure ) then
	       AddingLure = false;
	       -- update the skill line if we have one
	       if ( GetSetting("WatchCurrentSkill") == 1 ) then
		  needUpdate = true;
	       end
	    end
	 elseif ( ( event == "SPELLCAST_INTERRUPTED" ) or ( event == "SPELLCAST_FAILED" ) ) then
	    AddingLure = false;
	 end
      end
   end
   FishingBuddy.Extravaganza.IsTime(true);
   if ( needUpdate ) then
      FishingBuddy.WatchUpdate();
   end
end

FishingBuddy.PrintHelp = function(tab)
   if ( type(tab) == "table" ) then
      for _,line in tab do
	 FishingBuddy.PrintHelp(line);
      end
   else
      FishingBuddy.Output(tab);
   end
end

FishingBuddy["EFSV"] = {};
FishingBuddy["EFSV"]["MusicVolume"] = tonumber(GetCVar("MusicVolume"));
FishingBuddy["EFSV"]["AmbienceVolume"] = tonumber(GetCVar("AmbienceVolume"));
FishingBuddy["EFSV"]["SoundVolume"] =tonumber(GetCVar("SoundVolume"));
local SoundWasEnhanced;
FishingBuddy.EnhanceFishingSounds = function(turniton)
   if ( GetSetting("EnhanceFishingSounds") == 1 ) then
      if ( turniton ) then
         -- collect the current value
         FishingBuddy["EFSV"]["MusicVolume"] = tonumber(GetCVar("MusicVolume"));
         FishingBuddy["EFSV"]["AmbienceVolume"] = tonumber(GetCVar("AmbienceVolume"));
         FishingBuddy["EFSV"]["SoundVolume"] =tonumber(GetCVar("SoundVolume"));
	 -- turn 'em off!
	 for setting in FishingBuddy["EFSV"] do
	    SetCVar(setting,
		    GetSetting("EnhanceSound"..setting));
	 end
	 SoundWasEnhanced = true;
      else
	 if ( SoundWasEnhanced ) then
	    for setting, value in FishingBuddy["EFSV"] do
	       SetCVar(setting, value);
	    end
	    SoundWasEnhanced = false;
	 end
      end
   end
end

-- Drop-down menu support
FishingBuddy.ToggleSetting = function(setting)
   local value = GetSetting(setting);
   if ( not value ) then
      value = 0;
   end
   SetSetting(setting, 1 - value);
   FishingBuddy.WatchUpdate();
   FishingBuddy.UpdateMinimap();
end

-- save some memory by keeping one copy of each one
local ToggleFunctions = {};
-- let's use closures
local function MakeToggle(name)
   if ( not ToggleFunctions[name] ) then
      local n = name;
      ToggleFunctions[name] = function() FishingBuddy.ToggleSetting(n) end;
   end
   return ToggleFunctions[name];
end

FishingBuddy.MakeDropDown = function(switchItem, switchSetting)
   local info;
   -- If no outfit frame, we can't switch outfits...
   if ( FishingBuddy.OutfitManager.HasManager() ) then
      info = {};
      info.text = switchItem;
      info.func = MakeToggle(switchSetting);
      info.checked = (GetSetting(switchSetting) == 1);
      info.keepShownOnClick = 1;
      UIDropDownMenu_AddButton(info);
	  info = {};
	  info.disabled = 1;
	  UIDropDownMenu_AddButton(info);
   end

   for _, option in FishingBuddy.OPTIONS do
      local addthis = true;
      if ( option.check ) then
	 addthis = option.check();
      end
      if ( addthis ) then
	 info = {};
	 info.text = option.text;
	 info.func = MakeToggle(option.name);
	 info.checked = (GetSetting(option.name) == 1);
	 info.keepShownOnClick = 1;
	 UIDropDownMenu_AddButton(info);
      end
   end
end

-- utility functions
FishingBuddy.AddTooltip = function(text, r, g, b)
   if ( text ) then
      if ( type(text) == "table" ) then
	 for _,l in text do
	    FishingBuddy.AddTooltip(l.text, l.r, l.g, l.b);
	 end
      else
	 GameTooltip:AddLine(text, r, g, b);
      end
   end
end

FishingBuddy.ChatLink = function(item, name, color)
   if( item and name and ChatFrameEditBox:IsVisible() ) then
      if not color then
	 color = FishingBuddy.Colors.WHITE;
      end
      local link = "|c"..color.."|Hitem:"..item.."|h["..name.."]|h|r";
      ChatFrameEditBox:Insert(link);
   end
end

FishingBuddy.FishSort = function(tab, forcename)
   if ( forcename or GetSetting("SortByPercent") == 0 ) then
      table.sort(tab, function(a,b) return a.text and b.text and a.text<b.text; end);
   else
      table.sort(tab, function(a,b) return a.count and b.count and b.count<a.count; end);
   end
end

FishingBuddy.EnglishList = function(list, conjunction)
   if ( list ) then
      local n = table.getn(list);
      local str = "";
      for idx=1,n do
	 local name = list[idx];
	 if ( idx == 1 ) then
	    str = name;
	 elseif ( idx == n ) then
	    str = str .. ", ";
	    if ( conjunction ) then
	       str = str .. conjunction;
	    else
	       str = str .. "and";
	    end
	       str = str .. " " .. name;
	 else
	    str = str .. ", " .. name;
	 end
      end
      return str;
   end
end

FishingBuddy.Message = function(msg, r, g, b)
   FishingBuddy.Output(FishingBuddy.NAME..": "..msg, r, g, b);
end

FishingBuddy.Print = function(...)
   local msg = string.format(unpack(arg));
   FishingBuddy.Message(msg);
end

FishingBuddy.Error = function(msg)
   FishingBuddy.Output(FishingBuddy.NAME..": "..msg, 1.0, 0, 0);
end

FishingBuddy.UIError = function(msg)
   -- Okay, this check is probably not necessary...
   if ( UIErrorsFrame ) then
      UIErrorsFrame:AddMessage(msg, 1.0, 0.1, 0.1, 1.0, UIERRORS_HOLD_TIME);
   else
      FishingBuddy.Error(msg);
   end
end

FishingBuddy.Testing = function(line)
   if ( not FishingBuddy_Info["Testing"] ) then
      FishingBuddy_Info["Testing"] = {};
   end
   tinsert(FishingBuddy_Info["Testing"], line);
end
