Quantcast
-- $Revision: 212 $
-- Cauldron main file

UIPanelWindows["CauldronQueueWindowFrame"] = { area = "left", pushable = 6 };
UIPanelWindows["CauldronFrame"] = { area = "left", pushable = 3 };

Cauldron = LibStub("AceAddon-3.0"):NewAddon("Cauldron", "AceEvent-3.0", "AceTimer-3.0", "AceConsole-3.0", "AceHook-3.0", "LibLogger-1.0");
local L = LibStub("AceLocale-3.0"):GetLocale("Cauldron");

Cauldron.version = "@project-revision@";
Cauldron.date = string.sub("$Date: 2010-08-27 21:03:36 -0700 (Fri, 27 Aug 2010) $", 8, 17);

-- key binding names
BINDING_HEADER_CAULDRON = "Cauldron";
BINDING_NAME_TOGGLE_CAULDRONSHOPPINGLIST = "Toggle Shopping List Window";
BINDING_NAME_TOGGLE_CAULDRONQUEUE = "Toggle Queue";
BINDING_NAME_CAULDRONRESET = "Reset Cauldron";

Cauldron.options = {
	buttons = {},
};

Cauldron.vars = {
	enabled = true,
	showQueue = true,
	inventory = {},
	filterVersion = tonumber("@project-revision@") or 300,
	displayTimers = false,
};

Cauldron.libs = {};
-- Cauldron.libs.Abacus = LibStub("LibAbacus-3.0");
-- Cauldron.libs.PT = LibStub("LibPeriodicTable-3.1");
Cauldron.libs.GUI = LibStub("AceGUI-3.0");
Cauldron.libs.AceConfigDialog = LibStub("AceConfigDialog-3.0");

-- logging
Cauldron:SetLogLevel(Cauldron.logLevels.INFO);
-- Cauldron:SetLogLevel(Cauldron.logLevels.DEBUG);

if not CauldronLocalDB then
	CauldronLocalDB = {
		recipes = {},
		window = {},
	};
end

CURRENT_TRADESKILL = "";

local function GetProfileOption(info)

	if not Cauldron.db.global.options then
		Cauldron.db.global.options = {};
	end

	return Cauldron.db.global.options[info.arg];
end

local function SetProfileOption(info, value)

	if not Cauldron.db.global.options then
		Cauldron.db.global.options = {};
	end

	Cauldron.db.global.options[info.arg] = value;

end

function Cauldron:OnInitialize()
	local globalDbDefaults = {
		profile = {
		},
		realm = {
			userdata = {}, -- Stores all known characters
		},
		global = {
			options = {
				AutoOpenShoppingList = true,
				ModifyTooltip = true,
			},
		}
	};

	self.db = LibStub("AceDB-3.0"):New("CauldronDB", globalDbDefaults);
--	self.localDb = LibStub("AceDB-3.0"):New("CauldronLocalDB", localDbDefaults);

	-- set up slash command options
	local options = {
		desc = L["Cauldron"],
		handler = Cauldron,
		type = 'group',
		args = {
			general = {
				type = 'group',
				cmdInline = true,
				order = -1,
				get = GetProfileOption,
				set = SetProfileOption,
				name = L["General"],
				args = {
					autoOpenShoppingList = {
						type = 'toggle',
						order = 1,
						width = "double",
						name = L["Auto-open shopping list?"],
						desc = L["Automatically open the shopping list when the bank, guild bank, or a merchant window is opened."],
						arg = "AutoOpenShoppingList",
					},
					modifyTooltip = {
						type = 'toggle',
						order = 1,
						width = "double",
						name = L["Modify tooltip?"],
						desc = L["Adds information to the game tooltip when displaying information about certain crafting items."],
						arg = "ModifyTooltip",
					},
					--[[
					enableLilSparkysWorkshop = {
						type = 'toggle',
						order = 1,
						width = "double",
						name = L["Enable support for LilSparky's Workshop?"],
						desc = L["Registers Cauldron with LilSparky's Workshop so that pricing information will show up in the recipe list."],
						arg = "EnableLilSparkysWorkshop",
					},
					--]]
				},
			},
			shoppinglist = {
				name = L["Shopping list"],
				desc = L["Open shopping list window"],
				type = 'execute',
				func = function() Cauldron:ShowShoppingList() end,
			},
			enable = {
				name = L["Enable Cauldron"],
				desc = L["Use Cauldron as your tradeskill interface"],
				type = 'execute',
				func = function() Cauldron:Enable() end,
			},
			disable = {
				name = L["Disable Cauldron"],
				desc = L["Use the standard Blizzard window as your tradeskill interface"],
				type = 'execute',
				func = function() Cauldron:Disable() end,
			},
			version = {
				name = L["Version"],
				desc = L["Shows the version number of the addon"],
				type = 'execute',
				func = function() self:DisplayVersion() end,
			},
			--@alpha@
			debug = {
				name = L["Debug"],
				desc = L["Toggles whether Cauldron displays debug messages"],
				type = 'toggle',
				get = function() return Cauldron:GetLogLevel(); end,
				set = function(val)
						self:debug("val: "..tostring(val));
						if val == "debug" then
							Cauldron:SetLogLevel(Cauldron.logLevels.DEBUG);
						else
							Cauldron:SetLogLevel(Cauldron.logLevels.INFO);
						end
					end,
			},
			--@end-alpha@
			--[[
			forget = {
				name = L["Forget"],
				desc = L["Tells Cauldron to forget information for a character, recipe, or skill"],
				type = 'input',
--				get = function() return; end,
				set = function(info, v)
						Cauldron:Forget(v);
					end,
				usage = L["forget [skill <name>||recipe <name>]"],
				validate = function(val)
					end,
				confirm = L["Forget skills for this character?"],
--				func = function() self:Forget(arg1) end,
			},
			--]]
			reset = {
				name = L["Reset"],
				desc = L["Resets Cauldron to a fresh state"],
				type = 'execute',
				func = function() self:Reset() end,
			},
--			debug = LibStub('LibLogDebug-1.0'):GetAce3OptionTable(self, 110),
		},
	}

	-- register slash command with options
	LibStub("AceConfig-3.0"):RegisterOptionsTable("Cauldron", options, {"cauldron"});

	--[[ initialize PT
	for i=1,GetNumAddOns() do
		local metadata = GetAddOnMetadata(i, "X-PeriodicTable-3.0-Module");
		if metadata then
			local name, _, _, enabled = GetAddOnInfo(i);
			if enabled then
				LoadAddOn(name);
			end
		end
	end
	collectgarbage();
	--]]

	local config = Cauldron.libs.AceConfigDialog;

	-- add config UI to Blizzard interface
	self.optionsFrames = {};
	-- The ordering here matters, it determines the order in the Blizzard Interface Options
	self.optionsFrames.general = config:AddToBlizOptions("Cauldron", L["Cauldron"], nil, "general");
