--[[
Name: LibFishing-1.0
Maintainers: Sutorix <sutorix@hotmail.com>
Description: A library with fishing support routines used by Fishing Buddy, Fishing Ace and FB_Broker.
Copyright (c) by Bob Schumaker
Licensed under a Creative Commons "Attribution Non-Commercial Share Alike" License
--]]
--[[Modded and name altered for purpose of S&L's profession module.]]

local MAJOR_VERSION = "LibFishing-1.0-SLE"
local MINOR_VERSION = 101074
-- 5.0.4 has a problem with a global "_" (see some for loops below)
local _

if not LibStub then error(MAJOR_VERSION .. " requires LibStub") end

local FishLib, lastVersion = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION)
if not FishLib then
	return
end

FishLib.UNKNOWN = "UNKNOWN";
FishLib.caughtSoFar = 0;
FishLib.gearcheck = true;
FishLib.hasgear = false;

local PROFESSIONS_FISHING, INVSLOT_MAINHAND = PROFESSIONS_FISHING, INVSLOT_MAINHAND
local ipairs = ipairs
local GetInventoryItemLink = GetInventoryItemLink
local GetInventoryItemID = GetInventoryItemID
local GetWeaponEnchantInfo = GetWeaponEnchantInfo
local UnitBuff = UnitBuff
local C_Container_GetItemCooldown = C_Container.GetItemCooldown
local GetProfessions, GetProfessionInfo = GetProfessions, GetProfessionInfo
local GetItemInfo = GetItemInfo
local GetSpellLink, GetSpellInfo = GetSpellLink, GetSpellInfo
local GetActionTexture = GetActionTexture

local LureEffects = {
	[263] = 25,
	[264] = 50,
	[265] = 75,
	[266] = 100,
	[3868] = 100,
	[4225] = 150,
	[5386] = 200,
}

function FishLib:GetFishingSkillInfo()
	local _, _, _, fishing, _, _ = GetProfessions();
	if not fishing then
		return 131474, PROFESSIONS_FISHING
	end
	local name, _, _, _, count, offset, _ = GetProfessionInfo(fishing);
	local id = nil;
	for i = 1, count do
		local _, spellId = GetSpellLink(offset + i, "spell");
		local spellName = GetSpellInfo(spellId);
		if (spellName == name) then
			id = spellId;
			break;
		end
	end
	return id, name
end

-- get our current fishing skill level
function FishLib:GetCurrentSkill()
	local _, _, _, fishing, _, _ = GetProfessions();
	if (fishing) then
		local name, _, rank, skillmax, _, _, _, mods = GetProfessionInfo(fishing);
		local lure = self:GetPoleBonus();
		return rank, mods, skillmax, lure;
	end
	return 0, 0, 0;
end

-- Lure library
local FISHINGLURES = {
	{	["id"] = 88710,
		["n"] = "Nat's Hat",						-- 150 for 10 mins
		["b"] = 150,
		["s"] = 100,
		["d"] = 10,
		["w"] = true,
	},
	{	["id"] = 117405,
		["n"] = "Nat's Drinking Hat",				-- 150 for 10 mins
		["b"] = 150,
		["s"] = 100,
		["d"] = 10,
		["w"] = true,
	},
	{	["id"] = 33820,
		["n"] = "Weather-Beaten Fishing Hat",		 -- 75 for 10 minutes
		["b"] = 75,
		["s"] = 1,
		["d"] = 10,
		["w"] = true,
	},
	{	["id"] = 116826,
		["n"] = "Draenic Fishing Pole",				 -- 200 for 10 minutes
		["b"] = 200,
		["s"] = 1,
		["d"] = 20,									 -- 20 minute cooldown
		["w"] = true,
	},
	{	["id"] = 116825,
		["n"] = "Savage Fishing Pole",				 -- 200 for 10 minutes
		["b"] = 200,
		["s"] = 1,
		["d"] = 20,									 -- 20 minute cooldown
		["w"] = true,
	},

	{	["id"] = 34832,
		["n"] = "Captain Rumsey's Lager",			     -- 10 for 3 mins
		["b"] = 10,
		["s"] = 1,
		["d"] = 3,
		["u"] = 1,
	},
	{	["id"] = 67404,
		["n"] = "Glass Fishing Bobber",				-- ???
		["b"] = 15,
		["s"] = 1,
		["d"] = 10,
	},
	{	["id"] = 6529,
		["n"] = "Shiny Bauble",							  -- 25 for 10 mins
		["b"] = 25,
		["s"] = 1,
		["d"] = 10,
	},
	{	["id"] = 6811,
		["n"] = "Aquadynamic Fish Lens",				  -- 50 for 10 mins
		["b"] = 50,
		["s"] = 50,
		["d"] = 10,
	},
	{	["id"] = 6530,
		["n"] = "Nightcrawlers",						  -- 50 for 10 mins
		["b"] = 50,
		["s"] = 50,
		["d"] = 10,
	},
	{	["id"] = 7307,
		["n"] = "Flesh Eating Worm",					  -- 75 for 10 mins
		["b"] = 75,
		["s"] = 100,
		["d"] = 10,
	},
	{	["id"] = 6532,
		["n"] = "Bright Baubles",						  -- 75 for 10 mins
		["b"] = 75,
		["s"] = 100,
		["d"] = 10,
	},
	{	["id"] = 34861,
		["n"] = "Sharpened Fish Hook",				  -- 100 for 10 minutes
		["b"] = 100,
		["s"] = 100,
		["d"] = 10,
	},
	{	["id"] = 6533,
		["n"] = "Aquadynamic Fish Attractor",		  -- 100 for 10 minutes
		["b"] = 100,
		["s"] = 100,
		["d"] = 10,
	},
	{	["id"] = 62673,
		["n"] = "Feathered Lure",					  -- 100 for 10 minutes
		["b"] = 100,
		["s"] = 100,
		["d"] = 10,
	},
	{	["id"] = 46006,
		["n"] = "Glow Worm",						-- 100 for 60 minutes
		["b"] = 100,
		["s"] = 100,
		["d"] = 60,
		["l"] = 1,
	},
	{	["id"] = 68049,
		["n"] = "Heat-Treated Spinning Lure",		 -- 150 for 5 minutes
		["b"] = 150,
		["s"] = 250,
		["d"] = 5,
	},
	{	["id"] = 118391,
		["n"] = "Worm Supreme",						-- 200 for 10 mins
		["b"] = 200,
		["s"] = 100,
		["d"] = 10,
	},
}

-- sort ascending bonus and ascending time
-- we may have to treat "Heat-Treated Spinning Lure" differently someday
table.sort(FISHINGLURES,
	function(a,b)
		if ( a.b == b.b ) then
			return a.d < b.d;
		else
			return a.b < b.b;
		end
	end);

function FishLib:GetLureTable()
	return FISHINGLURES;
end

function FishLib:IsWorn(itemid)
	for slot=1,19 do
		local link = GetInventoryItemLink("player", slot);
		if ( link ) then
			local _, id, _ = self:SplitFishLink(link);
			if ( itemid == id ) then
				return true;
			end
		end
	end
	-- return nil
end

local useinventory = {};
local lureinventory = {};
function FishLib:UpdateLureInventory()
	local rawskill, _, _, _ = self:GetCurrentSkill();

	useinventory = {};
	lureinventory = {};
	local b = 0;
	for _,lure in ipairs(FISHINGLURES) do
		local id = lure.id;
		local count = GetItemCount(id);
		-- does this lure have to be "worn"
		if ( count > 0 ) then
			local startTime, _, _ = C_Container_GetItemCooldown(id);
			if (startTime == 0) then
				-- get the name so we can check enchants
				lure.n,_,_,_,_,_,_,_,_,_ = GetItemInfo(id);
				if ( lure.b > b or lure.w ) then
					b = lure.b;
					if ( lure.u ) then
						tinsert(useinventory, lure);
					elseif ( lure.s <= rawskill ) then
						if ( not lure.w or FishLib:IsWorn(id)) then
							tinsert(lureinventory, lure);
						end
					end
				end
			end
		end
	end
	return lureinventory, useinventory;
 end

function FishLib:GetLureInventory()
	return lureinventory, useinventory;
end

-- Deal with lures
function FishLib:HasBuff(buffName)
	if ( buffName ) then
		 local name = UnitBuff("player", buffName);
		 return name ~= nil;
	end
	-- return nil
end

local function UseThisLure(lure, b, enchant, skill, level)
	if ( lure ) then
		local startTime, _, _ = C_Container_GetItemCooldown(lure.id);
		level = level or 0;
		local bonus = lure.b or 0;
		if ( startTime == 0 and (skill and level <= (skill + bonus)) and (bonus > enchant) ) then
			if ( not b or bonus > b ) then
				return true, bonus;
			end
		end
		return false, bonus;
	end
	return false, 0;
end

function FishLib:FindBestLure(b, state, usedrinks)
		local rank, modifier, _, enchant = self:GetCurrentSkill();
		local skill = rank + modifier;

		if ( skill ) then
			self:UpdateLureInventory();
			-- if drinking will work, then we're done
			if ( usedrinks and #useinventory > 0 ) then
				if ( not LastUsed or not self:HasBuff(LastUsed.n) ) then
					local id = useinventory[1].id;
					if ( not self:HasBuff(useinventory[1].n) ) then
						return nil, useinventory[1];
					end
				end
			end

			skill = skill - enchant;
			state = state or 0;
			local checklure;
			local useit, b = 0;

			-- Look for lures we're wearing, first
			for s=state+1,#lureinventory,1 do
				checklure = lureinventory[s];
				if (checklure.w) then
					useit, b = UseThisLure(checklure, b, enchant, skill);
					if ( useit and b and b > 0 ) then
						return s, checklure;
					end
				end
			end

			b = 0;
			for s=state+1,#lureinventory,1 do
				checklure = lureinventory[s];
				useit, b = UseThisLure(checklure, b, enchant, skill);
				if ( useit and b and b > 0 ) then
					return s, checklure;
				end
			end

			-- if we ran off the end of the table and we had a valid lure, let's use that one
			if ( (not enchant or enchant == 0) and b and (b > 0) and checklure ) then
				return #lureinventory, checklure;
			end
		end
end

-- Handle events we care about
local canCreateFrame = false;

local FISHLIBFRAMENAME="FishLibFrame";
local fishlibframe = getglobal(FISHLIBFRAMENAME);
if ( not fishlibframe) then
	fishlibframe = CreateFrame("Frame", FISHLIBFRAMENAME);
	fishlibframe:RegisterEvent("PLAYER_ENTERING_WORLD");
	fishlibframe:RegisterEvent("PLAYER_LEAVING_WORLD");
	fishlibframe:RegisterEvent("UPDATE_CHAT_WINDOWS");
	fishlibframe:RegisterEvent("LOOT_OPENED");
	fishlibframe:RegisterEvent("CHAT_MSG_SKILL");
	fishlibframe:RegisterEvent("SKILL_LINES_CHANGED");
	fishlibframe:RegisterEvent("UNIT_INVENTORY_CHANGED");
	fishlibframe:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START");
	fishlibframe:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP");
	fishlibframe:RegisterEvent("EQUIPMENT_SWAP_FINISHED");
	fishlibframe:RegisterEvent("ITEM_LOCK_CHANGED");
end

fishlibframe.fl = FishLib;

fishlibframe:SetScript("OnEvent", function(self, event, ...)
	local arg1 = select(1, ...);
	if ( event == "UPDATE_CHAT_WINDOWS" ) then
		canCreateFrame = true;
		self:UnregisterEvent(event);
	elseif ( event == "UNIT_INVENTORY_CHANGED" and arg1 == "player" ) then
		self.fl:UpdateLureInventory();
		-- we can't actually rely on EQUIPMENT_SWAP_FINISHED, it appears
		self.fl:ForceGearCheck();
	elseif ( event == "SKILL_LINES_CHANGED" or event == "ITEM_LOCK_CHANGED" or event == "EQUIPMENT_SWAP_FINISHED" ) then
		-- Did something we're wearing change?
		self.fl:ForceGearCheck();
	elseif ( event == "CHAT_MSG_SKILL" ) then
		self.fl.caughtSoFar = 0;
	elseif ( event == "LOOT_OPENED" ) then
		if (IsFishingLoot()) then
			self.fl.caughtSoFar = self.fl.caughtSoFar + 1;
		end
	elseif ( event == "UNIT_SPELLCAST_CHANNEL_START" or event == "UNIT_SPELLCAST_CHANNEL_STOP" ) then
		if (arg1 == "player" ) then
			self.fl:UpdateLureInventory();
		end
	elseif ( event == "PLAYER_ENTERING_WORLD" ) then
		self:RegisterEvent("ITEM_LOCK_CHANGED")
		self:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
		self:RegisterEvent("SPELLS_CHANGED")
	elseif ( event == "PLAYER_LEAVING_WORLD" ) then
		self:UnregisterEvent("ITEM_LOCK_CHANGED")
		self:UnregisterEvent("PLAYER_EQUIPMENT_CHANGED")
		self:UnregisterEvent("SPELLS_CHANGED")
	end
end);
fishlibframe:Show();

-- this changes all the damn time
-- "|c(%x+)|Hitem:(%d+)(:%d+):%d+:%d+:%d+:%d+:[-]?%d+:[-]?%d+:[-]?%d+:[-]?%d+|h%[(.*)%]|h|r"
-- go with a fixed pattern, since sometimes the hyperlink trick appears not to work
local _itempattern = "|c(%x+)|Hitem:([^:]+):([^:]+)[-:%d]+|h%[(.*)%]|h|r"

function FishLib:GetItemPattern()
	if ( not _itempattern ) then
		-- This should work all the time
		self:GetPoleType(); -- force the default pole into the cache
		local _, pat, _, _, _, _, _, _ = GetItemInfo(6256);
		pat = string.gsub(pat, "|c(%x+)|Hitem:(%d+)(:%d+)", "|c(%%x+)|Hitem:(%%d+)(:%%d+)");
		pat = string.gsub(pat, ":[-]?%d+", ":[-]?%%d+");
		_itempattern = string.gsub(pat, "|h%[(.*)%]|h|r", "|h%%[(.*)%%]|h|r");
	end
	return _itempattern;
end

function FishLib:SplitFishLink(link)
	if ( link ) then
		local _,_, color, id, enchant, name = string.find(link, self:GetItemPattern());
		return color, tonumber(id), name, enchant;
	end
end

function FishLib:GetItemInfo(link)
-- name, link, rarity, itemlevel, minlevel, itemtype
-- subtype, stackcount, equiploc, texture
	local itemName, itemLink, itemRarity, itemLevel, itemMinLevel, itemType, itemSubType, itemStackCount, itemEquipLoc, itemTexture, itemSellPrice = GetItemInfo(link);
	return itemName, itemLink, itemRarity, itemMinLevel, itemType, itemSubType, itemStackCount, itemEquipLoc, itemTexture, itemLevel, itemSellPrice;
end

local fp_itemtype = nil;
local fp_subtype = nil;

function FishLib:GetPoleType()
	if ( not fp_itemtype ) then
		_,_,_,_,fp_itemtype,fp_subtype = self:GetItemInfo(6256);
		if ( not fp_itemtype ) then
			C_Timer.After(3, function() FishLib:GetPoleType() end)
			return
		end
	end
	return fp_itemtype, fp_subtype;
end

local UNDERLIGHT_ANGLER_ITEM_ID = 133755
function FishLib:IsFishingPole(itemLink)
	if (not itemLink) then
		itemLink = GetInventoryItemLink("player", INVSLOT_MAINHAND);
	end
	if ( itemLink ) then
		if (GetInventoryItemID("player", INVSLOT_MAINHAND) == UNDERLIGHT_ANGLER_ITEM_ID) then
			return true;
		end
		local _,_,_,_,itemtype,subtype = self:GetItemInfo(itemLink);
		if ( fp_itemtype and fp_subtype ) then
			return (itemtype == fp_itemtype) and (subtype == fp_subtype);
		end
	end
	return false;
end

function FishLib:ForceGearCheck()
	self.gearcheck = true;
	self.hasgear = false;
end

function FishLib:IsFishingReady(partial)
	if ( partial ) then
		return true
	else
		return self:IsFishingPole();
	end
end

local ACTIONDOUBLEWAIT = 0.4;
local MINACTIONDOUBLECLICK = 0.05;

function FishLib:WatchBobber(flag)
	self.watchBobber = flag;
end

-- look for double clicks
function FishLib:CheckForDoubleClick(button)
	if (button and button ~= self:GetSAMouseButton()) then
		return false;
	end
	if ( not LootFrame:IsShown() and self.lastClickTime ) then
		local pressTime = GetTime();
		local doubleTime = pressTime - self.lastClickTime;
		if ( (doubleTime < ACTIONDOUBLEWAIT) and (doubleTime > MINACTIONDOUBLECLICK) ) then
			if ( not self.watchBobber ) then
				self.lastClickTime = nil;
				return true;
			end
		end
	end
	self.lastClickTime = GetTime();

	return false;
end

-- Find an action bar for fishing, if there is one
local FISHINGTEXTURE = 136245;
function FishLib:GetFishingActionBarID(force)
	if ( force or not self.ActionBarID ) then
		for slot=1,72 do
			local tex = GetActionTexture(slot);
			if ( tex and tex == FISHINGTEXTURE ) then
				self.ActionBarID = slot;
				break;
			end
		end
	end
	return self.ActionBarID;
end

-- Secure action button
local SABUTTONNAME = "LibFishingSAButton";

function FishLib:ResetOverride()
	local btn = self.sabutton;
	if ( btn ) then
		btn.holder:Hide();
		ClearOverrideBindings(btn);
	end
end

local function ClickHandled(self)
	self.fl:ResetOverride();
	if ( self.postclick ) then
		self.postclick();
	end
end

function FishLib:CreateSAButton()
	local btn = getglobal(SABUTTONNAME);
	if ( not btn ) then
		local holder = CreateFrame("Frame", nil, UIParent);
		btn = CreateFrame("Button", SABUTTONNAME, holder, "SecureActionButtonTemplate");
		btn.holder = holder;
		btn:EnableMouse(true);
		btn:RegisterForClicks("RightButtonUp", "RightButtonDown");
		btn:Show();

		holder:SetPoint("LEFT", UIParent, "RIGHT", 10000, 0);
		holder:SetFrameStrata("LOW");
		holder:Hide();
	end
	-- if (not self.buttonevent) then
		-- self.buttonevent = "RightButtonUp";
	-- end
	btn:SetScript("PostClick", ClickHandled);
	btn:RegisterForClicks("RightButtonUp", "RightButtonDown");
	self.sabutton = btn;
	btn.fl = self;
end

FishLib.MOUSE1 = "RightButtonUp";
FishLib.MOUSE2 = "Button4Up";
FishLib.MOUSE3 = "Button5Up";
FishLib.CastButton = {};
FishLib.CastButton[FishLib.MOUSE1] = "RightButton";
FishLib.CastButton[FishLib.MOUSE2] = "Button4";
FishLib.CastButton[FishLib.MOUSE3] = "Button5";
FishLib.CastKey = {};
FishLib.CastKey[FishLib.MOUSE1] = "BUTTON2";
FishLib.CastKey[FishLib.MOUSE2] = "BUTTON4";
FishLib.CastKey[FishLib.MOUSE3] = "BUTTON5";

function FishLib:GetSAMouseEvent()
	if (not self.buttonevent) then
		self.buttonevent = "RightButtonUp";
	end
	return self.buttonevent;
end

function FishLib:GetSAMouseButton()
	return self.CastButton[self:GetSAMouseEvent()];
end

function FishLib:GetSAMouseKey()
	return self.CastKey[self:GetSAMouseEvent()];
end

function FishLib:SetSAMouseEvent(buttonevent)
	-- if (not buttonevent) then
		-- buttonevent = "RightButtonUp";
	-- end
	-- if (self.CastButton[buttonevent]) then
		-- self.buttonevent = buttonevent;
		local btn = getglobal(SABUTTONNAME);
		if ( btn ) then
			-- btn:RegisterForClicks(nil);
			-- btn:RegisterForClicks(self.buttonevent);
			btn:RegisterForClicks("RightButtonUp", "RightButtonDown");
		end
		return true;
	-- end
	-- return nil;
end

function FishLib:InvokeFishing(useaction)
	local btn = self.sabutton;
	if ( not btn ) then
		return;
	end
	local id, name = self:GetFishingSkillInfo();
	local findid = self:GetFishingActionBarID();
	if ( not useaction or not findid ) then
		btn:SetAttribute("type", "spell");
		btn:SetAttribute("spell", id);
		btn:SetAttribute("action", nil);
	else
		btn:SetAttribute("type", "action");
		btn:SetAttribute("action", findid);
		btn:SetAttribute("spell", nil);
	end
	btn:SetAttribute("item", nil);
	btn:SetAttribute("target-slot", nil);
	btn.postclick = nil;
end

function FishLib:InvokeLuring(id)
	local btn = self.sabutton;
	if ( not btn ) then
		return;
	end
	btn:SetAttribute("type", "item");
	if ( id ) then
		btn:SetAttribute("item", "item:"..id);
		btn:SetAttribute("target-slot", INVSLOT_MAINHAND);
	else
		btn:SetAttribute("item", nil);
		btn:SetAttribute("target-slot", nil);
	end
	btn:SetAttribute("spell", nil);
	btn:SetAttribute("action", nil);
	btn.postclick = nil;
end

function FishLib:OverrideClick(postclick)
	local btn = self.sabutton;
	if ( not btn ) then
		return;
	end
	local buttonkey = self:GetSAMouseKey();
	fishlibframe.fl = self;
	btn.fl = self;
	btn.postclick = postclick;
	SetOverrideBindingClick(btn, true, buttonkey, SABUTTONNAME);
	btn.holder:Show();
end

function FishLib:ClickSAButton()
	local btn = self.sabutton;
	if ( not btn ) then
		return;
	end
	btn:Click(self:GetSAMouseButton());
end

-- if we have a fishing pole, return the bonus from the pole
-- and the bonus from a lure, if any, separately
function FishLib:GetPoleBonus()
	if (self:IsFishingPole()) then
		local mhEnch,_,_,mhID = GetWeaponEnchantInfo();
		if mhEnch and LureEffects[mhID] then
			return LureEffects[mhID]
		else
			return 0
		end
	end
	return 0
end