Quantcast
-- $Revision: 212 $
-- Cauldron tradeskill functions

local L = LibStub("AceLocale-3.0"):GetLocale("Cauldron")

function Cauldron:GetTradeSkillName()

	local skillName = GetTradeSkillLine();
	if not skillName then
		return nil;
	end

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

	return skillName, CURRENT_TRADESKILL;
end

function Cauldron:NeedsSkillUpdate()

--	Cauldron:info("checking for skill update");

	local skillName = Cauldron:GetTradeSkillName();
	if not skillName then
		return false;
	end

	-- initialize the trade skill entry
	if not Cauldron.db.realm.userdata[Cauldron.vars.playername].skills[skillName] then
		Cauldron.db.realm.userdata[Cauldron.vars.playername].skills[skillName] = {};
--		Cauldron:info("no skill entry; update needed");
		return true;
	end

	-- initialize the reagent map
	if not Cauldron.db.realm.userdata[Cauldron.vars.playername].reagentMap then
		Cauldron.db.realm.userdata[Cauldron.vars.playername].reagentMap = {};
--		Cauldron:info("no reagent map; update needed");
		return true;
	end

	-- save the skill entry in a local var
	local skillDB = Cauldron.db.realm.userdata[Cauldron.vars.playername].skills[skillName];
	if not skillDB.recipes then
		skillDB.recipes = {};
	end

	local numTradeSkills = GetNumTradeSkills();
	local name, rank, maxRank = GetTradeSkillLine();

	if skillDB.rescan and skillDB.rescan.failedRecipes and (#skillDB.rescan.failedRecipes > 0) then
		return true;
	end

	if (tonumber(numTradeSkills) ~= tonumber(skillDB.actualSkillCount)) then
		return true;
	end
	if (tonumber(rank) ~= tonumber(skillDB.skillLevel)) then
		return true;
	end
	if ((time() - tonumber(skillDB.lastScanDate)) > 184000) then
	end

	return false;
end

function Cauldron:UpdateSkillItemCounts()

	local skillName, rank, maxRank = GetTradeSkillLine();

	local baseSkillName = nil;
	skillName, baseSkillName = Cauldron:GetTradeSkillName();
	if not skillName then
		return;
	end

	local skillDB = self.db.realm.userdata[self.vars.playername].skills[skillName];
	if not skillDB then
		return;
	end

	for i=1,GetNumTradeSkills() do
		local name, difficulty, avail, expanded, verb, numSkillUps = GetTradeSkillInfo(i);

		if name and difficulty ~= "header" then

			local minMade, maxMade = GetTradeSkillNumMade(i);

			if skillDB.recipes[name] then
				-- update the recipe info
				skillDB.recipes[name].index = i;
				skillDB.recipes[name].difficulty = difficulty;
				skillDB.recipes[name].available = avail;
				skillDB.recipes[name].minMade = minMade;
				skillDB.recipes[name].maxMade = maxMade;
				skillDB.recipes[name].numSkillUps = numSkillUps;

				-- update reagent skill index
				for _,r in ipairs(skillDB.recipes[name].reagents) do
					r.skillIndex = i;
				end
			end
		end
	end

end

function Cauldron:InitializeSkillData()

	local skillName, rank, maxRank = GetTradeSkillLine();

	local baseSkillName = nil;
	skillName, baseSkillname = Cauldron:GetTradeSkillName();
	if not skillName then
		return;
	end

	-- initialize the trade skill entry
	if not self.db.realm.userdata[self.vars.playername].skills[skillName] then
		self.db.realm.userdata[self.vars.playername].skills[skillName] = {};
	end

	-- initialize the reagent map
	if not self.db.realm.userdata[self.vars.playername].reagentMap then
		self.db.realm.userdata[self.vars.playername].reagentMap = {};
	end

	-- save the skill entry in a local var
	local skillDB = self.db.realm.userdata[self.vars.playername].skills[skillName];
	if not skillDB.recipes then
		skillDB.recipes = {};
	end

			-- record skill stats
	skillDB.skillLevel = rank;
	skillDB.skillCount = numTradeSkills;
	skillDB.lastScanDate = time();

	-- initialize window information, if necessary
	if not skillDB.window then
		skillDB.window = {
			search = "",
			filter = {
				optimal = true,
				medium = true,
				easy = true,
				trivial = true,
				haveAllReagents = false,
				haveKeyReagents = false,
				haveAnyReagents = false,
				sortDefault = true,
				sortDifficulty = false,
				sortAlpha = false,
				sortBenefit = false,
				sortItemLevel = false,
				sortRequiredLevel = false,
				sortFavorites = false,
				favorites = false,
				achievements = false,
			},
			skills = {},
			slots = {},
			categories = {},
			selected = 1,
		};
	end

end

--[==[
function Cauldron:NewUpdateSkills()

	local skillName, rank, maxRank = GetTradeSkillLine();

	local baseSkillName = nil;
	skillName, baseSkillName = Cauldron:GetTradeSkillName();
	if not skillName then
Cauldron:info("no skill name");
		return;
	end

--	local numTradeSkills = GetNumTradeSkills();
--	Cauldron.vars.numSkills = numTradeSkills;

	-- make sure we're getting a full list
	SetTradeSkillItemNameFilter(nil);
	SetTradeSkillItemLevelFilter(0, 0);
	TradeSkillOnlyShowSkillUps(false);
	TradeSkillOnlyShowMakeable(false);

	-- make sure the list is fully expanded
	for i=GetNumTradeSkills(),1,-1 do
		local name = GetTradeSkillInfo(i);
		if name == "header" then
			ExpandTradeSkillSubClass(i);
		end
	end

	local category = "";
	local skillDB = self.db.realm.userdata[self.vars.playername].skills[skillName];

	for i=1,GetNumTradeSkills() do
		local name, difficulty, avail, expanded, verb, numSkillUps = GetTradeSkillInfo(i);

		if name and difficulty ~= "header" then

			local minMade, maxMade = GetTradeSkillNumMade(i);

			-- add the recipe info and other miscellaneous info
			local itemLink = GetTradeSkillItemLink(i);
			local recipeLink = GetTradeSkillRecipeLink(i);
			local _, _, _, _, _, itemType, itemSubType, _, slot, _ = GetItemInfo(itemLink);

			local keywords = name;

			-- create the db entry
			local recipe = {
				['index'] = i,
				['name'] = name,
				['description'] = GetTradeSkillDescription(i),
				['itemLink'] = itemLink,
				['recipeLink'] = recipeLink,
				['icon'] = GetTradeSkillIcon(i),
				['tradeskill'] = baseSkillName,
				['difficulty'] = difficulty,
				['available'] = avail,
				['minMade'] = minMade,
				['maxMade'] = maxMade,
				['numSkillUps'] = numSkillUps,
				['verb'] = verb,

				-- filter information
				['slot'] = _G[slot],
				['defaultCategory'] = category,
				['type'] = itemType,
				['subtype'] = itemSubType,

				['reagents'] = {},
			};

			-- check for items from the original that need to be preserved
			if skillDB.recipes[name] then
Cauldron:info("doing replacements: "..name);
				-- preserve reagent list, if present and "sane"
				if skillDB.recipes[name].reagents then
					if #skillDB.recipes[name].reagents > 0 then
						local copyReagents = true;
						for _,ri in ipairs(skillDB.recipes[name].reagents) do
							if not ri.name then
								copyReagents = false;
								break;
							end
						end

						if copyReagents then
							-- copy the reagents list over
							recipe.reagents = skillDB.recipes[name].reagents;
						else
							Cauldron:LearnSkillReagents(recipe);
						end
					else
						Cauldron:LearnSkillReagents(recipe);
					end
				end

--[[
				-- preserve the old keywords, if present
				if skillDB.recipes[name].keywords then
					-- fill in the keywords db entry
					recipe.keywords = skillDB.recipes[name].keywords;
				end
--]]
			end

			skillDB.recipes[name] = recipe;
		else
			-- save the header name
			if name then
				category = name;

				-- expand the header, so we get all the skills
				if not expanded then
					ExpandTradeSkillSubClass(i);
				end
			else
				category = "Uncategorized";
			end
		end
	end

	local endTime = GetTime();
	if Cauldron.vars.displayTimers then
		Cauldron:info("scan time: "..tostring(endTime-startTime).."ms");
	end

	local numSkills = table.getn(skillDB.recipes)
Cauldron:info("numSkills="..tostring(numSkills));
	Cauldron.vars.numSkills = numSkills;
	skillDB.skillCount = numSkills;
	skillDB.actualSkillCount = GetNumTradeSkills();

end
--]==]

function Cauldron:LearnSkillReagents(skillInfo)

	-- sanity
	if not skillInfo then
		Cauldron:error("Can't learn reagents; no skill information!");
		return;
	end
	if skillInfo.tradeskill ~= CURRENT_TRADESKILL then
		Cauldron:error("Can't learn reagents; skill's trade doesn't match current!");
		return;
	end

	local skillName = skillInfo.tradeskill;

	-- don't clobber the previous list
	if table.getn(skillInfo.reagents) > 0 then
--		Cauldron:warn("Not replacing current list of reagents.");
		return;
	end

-- Cauldron:info("learning reagents for: "..skillInfo.name);

	local keywords = skillInfo.keywords or "";

	local reagents = {};

	-- populate the reagent list
	local num = GetTradeSkillNumReagents(skillInfo.index);
	for j=1,num,1 do
		local rName, rIcon, rCount, playerAmount = GetTradeSkillReagentInfo(skillInfo.index, j);
		local rLink = GetTradeSkillReagentItemLink(skillInfo.index, j);
		local rItemId = Cauldron:GetIdFromLink(rLink);
		local key = not Cauldron:IsVendorItem(rItemId);

		if (not rName) or (not rIcon) or (not rLink) then
--Cauldron:error("Failed to retrieve reagent info; marking recipe for rescan: "..skillInfo.name);
--Cauldron:warn("i="..tostring(skillInfo.index).."; j="..tostring(j));
		else
			local r = {
				["index"] = j,
				["name"] = rName,
				["numRequired"] = rCount,
				["skillIndex"] = skillInfo.index,
				["icon"] = rIcon,
				["link"] = rLink,
				["key"] = key,
				["amount"] = playerAmount,
			};

			table.insert(reagents, r);

			-- add the reagent to the reagent map
			if not self.db.realm.userdata[self.vars.playername].reagentMap[rName] then
				self.db.realm.userdata[self.vars.playername].reagentMap[rName] = {};
			end
			table.insert(self.db.realm.userdata[self.vars.playername].reagentMap[rName], skillName..";"..skillInfo.recipeLink);

			keywords = keywords..","..rName;
		end
	end

	-- if the learning process succeeded (total reagents in table matches count), then store them
	if #reagents == num then
		skillInfo.reagents = reagents;

		-- fill in the keywords db entry
		skillInfo.keywords = keywords;
--	else
--		Cauldron:warn("Error while learning reagents; discarding.");
	end

end

function Cauldron:UpdateSkills()

	if not self.updatingSkills then
		self:debug("not updating skills; return");
		return;
	end

	local skillName, rank, maxRank = GetTradeSkillLine();
	local baseSkillName;

	skillName, baseSkillName = Cauldron:GetTradeSkillName();
	if not skillName then
		return;
	end

	local numTradeSkills = GetNumTradeSkills();
	Cauldron.vars.numSkills = numTradeSkills;

--	Cauldron:info("scanningSkills="..tostring(self.scanningSkills));
	if not Cauldron.scanningSkills then

		local startTime = GetTime();

		Cauldron.scanningSkills = true;
--		Cauldron:Print("Scanning recipes..."); -- TODO: remove

		-- make sure we're getting a full list
		SetTradeSkillItemNameFilter(nil);
		SetTradeSkillItemLevelFilter(0, 0);
		TradeSkillOnlyShowSkillUps(false);
		TradeSkillOnlyShowMakeable(false);

		-- make sure the list is fully expanded
		for i=GetNumTradeSkills(),1,-1 do
			local name = GetTradeSkillInfo(i);
			if name == "header" then
				ExpandTradeSkillSubClass(i);
			end
		end

		--[[
		-- initialize the trade skill entry
		if not self.db.realm.userdata[self.vars.playername].skills[skillName] then
			self.db.realm.userdata[self.vars.playername].skills[skillName] = {};
		end

		-- initialize the reagent map
		if not self.db.realm.userdata[self.vars.playername].reagentMap then
			self.db.realm.userdata[self.vars.playername].reagentMap = {};
		end
		--]]

		-- save the skill entry in a local var
		local skillDB = self.db.realm.userdata[self.vars.playername].skills[skillName];
		if not skillDB.recipes then
			skillDB.recipes = {};
		end

		-- record skill stats
		skillDB.skillLevel = rank;
		skillDB.skillCount = numTradeSkills;
		skillDB.lastScanDate = time();

		local rescanNotify = false;

		-- initialize window information, if necessary
		if not skillDB.window then
			skillDB.window = {
				search = "",
				filter = {
					optimal = true,
					medium = true,
					easy = true,
					trivial = true,
					haveAllReagents = false,
					haveKeyReagents = false,
					haveAnyReagents = false,
					sortDefault = true,
					sortDifficulty = false,
					sortAlpha = false,
					sortBenefit = false,
					sortItemLevel = false,
					sortRequiredLevel = false,
					sortFavorites = false,
					favorites = false,
					achievements = false,
				},
				skills = {},
				slots = {},
				categories = {},
				selected = 1,
			};
		end

		--[[
		local slots = { GetTradeSkillSubClassFilteredSlots(0) };
		for i,slot in pairs(slots) do
			SetTradeSkillInvSlotFilter(i, 0, 0);
		end
		local subClasses = { GetTradeSkillSubClasses() };
		for i,subClass in pairs(subClasses) do
			SetTradeSkillSubClassFilter(i, 0, 0);
		end
		--]]

		local category = "";
--		local rescanCount = 0;

		for i=1,GetNumTradeSkills() do
			local name, difficulty, avail, expanded, verb, numSkillUps = GetTradeSkillInfo(i);

			if name and difficulty ~= "header" then

				local minMade, maxMade = GetTradeSkillNumMade(i);

				-- adjust min/max made for specialities
				-- TODO

--[[
				-- check if we're rescanning, updating or adding
				local rescan = false;
				if skillDB.rescan then
					if skillDB.rescan.failedRecipes[name] then
						rescan = true;
					end
				end

				if skillDB.recipes[name] and not rescan then
					-- update the recipe info
					skillDB.recipes[name].index = i;
					skillDB.recipes[name].difficulty = difficulty;
					skillDB.recipes[name].available = avail;
					skillDB.recipes[name].minMade = minMade;
					skillDB.recipes[name].maxMade = maxMade;
					skillDB.recipes[name].numSkillUps = numSkillUps;

					-- update reagent skill index
					for _,r in ipairs(skillDB.recipes[name].reagents) do
						r.skillIndex = i;
					end
				else
--]]
				-- add the recipe info and other miscellaneous info
				local itemLink = GetTradeSkillItemLink(i);
				local recipeLink = GetTradeSkillRecipeLink(i);
				local _, _, _, _, _, itemType, itemSubType, _, slot, _ = GetItemInfo(itemLink);

				local keywords = name;

--[[
				if rescan then
					-- local msg = string.format(L["Rescanning recipe: %1$s..."], name);
					-- Cauldron:Print(msg);

					-- remove it from the list
					skillDB.rescan.failedRecipes[name] = nil;

					-- populate the reagent list
					local num = GetTradeSkillNumReagents(i);
					for j=1,num do
						local rName, rIcon, rCount, _ = GetTradeSkillReagentInfo(i, j);
						local rLink = GetTradeSkillReagentItemLink(i, j);
						local rItemId = Cauldron:GetIdFromLink(rLink);
						local key = not Cauldron:IsVendorItem(rItemId);

						if not skillDB.recipes[name].reagents[j].name then
							if (not rName) or (not rIcon) or (not rLink) then
--									Cauldron:MarkRecipeForRescan(skillDB, name);
--									rescanCount = rescanCount + 1;
							else
								local r = {
									["index"] = j,
									["name"] = rName,
									["numRequired"] = rCount,
									["skillIndex"] = i,
									["icon"] = rIcon,
									["link"] = rLink,
									["key"] = key,
								};

								skillDB.recipes[name].reagents[j] = r;

								if not self.db.realm.userdata[self.vars.playername].reagentMap[rName] then
									self.db.realm.userdata[self.vars.playername].reagentMap[rName] = {};
								end
								table.insert(self.db.realm.userdata[self.vars.playername].reagentMap[rName], skillName..";"..recipeLink);

								skillDB.recipes[name].keywords = skillDB.recipes[name].keywords..","..rName;
							end
						end
					end
				else
--]]
					-- create the db entry
					local recipe = {
						['index'] = i,
						['name'] = name,
						['description'] = GetTradeSkillDescription(i),
						['itemLink'] = itemLink,
						['recipeLink'] = recipeLink,
						['icon'] = GetTradeSkillIcon(i),
						['tradeskill'] = baseSkillName,
						['difficulty'] = difficulty,
						['available'] = avail,
						['minMade'] = minMade,
						['maxMade'] = maxMade,
						['numSkillUps'] = numSkillUps,
						['verb'] = verb,

						-- filter information
						['slot'] = _G[slot],
						['defaultCategory'] = category,
						['type'] = itemType,
						['subtype'] = itemSubType,

						['reagents'] = {},
					};

					-- check the reagents list to see if it should be copied
					if skillDB.recipes[name] and skillDB.recipes[name].reagents then
						if #skillDB.recipes[name].reagents > 0 then
							local copyReagents = true;
							for _,ri in ipairs(skillDB.recipes[name].reagents) do
								if not ri.name then
									copyReagents = false;
									break;
								end
							end

							if copyReagents then
								-- copy the reagents list over
								recipe.reagents = skillDB.recipes[name].reagents;
							end
						end
					end

					skillDB.recipes[name] = recipe;

					-- set the action verb for this skill
--					skillDB.recipes[name].verb = verb;

					-- make sure the skill window info is initialized
					if not skillDB.window.skills[name] then
						skillDB.window.skills[name] = {
							['expanded'] = false,
							['favorite'] = false,
						};
					end

					-- make sure the category for the window is initialized
					if category ~= "" then
						if not skillDB.window.categories[category] then
							skillDB.window.categories[category] = {
								['shown'] = true,
								['expanded'] = true,
							};
						end
					end

					--[[
					-- populate the slot list
					Cauldron:debug("slot: "..tostring(slot));
					if slot and (slot ~= "") then
						skillDB.window.slots[slot] = {
							checked = true,
						};
					else
						-- special slot representing items that don't have a slot
						skillDB.window.slots.none = {
							checked = true,
						};
					end
					--]]

					-- populate the reagent list
					Cauldron:LearnSkillReagents(recipe);
					--[==[
					local num = GetTradeSkillNumReagents(i);
					for j=1,num,1 do
						local rName, rIcon, rCount, playerAmount = GetTradeSkillReagentInfo(i, j);
						local rLink = GetTradeSkillReagentItemLink(i, j);
						local rItemId = Cauldron:GetIdFromLink(rLink);
						local key = not Cauldron:IsVendorItem(rItemId);

						if (not rName) or (not rIcon) or (not rLink) then
							rescanNotify = true;
Cauldron:error("Failed to retrieve reagent info; marking recipe for rescan: "..name);
Cauldron:warn("i="..tostring(i).."; j="..tostring(j));
--[[
							-- store the info if it's not already from rescanning
							if not rescan then
								-- Cauldron:error("Failed to retrieve reagent info; marking recipe for rescan: "..name);
--									Cauldron:MarkRecipeForRescan(skillDB, name);
--									rescanCount = rescanCount + 1;
							end
--]]
						else
							local r = {
								["index"] = j,
								["name"] = rName,
								["numRequired"] = rCount,
								["skillIndex"] = i,
								["icon"] = rIcon,
								["link"] = rLink,
								["key"] = key,
								["amount"] = playerAmount,
							};

							table.insert(skillDB.recipes[name].reagents, r);

							-- add the reagent to the reagent map
							if rName then
								if not self.db.realm.userdata[self.vars.playername].reagentMap[rName] then
									self.db.realm.userdata[self.vars.playername].reagentMap[rName] = {};
								end
								table.insert(self.db.realm.userdata[self.vars.playername].reagentMap[rName], skillName..";"..recipeLink);

								keywords = keywords..","..rName;
							end
						end
					end
					--]==]

					-- fill in the keywords db entry
					skillDB.recipes[name].keywords = keywords;
--				end
--				end
			else
				-- save the header name
				if name then
					category = name;

					-- expand the header, so we get all the skills
					if not expanded then
						ExpandTradeSkillSubClass(i);
					end
				else
					category = "Uncategorized";
				end
			end
		end

		Cauldron.scanningSkills = false;

		local endTime = GetTime();
		if Cauldron.vars.displayTimers then
			Cauldron:info("scan time: "..tostring(endTime-startTime).."ms");
		end

	local numSkills = table.getn(skillDB.recipes)
-- Cauldron:info("numSkills="..tostring(numSkills));
	Cauldron.vars.numSkills = numSkills;
	skillDB.skillCount = numSkills;
	skillDB.actualSkillCount = GetNumTradeSkills();

		--[[
		if rescanCount > 0 then
			Cauldron:Print(rescanCount.." recipes marked for rescan.");
		end
		--]]
	end

	--[[
	if rescanNotify then
		Cauldron:error(L["Cauldron had issues with some recipes for this profession."]);
		local msg = string.format(L["Please close and re-open the %1$s window again to re-scan."], baseSkillName);
		Cauldron:info(msg);
	end
	--]]

	self.updatingSkills = false;

end

function Cauldron:GetDefaultCategories(player, skillName)

	local categories = {};

	if self.db then
		for name, info in pairs(self.db.realm.userdata[player].skills[skillName].window.categories) do
			categories[name] = info.shown;
		end
	end

--	table.sort(categories);

	return categories;
end

--[[
function Cauldron:GetCategories(skillList)
	self:debug("GetCategories enter");

	local categories = {};

	if not skillList then
		return categories;
	end

	for _, info in ipairs(skillList) do
		table.insert(categories, info.defaultCategory);
	end

	table.sort(categories);

	self:debug("GetCategories exit");

	return categories;
end
--]]

function Cauldron:GetSlots(player, skillName)

	local slots = {};

	if self.db then
		for name, info in pairs(self.db.realm.userdata[player].skills[skillName].window.slots) do
			slots[name] = info;
		end
	end

--	table.sort(slots);

	return slots;
end

function Cauldron:GetSkillCount(skillName)

	if not skillName then
		skillName = CURRENT_TRADESKILL;
	end
	if not skillName then
		-- still couldn't get a skill name, so return 0
		return 0;
	end

	local skillCount = 0;

	if (Cauldron.db.realm.userdata[Cauldron.vars.playername]) and
	   (Cauldron.db.realm.userdata[Cauldron.vars.playername].skills[skillName]) then
		for _, _ in pairs(Cauldron.db.realm.userdata[Cauldron.vars.playername].skills[skillName].recipes) do
			skillCount = skillCount + 1;
		end
	end

	return skillCount;
end

function Cauldron:GetSkillNumAvailable(skillInfo)
	if not skillInfo then
		return 0;
	end
	if skillInfo.tradeskill ~= CURRENT_TRADESKILL then
		return 0;
	end

	local _, _, avail, _, _, _ = GetTradeSkillInfo(skillInfo.index);

	return avail or 0;
end

function Cauldron:LearnSkill()
end

function Cauldron:GetSkillList(playername, skillName)

	if (not playername) or
	   (not skillName) then
		self:warn("GetSkillList: playername ("..tostring(playername)..") or skillName ("..tostring(skillName)..") not set!");
		return {};
	end
	if (not self.db.realm.userdata[playername]) or
	   (not self.db.realm.userdata[playername].skills[skillName]) then
		return {};
	end

	local skills = {};

	local startTime = GetTime();

	for name, recipe in pairs(self.db.realm.userdata[playername].skills[skillName].recipes) do

		local add = true;

		local filter = self.db.realm.userdata[playername].skills[skillName].window.filter;

		-- check the search text
		local search = self.db.realm.userdata[playername].skills[skillName].window.search or "";
		if #search > 0 then
			-- check for numbers
			local minLevel, maxLevel;
--			local matchItemLevel = false;
			local approxLevel, matchItemLevel = strmatch(search, "^~(%d+)(i?)");
			-- self:debug("GetSkillList: approxLevel="..tostring(approxLevel));
			if approxLevel then
				minLevel = approxLevel - 2;
				maxLevel = approxLevel + 2;
			else
				minLevel, maxLevel, matchItemLevel = strmatch(search, "^(%d+)%s*-*%s*(%d*)(i?)$");
				-- self:debug("GetSkillList: minLevel="..tostring(minLevel).."; maxLevel="..tostring(maxLevel));
			end
			if minLevel then
				if maxLevel == "" or maxLevel == "i" or maxLevel < minLevel then
					maxLevel = minLevel;
				end
				-- cleanse the level values
				minLevel = tonumber(minLevel) or 0;
				maxLevel = tonumber(maxLevel) or 0;

				local _,_,_,itemLevel,reqLevel,_,_,_,_,_ = GetItemInfo(recipe.itemLink);
				if itemLevel and (matchItemLevel == "i") then
					itemLevel = tonumber(itemLevel) or 0;
					if itemLevel < minLevel or itemLevel > maxLevel then
						add = false;
					end
				elseif reqLevel then
					reqLevel = tonumber(reqLevel) or 0;
					if reqLevel < minLevel or reqLevel > maxLevel then
						add = false;
					end
				end
			else
				-- match name or reagents
				if not string.find(string.lower(recipe.keywords or ""), string.lower(search)) then
					add = false;
				end
			end

		end

		-- check difficulty filter
		if not filter[recipe.difficulty] then
			add = false;
		end

		--[[
		-- check categories
		local categories = self.db.realm.userdata[playername].skills[skillName].window.categories;
		local catInfo = categories[recipe.defaultCategory] or categories[recipe.type] or categories[recipe.subtype];
		if catInfo and (not catInfo.shown) then
			add = false;
		end

		-- check slot
		local slotName = recipe.slot;
		if (not slotName) or (slotName == "") then
			slotName = "none";
		end
		local slotInfo = self.db.realm.userdata[playername].skills[skillName].window.slots[slotName];
		self:debug("slotInfo: "..tostring(slotInfo));
		if slotInfo and not(slotInfo.checked) then
			add = false;
		end
		--]]

		-- check reagent filter
		if filter.haveAllReagents then
			-- check if the available count is 0
			if recipe.available == 0 then
				add = false;
			end
		elseif filter.haveKeyReagents then
			-- check if the reagent count for key reagents is 0
			for _, rinfo in ipairs(recipe.reagents) do
				-- check possession count
				if (GetItemCount(rinfo.link, false) < rinfo.numRequired) and (rinfo.key) then
					add = false;
				end
			end
		elseif filter.haveAnyReagents then
			-- check if the reagent count for any reagent is > 0
			add = false;
			for _, rinfo in ipairs(recipe.reagents) do
				-- check possession count
				if GetItemCount(rinfo.link, false) > 0 then
					add = true;
				end
			end
		end

		-- check favorites filter
		if filter.favorites then
			if not self.db.realm.userdata[playername].skills[skillName].window.skills[recipe.name].favorite then
				add = false;
			end
		end

		-- check achievements filter
		if filter.achievements then
			local achievements = Cauldron:GetAchievementsForSkill(recipe);
			if (not achievements) or (#achievements == 0) then
				add = false;
			end
		end

		-- we got here, add the recipe to the list
		if add then
			table.insert(skills, recipe);
		end
	end

	local endTime = GetTime();
	if Cauldron.vars.displayTimers then
		Cauldron:info("get skill list: "..tostring(endTime-startTime).."ms");
	end

	startTime = GetTime();

	-- sort the list
	table.sort(skills, function(r1, r2)
			if (not r1) or (not r2) then
				return true;
			end
			if not r1.name or not r2.name then
				return true;
			end

			local filter = self.db.realm.userdata[playername].skills[skillName].window.filter;
			local recipe1 = self.db.realm.userdata[playername].skills[skillName].recipes[r1.name];
			local recipe2 = self.db.realm.userdata[playername].skills[skillName].recipes[r2.name];

			if filter.sortDefault then
				local r1v = recipe1.index;
				local r2v = recipe2.index;
				return r1v < r2v;
			elseif filter.sortAlpha then
				return r1.name < r2.name;
			elseif filter.sortDifficulty then
				local difficulty = {
					optimal = 4,
					medium = 3,
					easy = 2,
					trivial = 1,
				};

				local r1v = recipe1.difficulty;
				local r2v = recipe2.difficulty;
				return difficulty[r1v] > difficulty[r2v];
			elseif filter.sortItemLevel then
				-- get item info from item link
				local _,_,_,r1iLevel,_,_,_,_,_,_ = GetItemInfo(r1.itemLink);
				local _,_,_,r2iLevel,_,_,_,_,_,_ = GetItemInfo(r2.itemLink);

				return (r2iLevel or 0) < (r1iLevel or 0);
			elseif filter.sortRequiredLevel then
				-- get item info from item link
				local _,_,_,_,r1Level,_,_,_,_,_ = GetItemInfo(r1.itemLink);
				local _,_,_,_,r2Level,_,_,_,_,_ = GetItemInfo(r2.itemLink);

				return (r2Level or 0) < (r1Level or 0);
			elseif filter.sortFavorites then
				local r1f = (self.db.realm.userdata[playername].skills[skillName].window.skills[r1.name].favorite and 100) or 1;
				local r2f = (self.db.realm.userdata[playername].skills[skillName].window.skills[r2.name].favorite and 100) or 1;

				return r2f < r1f;
			elseif filter.sortBenefit then
				return true; -- TODO
			end

			return true;
		end);

	endTime = GetTime();
	if Cauldron.vars.displayTimers then
		Cauldron:info("sort skill list: "..tostring(endTime-startTime).."ms");
	end

	return skills;
end

function Cauldron:GetSkillInfo(tradeskill, skill)

	-- sanity checks
	if (not tradeskill) or (not skill) then
		self:warn("GetSkillInfo: missing tradeskill ("..tostring(tradeskill)..") or skill ("..tostring(skill)..")!");
		return nil;
	end

	if not self.db.realm.userdata[self.vars.playername].skills[tradeskill] then
		return nil;
	end

	local skillInfo = self.db.realm.userdata[self.vars.playername].skills[tradeskill].recipes[skill];
	if not skillInfo then
		-- couldn't find a skill with the item name, so scan the list for skills that craft
		-- the item
		for _, recipe in pairs(self.db.realm.userdata[self.vars.playername].skills[tradeskill].recipes) do
			local name, _ = GetItemInfo(recipe.itemLink);
			if name == skill then
				return recipe;
			end
		end
	end

	return skillInfo;
end

function Cauldron:GetSkillInfoForItem(item)

	if not item then
		Cauldron:error("GetSkillInfoForItem: item is nil!");
		return nil;
	end

	for tradeskill, list in pairs(self.db.realm.userdata[self.vars.playername].skills) do
		-- skip linked skills
		if not (string.find(tradeskill, "Linked-")) and not (string.find(tradeskill, "Guild-")) then
			for _, recipeInfo in pairs(list.recipes) do
				local name, _ = GetItemInfo(recipeInfo.itemLink);
				if name == item then
					return recipeInfo;
				end
			end
		end
	end

	return nil;
end

function Cauldron:GetSkillInfoForLink(recipeLink)

	if not recipeLink then
		Cauldron:error("GetSkillInfoForLink: recipeLink is nil!");
		return nil;
	end

	for tradeskill, list in pairs(self.db.realm.userdata[self.vars.playername].skills) do
		-- skip linked skills
		if not (string.find(tradeskill, "Linked-")) and not (string.find(tradeskill, "Guild-")) then
			for _, recipeInfo in pairs(list.recipes) do
				local id = Cauldron:GetIdFromLink(recipeLink);
				local recipeId = Cauldron:GetIdFromLink(recipeInfo.recipeLink);
				if id == recipeId then
					return recipeInfo;
				end
			end
		end
	end

	return nil;
end

function Cauldron:GetSkillInfoByIndex(itemIndex)

	if not itemIndex then
		Cauldron:error("GetSkillInfoByIndex: itemIndex is nil!");
		return nil;
	end

	for tradeskill, list in pairs(self.db.realm.userdata[self.vars.playername].skills) do
		-- skip linked skills
		if not (string.find(tradeskill, "Linked-")) and not (string.find(tradeskill, "Guild-")) then
			for _, recipeInfo in pairs(list.recipes) do
				if recipeInfo.index == itemIndex then
					return recipeInfo;
				end
			end
		end
	end

	return nil;
end

function Cauldron:GetReagentInfoByIndex(item, reagentIndex)

	local skillInfo = nil;

	if type(item) == "table" then
		skillInfo = item;
	elseif type(item) == "number" then
		skillInfo = self:GetSkillInfoByIndex(tonumber(item));
	end

	-- learn reagents
	Cauldron:LearnSkillReagents(skillInfo);

	if skillInfo then
		for i, reagentInfo in ipairs(skillInfo.reagents) do
			if reagentInfo.index == reagentIndex then
				return reagentInfo;
			end
		end
	end

	return nil;
end

function Cauldron:GetRequiredItems(skillInfo, amount)

	local intermediates = {};
	local reagents = {};

	-- sanity checks
	if not skillInfo then
		Cauldron:error("GetRequiredItems: no skillInfo provided!");
		return intermediates, reagents;
	end

	-- get a proper amount value
	amount = math.max(1, tonumber(amount) or 1);

	-- learn reagents
	Cauldron:LearnSkillReagents(skillInfo);

	-- find out what the reagents are
	for i, reagent in ipairs(skillInfo.reagents) do

		-- copy the reagent info so we can modify the amounts
		local r = CopyTable(reagent);
		r.numRequired = r.numRequired * amount;

		-- see if the character can make the item
		local si = Cauldron:GetSkillInfoForItem(r.name);
		if si then
			table.insert(intermediates, r);
		else
			table.insert(reagents, r);
		end
	end

	return intermediates, reagents;
end

local function split(sep, str)
	local result = {};

	while str do
		local s1, rest = strsplit(sep, str, 2);
		table.insert(result, s1);
		str = rest;
	end

	return result;
end

function Cauldron:GetSkillsForReagent(id)
	if not id then
		return {};
	end

	local itemName,itemInfo,_ = GetItemInfo(id);
	local skillList = {};

	if not Cauldron.db.realm.userdata[Cauldron.vars.playername].reagentMap then
		return {};
	end

	-- find the reagent in the map
	local reagentMap = Cauldron.db.realm.userdata[Cauldron.vars.playername].reagentMap[itemName];
	if reagentMap then
		for _,skill in ipairs(reagentMap) do
			table.insert(skillList, skill);
		end
	end

	return skillList;
end