--	self.optionsFrames.profile = config:AddToBlizOptions("Cauldron", L["Profiles"], L["Cauldron"], "profile");


	--@alpha@
	-- register test suite
	if WoWUnit then
		WoWUnit:AddTestSuite("CauldronTestSuite", CauldronTestSuite);
	end
	--@end-alpha@

	-- let the user know the addon is loaded
	self:Print(L["Cauldron loaded; version"],Cauldron.version);
end

function Cauldron:InitPlayer()

	-- check if the database needs to be updated
	if self.db.global.version then
		-- TODO: future checks
	else
		-- TODO: future checks
	end

	if not self.vars.playername then
		self.vars.playername = UnitName("player");
		if not self.db.realm.userdata[self.vars.playername] then
			self.db.realm.userdata[self.vars.playername] = {};
		end
--		if not self.db.realm.userdata[self.vars.playername].knownRecipes then
--			self.db.realm.userdata[self.vars.playername].knownRecipes = {};
--		end
		if not self.db.realm.userdata[self.vars.playername].skills then
			self.db.realm.userdata[self.vars.playername].skills = {};
		end
		if not self.db.realm.userdata[self.vars.playername].queue then
			self.db.realm.userdata[self.vars.playername].queue = CauldronQueue:NewQueue();
		end
		if not self.db.realm.userdata[self.vars.playername].options then
			self.db.realm.userdata[self.vars.playername].options = {
				autoBuy = false,
				compactView = false,
			};
		end
		if not self.db.realm.shopping then
			self.db.realm.shopping = CauldronShopping:NewList();
		end
		--[[
		if not self.localDb.recipes then
			self.localDb.recipes = {};
		end
		--]]
	end

	-- store the current revision in the database
	self.db.global.version = Cauldron.version;

end

function Cauldron:OnEnable()

	-- set init flag, for some callbacks
	self.initializing = true;

	self:InitPlayer();

	-- scan bags
	self:ScanBags();

	-- register for events we're interested in
	self:RegisterEvent("TRADE_SKILL_SHOW", "OnTradeShow");
	self:RegisterEvent("TRADE_SKILL_CLOSE", "OnTradeClose");
	self:RegisterEvent("ADDON_LOADED", "OnAddonLoaded");
	self:RegisterEvent("BANKFRAME_OPENED", "OnBankOpened");
	self:RegisterEvent("BANKFRAME_CLOSED", "OnBankClosed");
	self:RegisterEvent("MERCHANT_SHOW", "OnMerchantShow");
	self:RegisterEvent("MERCHANT_CLOSED", "OnMerchantClose");
	self:RegisterEvent("UI_ERROR_MESSAGE", "OnError");
	self:RegisterEvent("UNIT_QUEST_LOG_CHANGED", "OnQuestLogChanged");
	self:RegisterEvent("ACHIEVEMENT_EARNED", "OnAchievementEarned");

	-- setup hooks for tooltips
	self:HookTooltips();

	-- initialize the achievement map
	self:CreateAchievementSkillMap();

	-- replace the "show" function for the standard tradeskill frame
	LoadAddOn("Blizzard_TradeSkillUI");
	self.blizzTradeSkillShow = TradeSkillFrame_Show;
	TradeSkillFrame_Show = function()
		-- Cauldron:info("TradeSkillFrame_Show");
	end

	-- clear init flag
	self.initializing = false;

end

function Cauldron:OnDisable()

	-- TODO

end

function Cauldron:OnAddonLoaded(event, addon)

	-- show the shopping list?
	if self.db.profile.showShoppingList then
		Cauldron:ShowShoppingList();
	else
		if CauldronShopping:ContainsItems(self.db.realm.shopping) then
			Cauldron:ShowShoppingList();
		end
	end

end

function Cauldron:OnEvent(event, ...)

	--[[
	if event == "UNIT_PORTRAIT_UPDATE" then
		local arg1 = ...;
		if arg1 == "player" then
			SetPortraitTexture(CauldronQueueWindowFramePortrait, "player");
		end
	end
	--]]

end

function Cauldron:OnTradeShow()

	TradeSkillFrame_Update(); -- seems to fix the early bailout of trade skill iterations

	Cauldron:InitializeSkillData();

	-- update our known skills
--	if not Cauldron.updatingSkills then
		Cauldron.updatingSkills = true; -- Cauldron:NeedsSkillUpdate();
		Cauldron:UpdateSkills();
--		Cauldron:NewUpdateSkills();
-- Cauldron:info("on trade show: skills="..tostring(Cauldron:GetSkillCount()));
--	end

	-- register for events that are needed when the window is open
	self:RegisterEvent("UPDATE_TRADESKILL_RECAST", "OnTradeSkillRecast");
	self:RegisterEvent("UNIT_SPELLCAST_START", "OnSpellcastStart");
	self:RegisterEvent("UNIT_SPELLCAST_STOP", "OnSpellcastStop");
	self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED", "OnSpellcastSucceed");
	self:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED", "OnSpellcastInterrupt");

	-- show the UI frame
--	Cauldron.needsRedraw = true;
	self:Frame_Show();

end

function Cauldron:OnTradeUpdate(event)

--	TODO

end

function Cauldron:OnTradeClose()

	-- hide the window
	self:Frame_Hide();

	-- unregister for events that are only needed when the window is open
	self:UnregisterEvent("UPDATE_TRADESKILL_RECAST");
	self:UnregisterEvent("UNIT_SPELLCAST_START");
	self:UnregisterEvent("UNIT_SPELLCAST_STOP");
	self:UnregisterEvent("UNIT_SPELLCAST_SUCCEEDED");
	self:UnregisterEvent("UNIT_SPELLCAST_INTERRUPTED");

end

function Cauldron:OnSkillUpdate(event)

	Cauldron:UpdateSkillItemCounts();
--[[
	if CURRENT_TRADESKILL ~= "" then
		if not Cauldron.db.realm.userdata[Cauldron.vars.playername].skills[CURRENT_TRADESKILL] then
			return;
		end

		-- TODO check if the skill rank has changed, and unselect
		-- Cauldron.db.realm.userdata[Cauldron.vars.playername].skills[CURRENT_TRADESKILL].window.selected = 0;

		if not Cauldron.updatingSkills then
--			Cauldron:info("Requesting skill update for skill update");
			Cauldron.updatingSkills = Cauldron:NeedsSkillUpdate();
			Cauldron:UpdateSkills();
			Cauldron.updatingSkills = false;

			CauldronQueue:CalculateAllRequiredItems(Cauldron.db.realm.userdata[Cauldron.vars.playername].queue);

			Cauldron.needsRedraw = true;
			self:Frame_Update();
		end
	end
--]]
end

function Cauldron:OnTradeSkillRecast()

	-- keep the processing flag set
	self.processing = true;

--	self:UpdateSkills();

	CauldronInputBox:SetNumber(GetTradeskillRepeatCount());

	Cauldron:UpdateSkillItemCounts();
	Cauldron:UpdateSkillList();
--	Cauldron.needsRedraw = true;
--	self:Frame_Update();

end

function Cauldron:OnBagUpdate(event, bagid)

    -- make sure we're not reacting to initial bag update events when the DB isn't initialized yet
    if (self.initializing) or (not self.db) or (not self.vars) then
        return;
    end

	local queue = self.db.realm.userdata[self.vars.playername].queue;

	CauldronQueue:CalculateAllRequiredItems(queue);
	Cauldron:UpdateQueue();

	Cauldron:UpdateSkillItemCounts();
	Cauldron:UpdateSkillList();

	Cauldron:UpdateShoppingList();

end

function Cauldron:OnMerchantShow()

	-- check if there's anything in the shopping list
	if CauldronShopping:ContainsItems(Cauldron.db.realm.shopping) then
		if Cauldron.db.global.options[AutoOpenShoppingList] then
			Cauldron:ShowShoppingList();
		end

		if Cauldron.db.realm.userdata[Cauldron.vars.playername].options.autoBuy then
			CauldronShopping:AutoBuyShoppingItems(Cauldron.db.realm.shopping, Cauldron.vars.playername);
		end
	end

end

function Cauldron:OnMerchantClose()

	if not CauldronShopping:ContainsItems(Cauldron.db.realm.shopping) then
		if Cauldron.db.global.options[AutoOpenShoppingList] then
			Cauldron:HideShoppingList();
		end
	end

end

function Cauldron:OnBankOpened()

	-- check if there's anything in the shopping list
	if CauldronShopping:ContainsItems(Cauldron.db.realm.shopping) then
		if Cauldron.db.global.options[AutoOpenShoppingList] then
			Cauldron:ShowShoppingList();
		end
	end

end

function Cauldron:OnBankClosed()

	if not CauldronShopping:ContainsItems(Cauldron.db.realm.shopping) then
		if Cauldron.db.global.options[AutoOpenShoppingList] then
			Cauldron:HideShoppingList();
		end
	end

end

function Cauldron:OnQuestLogChanged()

	-- TODO

end

function Cauldron:OnAchievementEarned()

	-- update the achievement skill map
	Cauldron:CreateAchievementSkillMap();
	Cauldron:UpdateSkillList();

end

function Cauldron:OnSpellcastStart(event, unit, spell, rank)

	self.processing = true;

end

function Cauldron:OnSpellcastStop(event, unit, spell, rank)

	self.processing = false;

end

function Cauldron:OnSpellcastSucceed(event, unit, spell, rank)

	-- ignore if the unit was not the player
	if unit ~= "player" then
		return;
	end

	local queue = self.db.realm.userdata[self.vars.playername].queue;

	if Cauldron.makingItem then
		CauldronQueue:AdjustItemCount(queue, self.makingItem, -1);

		local count = Cauldron.makingItemCount - 1;
		if count < 1 then
			Cauldron.makingItem = nil;
			Cauldron.makingItemCount = nil;
		else
			Cauldron.makingItemCount = count;
		end
	end

	Cauldron:UpdateSkillItemCounts();
	Cauldron:UpdateSkillList();

--[[
	-- adjust queue, but only if window is open
	if CauldronFrame:IsShown() then
		self:debug("makingItemSpell: "..tostring(self.makingItemSpell));
		if self.makingItemSpell == spell then
			self.processing = false;

			CauldronQueue:AdjustItemCount(queue, self.makingItem, -1);
		end
	end
--]]

end

function Cauldron:OnSpellcastInterrupt(event, unit, spell, rank)

	Cauldron.makingItem = nil;
	Cauldron.makingItemCount = nil;

	self.processing = false;

end

function Cauldron:OnError()

--	TODO

end

function Cauldron:TradeSkillFrame_SetSelection(id)

	-- TODO

end

function Cauldron:GetSelectedSkill()

	local skillName = CURRENT_TRADESKILL;
	if IsTradeSkillLinked() then
		skillName = "Linked-"..skillName;
	end
	if IsTradeSkillGuild() then
		skillName = "Guild-"..skillName;
	end

	if (not self.db.realm.userdata[self.vars.playername]) or
	   (not self.db.realm.userdata[self.vars.playername].skills[skillName]) then
		return;
	end

	local selected = self.db.realm.userdata[self.vars.playername].skills[skillName].window.selected;
--	local selected = GetTradeSkillSelectionIndex();
	if not selected then
		return nil;
	end

	for name, info in pairs(self.db.realm.userdata[self.vars.playername].skills[skillName].recipes) do
		if selected == info.index then
			return info;
		end
	end

	return nil;
end

function Cauldron:QueueAllTradeSkillItem()

	local skillInfo = Cauldron:GetSelectedSkill();

	if skillInfo then
		local amount = skillInfo.available;
		local potential = Cauldron:GetPotentialCraftCount(skillInfo);
		local queueAmount = 0;

		-- if regular amount > 0 ...
		if amount > 0 then
			if (potential > 0) and IsShiftKeyDown() then
				queueAmount = potential;
			else
				queueAmount = amount;
			end
		else
			queueAmount = potential;
		end

		if queueAmount > 0 then
			CauldronQueue:AddItem(self.db.realm.userdata[self.vars.playername].queue, skillInfo, queueAmount);

			Cauldron:UpdateQueue();

			-- update the shopping list
			Cauldron:UpdateShoppingListFromQueue();
		else
			Cauldron:info("No amount to queue for "..skillInfo.name..".");
		end
	end

end

function Cauldron:QueueTradeSkillItem()

	local skillInfo = Cauldron:GetSelectedSkill();

	if skillInfo then
		local amount = CauldronInputBox:GetNumber();
		if not amount or amount < 1 then
			amount = 1;
		end
		CauldronQueue:AddItem(self.db.realm.userdata[self.vars.playername].queue, skillInfo, amount);

		Cauldron:UpdateQueue();

		-- update the shopping list
		Cauldron:UpdateShoppingListFromQueue();
	end

end

function Cauldron:CreateAllTradeSkillItem()

	if (not PartialPlayTime()) and (not NoPlayTime()) then
		CauldronInputBox:ClearFocus();

		local skillInfo = Cauldron:GetSelectedSkill();

		CauldronInputBox:SetNumber(skillInfo.available);

		DoTradeSkill(skillInfo.index, skillInfo.available);

		Cauldron:UpdateSkillItemCounts();
		Cauldron:UpdateSkillList();
	end

end

function Cauldron:CreateTradeSkillItem()

	if ((not PartialPlayTime()) and (not NoPlayTime())) then
		CauldronInputBox:ClearFocus();

		local skillInfo = Cauldron:GetSelectedSkill();
		local amount = CauldronInputBox:GetNumber();

		DoTradeSkill(skillInfo.index, amount);

		Cauldron:UpdateSkillItemCounts();
		Cauldron:UpdateSkillList();
	end

end

function Cauldron:ProcessQueue()

	if IsTradeSkillLinked() then
		self:error("Can't process queue for linked tradeskill!");
		return;
	end

	--[[ TODO: update queue logic
	-- look at first item, if it can be made, make it (lower of queued quantity vs. quantity able to make)
	-- if items other than first can be made and first can't, ask user if they want to make that instead
	--]]

	local queue = CauldronQueue:GetItems(self.db.realm.userdata[self.vars.playername].queue);

	local queueInfo = nil;
	local skillInfo = nil;

	if #queue > 0 then
		-- see if first item can be made
		queueInfo = queue[1];
--		self:debug("ProcessQueue: queueInfo="..queueInfo.name);
		skillInfo = Cauldron:GetSkillInfo(queueInfo.tradeskill, queueInfo.name);
--		self:debug("ProcessQueue: skillInfo="..tostring(skillInfo));

		if skillInfo.available > 0 then
--			self:debug("First item in main queue can be made "..skillInfo.available.." times");
			self:SubmitItemToProcess(queueInfo, skillInfo, math.min(skillInfo.available, queueInfo.amount));
			return;
--[[		else
			-- see if queue contains other items that can be made if the first can't be
			if #queue > 1 then
				for i=2,#queue do
					queueInfo = queue[i];
					skillInfo = Cauldron:GetSkillInfo(queueInfo.tradeskill, queueInfo.name);
					self:debug("ProcessQueue: skillInfo="..tostring(skillInfo));

					if skillInfo.available > 0 then
						-- present dialog to user to move item to top of queue
						Cauldron:ConfirmDialog(L["Confirm"], L["message"],
							L["Okay"],
							function()
								Cauldron:info("Okay");
								-- TODO
							end,
							L["Cancel"],
							function()
								Cauldron:info("Cancel");
								-- TODO
							end);
						return;
					end
				end
			end --]]
		end
	end

	-- find intermediate items that need to be crafted
	local intQueue = CauldronQueue:GetIntermediates(self.db.realm.userdata[self.vars.playername].queue);

	if #intQueue > 0 then
	 	queueInfo = intQueue[1];
		skillInfo = Cauldron:GetSkillInfo(queueInfo.tradeskill, queueInfo.name);
	else
		if #queue > 0 then
			queueInfo = queue[1];
			skillInfo = Cauldron:GetSkillInfo(queueInfo.tradeskill, queueInfo.name);
		end
	end

	self:SubmitItemToProcess(queueInfo, skillInfo);

end

function Cauldron:SubmitItemToProcess(queueInfo, skillInfo, amount)

	if queueInfo and skillInfo then

		if queueInfo.tradeskill ~= CURRENT_TRADESKILL then
			local msg = string.format(L["Crafting %1$s requires the %2$s skill."], queueInfo.name, queueInfo.tradeskill);
			UIErrorsFrame:AddMessage(msg, 1.0, 0.0, 0.0);
			return;
		end

		Cauldron:ProcessItem(skillInfo, queueInfo, amount or queueInfo.amount);
	else
		if not queueInfo then
			self:error("Missing queue info!");
		end
		if not skillInfo then
			self:error("Missing skill info!");
		end
	end

end

function Cauldron:ProcessItem(skillInfo, queueInfo, amount)

	if not skillInfo then
		self:error("ProcessItem: Missing skill info!");
		return;
	end
	if amount < 1 then
		self:error("ProcessItem: Invalid amount specified: "..tostring(amount).."!");
		return;
	end

	if ((not PartialPlayTime()) and (not NoPlayTime())) then
		-- record the item we're making
		self.makingItem = Cauldron:GetNameFromLink(queueInfo.link);
		self.itemCurrentCount = GetItemCount(skillInfo.itemLink);
		self.makingItemSpell = queueInfo.spell or Cauldron:GetNameFromLink(queueInfo.link);
		self.makingItemCount = amount;
		self.queueInfo = queueInfo;

		-- tell the user what we're doing
		self:Print(string.format(L["Crafting %1$d of %2$s..."], amount, queueInfo.link));

		-- do it
		self.processing = true;
		DoTradeSkill(skillInfo.index, amount);
	else
		-- TODO: notify player?
		Cauldron:warn(L["Unable to process item due to play time limitation."]);
	end

end

function Cauldron:RemoveQueueItem(name)
	CauldronQueue:RemoveItem(Cauldron:GetQueue(), name);
end

function Cauldron:IncreaseItemPriority(name, top)
	CauldronQueue:IncreasePriority(Cauldron:GetQueue(), name, top);
end

function Cauldron:DecreaseItemPriority(name, bottom)
	CauldronQueue:DecreasePriority(Cauldron:GetQueue(), name, bottom);
end

function Cauldron:DecreaseItemCount(name)
	CauldronQueue:AdjustItemCount(Cauldron:GetQueue(), name, -1);
end

function Cauldron:IncreaseItemCount(name)
	CauldronQueue:AdjustItemCount(Cauldron:GetQueue(), name, 1);
end

function Cauldron:FirstPage()

	local skillName = CURRENT_TRADESKILL;
	if skillName == "UNKNOWN" then
		return;
	end
	if IsTradeSkillLinked() then
		skillName = "Linked-"..skillName;
	end
	if IsTradeSkillGuild() then
		skillName = "Guild-"..skillName;
	end

	self.db.realm.userdata[self.vars.playername].skills[skillName].window.offset = 0;
	CauldronSkillListScrollFrame:SetVerticalScroll(0);
end

function Cauldron:PrevPage()

	local skillName = CURRENT_TRADESKILL;
	if skillName == "UNKNOWN" then
		return;
	end
	if IsTradeSkillLinked() then
		skillName = "Linked-"..skillName;
	end
	if IsTradeSkillGuild() then
		skillName = "Guild-"..skillName;
	end

	local offset = self.db.realm.userdata[self.vars.playername].skills[skillName].window.offset;
	if not offset then
		self.db.realm.userdata[self.vars.playername].skills[skillName].window.offset = 0;
	else
		self.db.realm.userdata[self.vars.playername].skills[skillName].window.offset = math.max(0, offset - CAULDRON_SKILL_LIST_MAX);
	end
	CauldronSkillListScrollFrame:SetVerticalScroll(0);
end

function Cauldron:NextPage()

	local skillName = CURRENT_TRADESKILL;
	if skillName == "UNKNOWN" then
		return;
	end
	if IsTradeSkillLinked() then
		skillName = "Linked-"..skillName;
	end
	if IsTradeSkillGuild() then
		skillName = "Guild-"..skillName;
	end

	local offset = self.db.realm.userdata[self.vars.playername].skills[skillName].window.offset;
	local numSkills = table.getn(Cauldron:GetSkillList(self.vars.playername, skillName));
--	local numSkills = self.db.realm.userdata[self.vars.playername].skills[skillName].skillCount or 0;
	if not offset then
		self.db.realm.userdata[self.vars.playername].skills[skillName].window.offset = CAULDRON_SKILL_LIST_MAX;
	else
		if (offset + CAULDRON_SKILL_LIST_MAX) <= numSkills then
			self.db.realm.userdata[self.vars.playername].skills[skillName].window.offset = offset + CAULDRON_SKILL_LIST_MAX;
		end
	end
	CauldronSkillListScrollFrame:SetVerticalScroll(0);
end

function Cauldron:LastPage()

	local skillName = CURRENT_TRADESKILL;
	if skillName == "UNKNOWN" then
		return;
	end
	if IsTradeSkillLinked() then
		skillName = "Linked-"..skillName;
	end
	if IsTradeSkillGuild() then
		skillName = "Guild-"..skillName;
	end

	local numSkills = table.getn(Cauldron:GetSkillList(self.vars.playername, skillName));
	self.db.realm.userdata[self.vars.playername].skills[skillName].window.offset = math.floor(numSkills / CAULDRON_SKILL_LIST_MAX) * CAULDRON_SKILL_LIST_MAX;
	CauldronSkillListScrollFrame:SetVerticalScroll(0);
end

function Cauldron:GetQueue(player)

	if not player then
		player = self.vars.playername;
	end

	local queue = self.db.realm.userdata[player].queue;
	if not queue then
		queue = CauldronQueue:NewQueue();
		self.db.realm.userdata[player].queue = queue;
	end

	return queue;
end

function Cauldron:AddItemToShoppingList(itemName, amount, replace)
	if replace then
		CauldronShopping:RemoveFromList(self.db.realm.shopping, self.vars.playername, itemName);
	end
	CauldronShopping:AddToList(self.db.realm.shopping, self.vars.playername, itemName, amount);
	Cauldron:ShowShoppingList();
end

function Cauldron:RemoveShoppingListItem(requestor, itemName)
	CauldronShopping:RemoveFromList(self.db.realm.shopping, requestor, itemName, nil);

--	if not CauldronShopping:ContainsItems(self.db.realm.shopping) then
--		Cauldron:HideShoppingList();
--	end
end

function Cauldron:UpdateShoppingListFromQueue()
	local items = CauldronQueue:GetReagents(self.db.realm.userdata[self.vars.playername].queue);
	if items then
		for i,item in ipairs(items) do
			local id = Cauldron:GetIdFromLink(item.link);
			local have = GetItemCount(item.link);
			local need = math.max(0, item.amount - have);
			if (need > 0) and Cauldron:IsVendorItem(id) then
				Cauldron:AddItemToShoppingList(item.name, need, true);
			end
		end
	end
end

function Cauldron:SetWindowOffset(offset)

	local skillName = CURRENT_TRADESKILL;
	if skillName == "UNKNOWN" then
		return;
	end
	if IsTradeSkillLinked() then
		skillName = "Linked-"..skillName;
	end
	if IsTradeSkillGuild() then
		skillName = "Guild-"..skillName;
	end

	Cauldron.db.realm.userdata[Cauldron.vars.playername].skills[skillName].window.offset = offset;
end

function Cauldron:LocaleString(str)
	return L[str];
end

function Cauldron:DisplayVersion()
	self:Print(L["Version "],Cauldron.version);
	self:Print(L["Date: "],Cauldron.date);
	self:Print(L["Oh. Smells like barbecued dog hair."]);
end

----------------------------------------------------------------
--  Tooltip Functions
----------------------------------------------------------------

function Cauldron:HookTooltips()

	self:SecureHook(GameTooltip, "SetBagItem");
	self:SecureHook(GameTooltip, "SetInventoryItem");
	self:SecureHook(GameTooltip, "SetLootItem");
	self:SecureHook(GameTooltip, "SetHyperlink");
	self:SecureHook(GameTooltip, "SetTradeSkillItem");
	self:SecureHook(GameTooltip, "SetMerchantItem");
	self:SecureHook(GameTooltip, "SetAuctionItem");
--	self:SecureHook(GameTooltip, "SetTrainerService");
	self:SecureHook(GameTooltip, "SetGuildBankItem");
	self:SecureHook("SetItemRef");

end

function Cauldron:AddToTooltip(tooltip, id)

	if not Cauldron.db.global.options.ModifyTooltip then
		return;
	end

	if not id then
		return;
	end

	local skillList = Cauldron:GetSkillsForReagent(id);

	-- add favorite info
	local favSkills = filterFavoriteSkills(skillList);
	if favSkills and #favSkills > 0 then
		if #favSkills > 3 then
			-- if the skill list has more than 3 items, summarize
			local favInfo = string.format(L["Needed for %1$d favorite skills"], #favSkills);
			tooltip:AddLine("|r"..favInfo.."|r");
		else
			-- if the skill list is 3 or less, list all
			for i,skill in ipairs(favSkills) do
				local skillName,skillLink = string.split(";", skill, 2);
				local favInfo = L["Needed for favorites:"];
				if i > 1 then
					favInfo = " ";
				end
				local skillInfo = Cauldron:GetSkillInfoForLink(skillLink);
				local color;
				if TradeSkillTypeColor then
					color = TradeSkillTypeColor[skillInfo.difficulty];
				else
					local colorMap = {
						optimal = {r=1.0, g=0.5, b=0.25},
						medium = {r=1.0, g=1.0, b=0.0},
						easy = {r=0.25, g=0.75, b=0.25},
					};
					color = colorMap[skillInfo.difficulty];
				end
				local colorStr = "|r";
				if color then
					colorStr = string.format("|cff%02x%02x%02x", (color.r*255), (color.g*255), (color.b*255));
				end
				tooltip:AddDoubleLine(favInfo, colorStr..skillInfo.name.."|r");
			end
		end
	end

	-- add skill-up info
	local skillups = filterSkillups(skillList);
	if skillups and #skillups > 0 then
		if #skillups > 3 then
			-- if the skill list has more than 3 items, summarize
			local levelInfo = string.format(L["Needed for %1$d skills for leveling"], #skillups);
			tooltip:AddLine("|r"..levelInfo.."|r");
		else
			-- if the skill list is 3 or less, list all
			for i,skill in ipairs(skillups) do
				local skillName,skillLink = string.split(";", skill, 2);
				local levelInfo = L["Needed for leveling:"];
				if i > 1 then
					levelInfo = " ";
				end
				local skillInfo = Cauldron:GetSkillInfoForLink(skillLink);
				local color;
				if TradeSkillTypeColor then
					color = TradeSkillTypeColor[skillInfo.difficulty];
--				else
--					color = {r=1.0, g=1.0, b=1.0};
				end
				local colorStr = "|r";
				if color then
					colorStr = string.format("|cff%02x%02x%02x", (color.r*255), (color.g*255), (color.b*255));
				end
				tooltip:AddDoubleLine(levelInfo, colorStr..skillInfo.name.."|r");
			end
		end
	end

	-- force resize of the tooltip
	tooltip:Show();
end

function filterFavoriteSkills(skillList)

	if not skillList then
		return nil;
	end

	local faves = {};

	for i,skill in ipairs(skillList) do
		local skillName, skillLink = string.split(";", skill, 2);
		local skillInfo = Cauldron:GetSkillInfoForLink(skillLink);

		if skillInfo and skillName then
			if Cauldron.db.realm.userdata[Cauldron.vars.playername].skills[skillName] then
				if Cauldron.db.realm.userdata[Cauldron.vars.playername].skills[skillName].window.skills[skillInfo.name] then
					if Cauldron.db.realm.userdata[Cauldron.vars.playername].skills[skillName].window.skills[skillInfo.name].favorite then
						table.insert(faves, skill);
					end
				end
			end
		end
	end

	return faves;
end

function filterSkillups(skillList)

	if not skillList then
		return nil;
	end

	local skillups = {};

	local difficulty = {
		optimal = 4,
		medium = 3,
		easy = 2,
		trivial = 1,
	};

	for i,skill in ipairs(skillList) do
		local skillName, skillLink = string.split(";", skill, 2);
		local skillInfo = Cauldron:GetSkillInfoForLink(skillLink);

		if skillInfo then
			if difficulty[skillInfo.difficulty] > 1 then
				table.insert(skillups, skill);
			end
		end
	end

	return skillups;
end

function Cauldron:SetTradeSkillItem(tooltip, itemIndex, reagentIndex)
--[[
	local link;
	local name;
	if reagentIndex then
		local skillInfo = Cauldron:GetSkillInfoByIndex(itemIndex);
		local reagentInfo = Cauldron:GetReagentInfoByIndex(itemIndex, reagentIndex);

		if reagentInfo then
			local id = Cauldron:GetIdFromLink(reagentInfo.link);
			self:AddToTooltip(tooltip, id);

    		-- let the user know if the reagent is a "non-key" reagent
	    	if not reagentInfo.key then
		    	tooltip:AddLine("|cff666666"..L["Available at vendor"].."|r");
		    end
		end
	else
--		link = GetTradeSkillItemLink(itemIndex);
--		name = Cauldron:GetIdFromLink(link);
	end
--]]
	tooltip:Show();
end

function Cauldron:SetHyperlink(tooltip, link)

--	local name = Cauldron:GetNameFromLink(link);
--	local skillInfo = Cauldron:
	local id = Cauldron:GetIdFromLink(link);

end

function Cauldron:SetItemRef(link, text, button)
--	if IsControlKeyDown() or IsShiftKeyDown() then return end
	if ( IsModifiedClick() ) then return end
	local id = Cauldron:GetIdFromLink(link);
	if id then
--		self:AddToTooltip(ItemRefTooltip,id);
	end
end

function Cauldron:SetBagItem(tooltip, bag, slot)
	local link = GetContainerItemLink(bag, slot);
	local id = Cauldron:GetIdFromLink(link);
	self:AddToTooltip(tooltip, id);
end

function Cauldron:SetGuildBankItem(tooltip, tab, slot)
	local link = GetGuildBankItemLink(tab, slot);
	local id = Cauldron:GetIdFromLink(link);
	self:AddToTooltip(tooltip, id);
end

function Cauldron:SetInventoryItem(tooltip, unit, slot, nameOnly)
	if slot > 39 and slot < 68 then
		local link = GetContainerItemLink(BANK_CONTAINER, slot-39)
		local id = Cauldron:GetIdFromLink(link);
		self:AddToTooltip(tooltip, id);
	end
end

function Cauldron:SetLootItem(tooltip, index)
	local link = GetLootSlotLink(index);
	local id = Cauldron:GetIdFromLink(link);
	self:AddToTooltip(tooltip, id);
end

function Cauldron:SetMerchantItem(tooltip, index)
	local link = GetMerchantItemLink(index);
	local id = Cauldron:GetIdFromLink(link);
	self:AddToTooltip(tooltip, id);
end

function Cauldron:SetAuctionItem(tooltip, type, index)
	local link = GetAuctionItemLink(type, index);
	local id = Cauldron:GetIdFromLink(link);
	self:AddToTooltip(tooltip, id);
end

----------------------------------------------------------------------
-- Property functions
----------------------------------------------------------------------

--[[ Databroker Stuff --]]

local ldb = LibStub:GetLibrary("LibDataBroker-1.1", true)
if ldb then
	ldb:NewDataObject("Cauldron", {
		type = "launcher",
		text = "Cauldron shopping list",
		icon = "Interface\\Icons\\INV_Elemental_Mote_Nether",
		OnClick = function(frame, button)
			if button == "LeftButton" then
				Cauldron:ShowShoppingList();
			elseif button == "RightButton" then
				Cauldron:ShoppingList_Toggle();
			end
		end,
		OnTooltipShow = function(tooltip)
			tooltip:AddLine("Cauldron " .. Cauldron.version)
		end,
	})
end

--[[
	Database table structure:

	["userdata"] = {
		["<character-name>"] = {
			["queue"] = {
				["intermediate"] = {
					["<skill>"] = {
						["tradeskill"] = "<tradeskill>", -- human-readable name of the tradeskill that contains this skill
						["name"] = "<skill>", -- human-readable name of the skill
						["amount"] = <amount>, -- amount of this skill that must be executed
						["priority"] = <priority>, -- priority of the skill, for ordering in the queue
						["icon"] = "<icon>", -- the icon path of the skill, for display
						["link"] = "<link>",
					},
				},
				["main"] = {
					["<skill>"] = {
						["tradeskill"] = "<tradeskill>",
						["name"] = "<skill>",
						["amount"] = <amount>,
						["priority"] = <priority>,
						["icon"] = "<icon>",
						["link"] = "<link>",
					},
				},
				["reagents"] = {
					["<reagent>"] = {
						["tradeskill"] = "<tradeskill>",
						["name"] = "<reagent>",
						["amount"] = <amount>,
						["icon"] = "<icon>",
						["link"] = "<link>",
					},
				},
			},
			["skills"] = {
				["<tradeskill>"] = {
					["skillLevel"] = <level>,
					["skillCount"] = <count>,
					["lastScanDate"] = <millis>,
					["window"] = { -- window metadata
						["categories"] = { -- category cache of the tradeskill
							["<category>"] = { -- name of the category
								["shown"] = true, -- whether the category is being shown in the skill list
							},
							...
						},
						["search"] = "", -- search text the user typed in the search box
						["filter"] = { -- flags for the filter dropdown
							["sortAlpha"] = false, -- should the list be sorted alphabetically (mutually-exclusive to "sortDifficulty" and "sortBenefit")
							["sortDifficulty"] = true, -- should the list be sorted by difficulty  (mutually-exclusive to "sortAlpha" and "sortBenefit")
							["sortBenefit"] = false, -- should the list be sorted by potentcy of the benefit the crafted item/enchantment grants  (mutually-exclusive to "sortDifficulty" and "sortAlpha") UNUSED
							["haveAllReagents"] = false, -- should only items with all required reagents be shown in the list?
							["haveKeyReagents"] = false, -- should only items with all key reagents be shown in the list?
							["haveAnyReagents"] = false, -- should only items with any reagents be shown in the list?
							["optimal"] = true, -- should optimal skills be shown?
							["medium"] = true, -- should medium skills be shown?
							["easy"] = true, -- should easy skills be shown?
							["trivial"] = false, -- should trivial skills be shown?
							["favorites"] = false, -- should only favorite skills be shown?
						},
						["skills"] = {
							["<skill>"] = {
								["expanded"] = false, -- should the skill's reagents be displayed?
								["favorite"] = false, -- is the skill a favorite?
							},
							...
						},
						["selected"] = 1, -- skill index of the selected skill (gets reset on skill update events, like skill level increases and training)
						["slots"] = { -- slot cache
							["INVTYPE_FEET"] = true,
							["INVTYPE_BAG"] = true,
							["INVTYPE_CLOAK"] = true,
							["(none)"] = true, -- special slot for items that don't have an assigned slot
							["INVTYPE_WRIST"] = true,
							["INVTYPE_WAIST"] = true,
							["INVTYPE_CHEST"] = true,
							["INVTYPE_LEGS"] = true,
							["INVTYPE_HAND"] = true,
						},
					},
					["recipes"] = {
						["<skill>"] = {
							["index"] = <skill index>,
							["name"] = "<skill>",
							["link"] = "<link>",
							["icon"] = "<icon>",
							["verb"] = "<verb>", -- this is present for skills that cast, like enchantments
							["tradeskill"] = "<tradeskill>",
							["difficulty"] = "<difficulty>",
							["available"] = <available>,
							["minMade"] = <min>,
							["maxMade"] = <max>,
							["slot"] = "INVTYPE_LEGS",
							["defaultCategory"] = "<category>",
							["reagents"] = {
								{
									["name"] = "<reagent name>",
									["numRequired"] = <required>, -- the number required of this reagent to complete one execution of the skill
									["index"] = <reagent index>, -- the index of the reagent, for API call usage
									["skillIndex"] = <skill index>, -- the index of the skill, for API call usage
									["icon"] = "<icon>",
									["key"] = <true-or-false>,
								}, -- [1]
								...
							},
							["keywords"] = "<skill>,<reagent>,...",
						},
					},
				},
			},
		},
	},
	["shopping"] = {
		["<character-name>"] = {
			["<reagent name>"] = <amount>,
		},
	},

--]]

function Cauldron:Enable()
	Cauldron.vars.enabled = true;
	self:Print(L["enabled"]);
end

function Cauldron:Disable()
	Cauldron.vars.enabled = false;
	self:Print(L["disabled"]);

	Cauldron:Frame_Hide();
end

--[==[
function Cauldron:Forget(param)
	if not name then
		return;
	end

	self:info("param: "..tostring(param));

	for k,v in pairs(name) do
		self:info("k: "..k.."; v: "..tostring(v));
		--[[
		if type(v) == "table" then
			for k2,v2 in pairs(v) do
				self:info("k2: "..k2.."; v2: "..tostring(v2));
			end
		end
		--]]
	end
end
--]==]

function Cauldron:Reset()
	self:Print("Starting reset.");

	-- close the window
	self:Print("Closing windows...");
	Cauldron:Frame_Hide();
	HideUIPanel(TradeSkillFrame);
	Cauldron:HideShoppingList();

	-- reset some internal variables
	self:Print("Resetting internal variables...");
	self.vars.enabled = true;

	-- reset the shopping list
	self:Print("Clearing shopping list...");
	self.db.realm.shopping = CauldronShopping:NewList();

	-- reset the skills for each character
	self:Print("Purging data structures...");
	for toon, info in pairs(self.db.realm.userdata) do
		self.db.realm.userdata[toon] = {};
		self.db.realm.userdata[toon].skills = {};
		self.db.realm.userdata[toon].queue = CauldronQueue:NewQueue();
		self.db.realm.userdata[toon].options = {
			autoBuy = false,
			compactView = false,
		};
	end

	self:Print("Reset complete.");
end