Quantcast
--[[
************************************************************************
Scanner.lua
Vendor/trainer scanning for Ackis Recipe List.
************************************************************************
File date: @file-date-iso@
File hash: @file-abbreviated-hash@
Project hash: @project-abbreviated-hash@
Project version: @project-version@
************************************************************************
Please see http://www.wowace.com/addons/arl/ for more information.
************************************************************************
This source code is released under All Rights Reserved.
************************************************************************
--- Vendor/trainer scanning for Ackis Recipe List.
@class file
@name core.lua
@release 2.0
************************************************************************
]]--

-------------------------------------------------------------------------------
-- Upvalued Lua API.
-------------------------------------------------------------------------------
local _G = getfenv(0)

-- Functions
local ipairs, pairs = _G.ipairs, _G.pairs
local tonumber, tostring = _G.tonumber, _G.tostring
local type = _G.type

-- Libraries
local bit = _G.bit
local string = _G.string
local table = _G.table

-------------------------------------------------------------------------------
-- AddOn namespace.
-------------------------------------------------------------------------------
local FOLDER_NAME, private = ...

local LibStub = _G.LibStub

local addon = LibStub("AceAddon-3.0"):GetAddon(private.addon_name)
local L = LibStub("AceLocale-3.0"):GetLocale(private.addon_name)

-------------------------------------------------------------------------------
-- Constants
-------------------------------------------------------------------------------
local F = private.filter_flags
local A = private.acquire_types

local NO_ROLE_FLAG	-- Populated at the end of the file.

-------------------------------------------------------------------------------
-- Functions/methods
-------------------------------------------------------------------------------
local function LoadRecipe()
	local recipe_list = private.recipe_list

	if addon.db.profile.autoloaddb then
		-- Make sure the lookup lists are loaded as well, since they are no longer automatically loaded in addon:OnEnable().
		if addon.InitializeLookups then
			addon:InitializeLookups()
		end

		for index, prof in pairs(private.professions) do
			addon:InitializeProfession(prof)
		end
	end
	return recipe_list
end

-------------------------------------------------------------------------------
-- Creates a reverse lookup for a recipe list
-------------------------------------------------------------------------------
local GetReverseLookup
do
	local reverse_lookup = {}

	function GetReverseLookup(recipe_list)
		if not recipe_list then
			addon:Print(L["DATAMINER_NODB_ERROR"])
			return
		end
		table.wipe(reverse_lookup)

		for i in pairs(recipe_list) do
			--if t[recipe_list[i].name] then addon:Print("Dupe: " .. i) end
			reverse_lookup[recipe_list[i].name] = i
		end
		return reverse_lookup
	end

end

-------------------------------------------------------------------------------
-- Tooltip for data-mining.
-------------------------------------------------------------------------------
local ARLDatamineTT = _G.CreateFrame("GameTooltip", "ARLDatamineTT", _G.UIParent, "GameTooltipTemplate")

do
	-- Tables used in addon:ScanTrainerData
	local scanned_recipes, scanned_items, output = {}, {}, {}
	local missing_spell_ids, extra_spell_ids, fixed_item_spell_ids = {}, {}, {}
	local mismatched_levels, mismatched_levels_unconfirmed = {}, {}
	local itemless_spells = {}

	--- Function to compare which recipes are available from a trainer and compare with the internal ARL database.
	-- @name AckisRecipeList:ScanTrainerData
	-- @param autoscan True when autoscan is enabled in preferences, it will surpress output letting you know when a scan has occured.
	-- @return Does a comparison of the information in your internal ARL database, and those items which are available on the trainer.
	-- Compares the acquire information of the ARL database with what is available on the trainer.
	function addon:ScanTrainerData(autoscan)
		if not (_G.UnitExists("target") and (not _G.UnitIsPlayer("target")) and (not _G.UnitIsEnemy("player", "target"))) then	-- Make sure the target exists and is a NPC
			if not autoscan then
				self:Print(L["DATAMINER_TRAINER_NOTTARGETTED"])
			end
			return
		end

		if not _G.IsTradeskillTrainer() then
			if not autoscan then
				self:Print(L["DATAMINER_SKILLLEVEL_ERROR"])
			end
			return
		end
		local recipe_list = LoadRecipe()	-- Get internal database

		if not recipe_list then
			self:Print(L["DATAMINER_NODB_ERROR"])
			return
		end

		-- Get the initial trainer filters
		local avail = _G.GetTrainerServiceTypeFilter("available")
		local unavail = _G.GetTrainerServiceTypeFilter("unavailable")
		local used = _G.GetTrainerServiceTypeFilter("used")

		-- Clear the trainer filters
		_G.SetTrainerServiceTypeFilter("available", 1)
		_G.SetTrainerServiceTypeFilter("unavailable", 1)
		_G.SetTrainerServiceTypeFilter("used", 1)

		if _G.GetNumTrainerServices() == 0 then
			self:Print("Warning: Trainer is bugged, reporting 0 trainer items.")
		end
		table.wipe(scanned_items)
		table.wipe(scanned_recipes)

		local trainer_profession = _G.GetTrainerServiceSkillLine(1)

		for index = 1, _G.GetNumTrainerServices(), 1 do
			local item_name = _G.GetTrainerServiceInfo(index)
			local item_id = private.ItemLinkToID(_G.GetTrainerServiceItemLink(index))
			local _, skill_level = _G.GetTrainerServiceSkillReq(index)

			if not skill_level then
				skill_level = 0
			end

			if item_id then
				scanned_items[item_id] = skill_level
			else
				scanned_recipes[item_name] = skill_level
			end
		end
		table.wipe(missing_spell_ids)
		table.wipe(extra_spell_ids)
		table.wipe(fixed_item_spell_ids)
		table.wipe(mismatched_levels)
		table.wipe(mismatched_levels_unconfirmed)
		table.wipe(output)

		-- Dump out trainer info
		local trainer_id = tonumber(string.sub(_G.UnitGUID("target"), -12, -9), 16)
		local trainer_name = _G.UnitName("target")

		table.insert(output, "ARL Version: @project-version@")
		table.insert(output, L["DATAMINER_TRAINER_INFO"]:format(trainer_name, trainer_id))

		for spell_id, recipe in pairs(recipe_list) do
			if trainer_profession == recipe.profession then
				local train_data = recipe.acquire_data[A.TRAINER]
				local matching_trainer = false

				if train_data then
					for id_num in pairs(train_data) do
						if id_num == trainer_id then
							matching_trainer = true
							break
						end
					end
				end
				local matching_item = scanned_items[recipe:CraftedItemID()]
				local matching_recipe = scanned_recipes[recipe.name]

				if recipe:CraftedItemID() then
					matching_recipe = nil
				end

				if matching_item or matching_recipe then
					if not matching_trainer then
						recipe:AddTrainer(trainer_id)
						table.insert(missing_spell_ids, spell_id)

						if not recipe:HasFilter("common1", "TRAINER") then
							recipe:AddFilters(F.TRAINER)

							if matching_item then
								table.insert(output, ("Added trainer flag to recipe with spell ID %d. (matching crafted item ID %d)"):format(spell_id, recipe:CraftedItemID()))
							elseif matching_recipe then
								table.insert(output, ("Added trainer flag to recipe with spell ID %d. (matching recipe name \"%s\")"):format(spell_id, recipe.name))
							end
						end
					end
					local recipe_skill = recipe:SkillLevels()

					if matching_item and matching_item ~= recipe_skill then
						table.insert(mismatched_levels, spell_id)
					elseif matching_recipe and matching_recipe ~= recipe_skill then
						table.insert(mismatched_levels_unconfirmed, spell_id)
					end
				elseif matching_trainer then
					table.wipe(itemless_spells)

					if not recipe:CraftedItemID() then
						for item_id in pairs(scanned_items) do
							if recipe.name == _G.GetItemInfo(item_id) then
								recipe:SetCraftedItemID(item_id)
								itemless_spells[spell_id] = true
							end
						end
					end

					if itemless_spells[spell_id] then
						table.insert(fixed_item_spell_ids, spell_id)
					else
						table.insert(extra_spell_ids, spell_id)
					end
				end
			end
		end
		local found_missing = #missing_spell_ids > 0
		local found_extra = #extra_spell_ids > 0
		local found_fixed_item = #fixed_item_spell_ids > 0
		local found_wrong_level = #mismatched_levels > 0
		local found_unconfirmed_level = #mismatched_levels_unconfirmed > 0

		if found_missing then
			table.insert(output, "\nTrainer is missing from the following entries:")
			table.sort(missing_spell_ids)

			for index in ipairs(missing_spell_ids) do
				local spell_id = missing_spell_ids[index]
				table.insert(output, ("%d (%s)"):format(spell_id, recipe_list[spell_id].name))
			end
		end

		if found_extra then
			table.insert(output, "\nRecipes which are wrongly assigned to the trainer:")
			table.sort(extra_spell_ids)

			for index in ipairs(extra_spell_ids) do
				local spell_id = extra_spell_ids[index]
				table.insert(output, ("%d (%s)"):format(spell_id, recipe_list[spell_id].name))
			end
		end

		if found_fixed_item then
			table.insert(output, "\nRecipes which had no crafted item ID, but will once a dump is performed:")
			table.sort(fixed_item_spell_ids)

			for index in ipairs(fixed_item_spell_ids) do
				local spell_id = fixed_item_spell_ids[index]
				table.insert(output, ("%d (%s)"):format(spell_id, recipe_list[spell_id].name))
			end
		end

		if found_wrong_level then
			table.insert(output, "\nRecipes which had an incorrect skill level, but will not once a dump is performed:")
			table.sort(mismatched_levels)

			for index in ipairs(mismatched_levels) do
				local spell_id = mismatched_levels[index]
				local recipe = recipe_list[spell_id]
				local recipe_skill = recipe:SkillLevels()
				local corrected_skill = scanned_items[recipe:CraftedItemID()]
				table.insert(output, ("%d (%s): Corrected skill level from %d to %d."):format(spell_id, recipe.name, recipe_skill, corrected_skill))
				recipe:SetSkillLevels(corrected_skill)
			end
		end

		if found_unconfirmed_level then
			table.insert(output, "\nRecipes with possible incorrect skill levels - unable to confirm:")
			table.sort(mismatched_levels_unconfirmed)

			for index in ipairs(mismatched_levels_unconfirmed) do
				local spell_id = mismatched_levels_unconfirmed[index]
				local recipe = recipe_list[spell_id]
				local recipe_skill = recipe:SkillLevels()
				table.insert(output, ("%d (%s): Skill set to %d; trainer reports %d."):format(spell_id, recipe.name, recipe_skill, scanned_recipes[recipe.name]))
			end
		end

		if found_missing or found_extra or found_fixed_item or found_wrong_level or found_unconfirmed_level then
			if found_extra and trainer_profession == private.professions.Engineering then
				table.insert(output, "\nSome goggles may be listed as extra. These goggles ONLY show up for the classes who can make them, so they may be false positives.")
			end

			self:DisplayTextDump(nil, nil, table.concat(output, "\n"))
		end
		-- Reset the filters to what they were before
		_G.SetTrainerServiceTypeFilter("available", avail or 0)
		_G.SetTrainerServiceTypeFilter("unavailable", unavail or 0)
		_G.SetTrainerServiceTypeFilter("used", used or 0)
	end
end	-- do

--- Generates tradeskill links for all professions so you can scan them for completeness.
-- @name AckisRecipeList:GenerateLinks
-- @return Generates tradeskill links with all recipes.  Used for testing to see if a recipe is missing from the database or not.
function addon:GenerateLinks()
	-- This code adopted from Gnomish Yellow Pages with permission

	local guid = _G.UnitGUID("player")
	local playerGUID = string.gsub(guid, "0x0+", "")

	-- Listing of all tradeskill professions
	local tradelist = {51304, 51300, 51313, 51306, 45363, 51311, 51302, 51309, 51296, 45542}

--[[
	local encodingLength = floor((#recipeList+5) / 6)

	local encodedString = string.rep("/", encodingLength)
]]--
	local bitmap = {}
	bitmap[45542] = "8bffAA" -- First Aid  100%   17/17 recipes
	--bitmap[51296] = "2/6///7///9f///7//////////g///B" -- Cooking  98.2%  166/169 recipes
	bitmap[51296] = "GEgAGAjmFAwBAxAzfex//DgAgPA6HtgAI8lC"
	--bitmap[51306] = "4/////////////3nFA+///9+/P7//f//n//9dgdJgHA87/3f/TolD" -- Engineering (53)
	bitmap[51306] = "4/////////////3nFA+///9+/P7//f//n//9dgdJgHA87/3f/Tol3B" -- Engineering
	bitmap[51302] = "e+//ff////d//u//v//n+vv7/+ujr7/9////bg/+////f344//dA4I8j/X//B4/vu5/////////////////nvA8/M8/D" -- Leatherworking  85.4%   451/528 recipes
	bitmap[51304] = "2//v//////f////3//v///////5//////////9/////v" -- Alchemy 100%   251/251 recipes
	bitmap[51300] = "2//////f7fM//f/f53/+//7///ze8c6q/f9///P2/////m4BAA2XIG+dFA8//PC////4//z//////////Pwvy//H" -- Blacksmithing 82.9%   413/498 recipes
	bitmap[51309] = "4//+/76J7//v7/ve+/XR9fLb2f3nN3vLc6vS0+Hf8/XG7/VAIh9B+/f6/////3/////9/f1wCB" -- Tailoring   83.37%   341/409 recipes
	bitmap[51311] = "8//3////fzj//u//////v/7///9///33////////Pw////////////////////////3///////////////////////////D" -- Jewelcrafting  97.5%   540/554 recipes
	bitmap[45363] = "g////7////3///////////////////////////////////////////////////f///////////f" --Inscription 99.77%    439/440 recipes
	bitmap[51313] = "4//////////7///////////w//+//9/n7///////3f//////ZsD" -- Enchanting   94.3%    279/296 recipes

	for i in pairs(tradelist) do

		local tradeName = _G.GetSpellInfo(tradelist[i])
		local tradelink = {}
		tradelink[1] = "|cffffd000|Htrade:"
		tradelink[2] = tradelist[i]
		tradelink[3] = ":450:450:"
		tradelink[4] = playerGUID
		tradelink[5] = ":"
		tradelink[6] = bitmap[tradelist[i]]
		tradelink[7] = "|h["
		tradelink[8] = tradeName
		tradelink[9] = "]|h|r"

		if (bitmap[tradelist[i]]) then
			self:Print(table.concat(tradelink, ""))
		else
			self:Print("There currently is not a generated tradeskill link for: " .. tradeName)
		end
		-- /script DEFAULT_CHAT_FRAME:AddMessage(string.gsub(GetTradeSkillListLink(), "\124", "\124\124"))
	end

end

do
	local ORDERED_PROFESSIONS = private.ordered_professions

	local recipe_list = {}
	local output = {}

	local function Sort_AscID(a, b)
		local reca, recb = private.recipe_list[a], private.recipe_list[b]

		return reca.spell_id < recb.spell_id
	end

	local function SortRecipesByID()
		local sorted_recipes = addon.sorted_recipes
		table.wipe(sorted_recipes)

		for n, v in pairs(recipe_list) do
			table.insert(sorted_recipes, n)
		end
		table.sort(sorted_recipes, Sort_AscID)
	end

	-------------------------------------------------------------------------------
	--- Scans the items in the specified profession
	-------------------------------------------------------------------------------
	local function ProfessionScan(prof_name)
		local master_list = LoadRecipe()

		if not master_list then
			addon:Print(L["DATAMINER_NODB_ERROR"])
			return
		end
		table.wipe(recipe_list)

		if prof_name == private.professions["Smelting"]:lower() then
			prof_name = private.mining_name:lower()
		end

		for i in pairs(master_list) do
			local prof = string.lower(master_list[i].profession)

			if prof and prof == prof_name then
				recipe_list[i] = master_list[i]
			end
		end
		SortRecipesByID()
		table.wipe(output)

		-- Parse the entire recipe database
		for index, id in ipairs(addon.sorted_recipes) do
			local ttscantext = addon:TooltipScanRecipe(id, false, true)

			if ttscantext and ttscantext ~= "" then
				table.insert(output, ttscantext)
			end
		end

		if #output == 0 then
			addon:Debug("ProfessionScan(): output is empty.")
		end
		addon:DisplayTextDump(nil, nil, table.concat(output, "\n"))
		ARLDatamineTT:Hide()
	end

	--- Parses all recipes for a specified profession, scanning their tool tips.
	-- @name AckisRecipeList:ScanProfession
	-- @usage AckisRecipeList:ScanProfession("first aid")
	-- @param prof_name The profession name or the spell ID of it, which you wish to scan.
	-- @return Recipes in the given profession have their tooltips scanned.
	function addon:ScanProfession(prof_name)
		if type(prof_name) == "number" then
			prof_name = _G.GetSpellInfo(prof_name)
		end

		local found = false
		prof_name = string.lower(prof_name)

		local scan_all = prof_name == "all"

		if not scan_all then
			for idx, name in ipairs(ORDERED_PROFESSIONS) do
				if prof_name == name:lower() then
					found = true
					break
				end
			end

			if not found then
				self:Print(L["DATAMINER_NODB_ERROR"])
				return
			end

			ProfessionScan(prof_name)
		else
			for idx, name in ipairs(ORDERED_PROFESSIONS) do
				ProfessionScan(name:lower())
			end
		end
	end

	local function RecipeDump(id, single)
		local recipe = private.recipe_list[id or 1]

		if single and not recipe then
			addon:Print("Invalid recipe ID.")
			return
		end
		recipe:Dump(output)
	end

	-------------------------------------------------------------------------------
	--- Dumps the items in the specified profession
	-------------------------------------------------------------------------------
	local function ProfessionDump(prof_name)
		local master_list = LoadRecipe()

		if not master_list then
			addon:Print(L["DATAMINE_NODB_ERROR"])
			return
		end
		table.wipe(recipe_list)

		if prof_name == private.professions["Smelting"]:lower() then
			prof_name = private.mining_name:lower()
		end
		for i in pairs(master_list) do
			local prof = string.lower(master_list[i].profession)

			if prof and prof == prof_name then
				recipe_list[i] = master_list[i]
			end
		end
		SortRecipesByID()
		table.wipe(output)

		for index, id in ipairs(addon.sorted_recipes) do
			RecipeDump(id, false)
		end
		addon:DisplayTextDump(nil, nil, table.concat(output, "\n"))
	end

	function addon:DumpRecipe(id_num)
		table.wipe(output)
		RecipeDump(id_num, true)
		addon:DisplayTextDump(nil, nil, table.concat(output, "\n"))
	end

	function addon:DumpProfession(prof_name)
		if type(prof_name) == "number" then
			prof_name = _G.GetSpellInfo(prof_name)
		end

		local found = false
		prof_name = string.lower(prof_name)

		local scan_all = prof_name == "all"

		if not scan_all then
			for idx, name in ipairs(ORDERED_PROFESSIONS) do
				if prof_name == name:lower() then
					found = true
					break
				end
			end

			if not found then
				self:Print(L["DATAMINER_NODB_ERROR"])
				return
			end
			ProfessionDump(prof_name)
		else
			for idx, name in ipairs(ORDERED_PROFESSIONS) do
				ProfessionDump(name:lower())
			end
		end
	end

	-------------------------------------------------------------------------------
	--- Dumps the trainers for the specified recipe/profession
	-------------------------------------------------------------------------------
	local trainer_registry = {}
	local sorted_data = {}

	local function ProfessionTrainerDump(prof_name)
		local master_list = LoadRecipe()

		if not master_list then
			addon:Print(L["DATAMINE_NODB_ERROR"])
			return
		end
		table.wipe(recipe_list)

		if prof_name == private.professions["Smelting"]:lower() then
			prof_name = private.mining_name:lower()
		end
		for i in pairs(master_list) do
			local prof = master_list[i].profession:lower()

			if prof and prof == prof_name then
				recipe_list[i] = master_list[i]
			end
		end
		SortRecipesByID()
		table.wipe(output)
		table.wipe(trainer_registry)
		table.wipe(sorted_data)

		for index, spell_id in ipairs(addon.sorted_recipes) do
			private.recipe_list[spell_id]:DumpTrainers(trainer_registry)
		end

		for identifier in pairs(trainer_registry) do
			table.insert(sorted_data, identifier)
		end
		table.sort(sorted_data)

		for index, identifier in ipairs(sorted_data) do
			local trainer = private.trainer_list[identifier]
			local trainer_name

			if trainer then
				if trainer.spell_id then
					table.insert(output, ("AddTrainer(%s, %s, \"%s\", %s, %s, \"%s\")"):format(identifier, trainer.spell_id, trainer.location, trainer.coord_x, trainer.coord_y, trainer.faction))
				else
					table.insert(output, ("AddTrainer(%s, \"%s\", \"%s\", %s, %s, \"%s\")"):format(identifier, trainer.name:gsub("\"", "\\\""), trainer.location, trainer.coord_x, trainer.coord_y, trainer.faction))
				end
			end
		end
		addon:DisplayTextDump(nil, nil, table.concat(output, "\n"))
	end

	function addon:DumpTrainers(prof_name)
		if type(prof_name) == "number" then
			prof_name = _G.GetSpellInfo(prof_name)
		end

		local found = false
		prof_name = string.lower(prof_name)

		local scan_all = prof_name == "all"

		if not scan_all then
			for idx, name in ipairs(ORDERED_PROFESSIONS) do
				if prof_name == name:lower() then
					found = true
					break
				end
			end

			if not found then
				self:Print(L["DATAMINER_NODB_ERROR"])
				return
			end
			ProfessionTrainerDump(prof_name)
		else
			for idx, name in ipairs(ORDERED_PROFESSIONS) do
				ProfessionTrainerDump(name:lower())
			end
		end
	end
end	-- do

local RECIPE_TYPES = {
	-- JC
	["design: "] = true,
	-- LW or Tailoring
	["pattern: "] = true,
	-- Alchemy or Cooking
	["recipe: "] = true,
	-- BS
	["plans: "] = true,
	-- Enchanting
	["formula: "] = true,
	-- Engineering
	["schematic: "] = true,
	-- First Aid
	["manual: "] = true,

	["alchemy: "] = true,
	["blacksmithing: "] = true,
	["cooking: "] = true,
	["enchanting: "] = true,
	["engineering: "] = true,
	["first aid: "] = true,
	["inscription: "] = true,
	["jewelcrafting: "] = true,
	["leatherworking: "] = true,
	["tailoring: "] = true,
}

--- Scans the items on a vendor, determining which recipes are available if any and compares it with the database entries
-- @name AckisRecipeList:ScanVendor
-- @usage AckisRecipeList:ScanVendor()
-- @return Obtains all the vendor information on tradeskill recipes and attempts to compare the current vendor with the internal database
do
	local output = {}
	local RECIPE_ITEM_TO_SPELL_MAP

	function addon:ScanVendor()
		if not (_G.UnitExists("target") and (not _G.UnitIsPlayer("target")) and (not _G.UnitIsEnemy("player", "target"))) then	-- Make sure the target exists and is a NPC
			self:Print(L["DATAMINER_VENDOR_NOTTARGETTED"])
			return
		end
		local recipe_list = LoadRecipe()		-- Get internal database

		if not recipe_list then
			self:Print(L["DATAMINER_NODB_ERROR"])
			return
		end
		local vendor_name = _G.UnitName("target")
		local vendor_id = tonumber(string.sub(_G.UnitGUID("target"), -12, -9), 16)
		local added_output = false

		table.wipe(output)

		table.insert(output, "ARL Version: @project-version@")
		table.insert(output, L["DATAMINER_VENDOR_INFO"]:format(vendor_name, vendor_id))

		if not RECIPE_ITEM_TO_SPELL_MAP then
			RECIPE_ITEM_TO_SPELL_MAP = {}

			for spell_id, recipe in pairs(private.recipe_list) do
				local recipe_item_id = recipe:RecipeItemID()

				if recipe_item_id then
					RECIPE_ITEM_TO_SPELL_MAP[recipe_item_id] = spell_id
				elseif recipe:HasFilter("common1", "VENDOR") or recipe:HasFilter("common1", "INSTANCE") or recipe:HasFilter("common1", "RAID") or recipe:HasFilter("common1", "MOB_DROP") or recipe:HasFilter("common1", "WORLD_DROP") then
					self:Debug("Recipe %d (%s) is missing a recipe item ID.", spell_id, recipe.name)
				end
			end
		end

		for index = 1, _G.GetMerchantNumItems(), 1 do
			local item_name, _, _, _, supply = _G.GetMerchantItemInfo(index)

			if item_name then
				local match_text = string.match(item_name, "%a+: ")

				if match_text and RECIPE_TYPES[match_text:lower()] then
					local item_id = private.ItemLinkToID(_G.GetMerchantItemLink(index))
					local spell_id = RECIPE_ITEM_TO_SPELL_MAP[item_id]

					if spell_id then
						local scanned_text = addon:TooltipScanRecipe(spell_id, true, true)

						if scanned_text and scanned_text ~= "" then
							added_output = true
							table.insert(output, scanned_text)
						end

						-- Check the database to see if the vendor is listed as an acquire method.
						local recipe = recipe_list[spell_id]
						local acquire = recipe and recipe.acquire_data
						local vendor_data = acquire and acquire[A.VENDOR]
						local rep_data = acquire and acquire[A.REPUTATION]
						local found_recipe = false

						if vendor_data then
							for id_num in pairs(vendor_data) do
								if id_num == vendor_id then
									found_recipe = true
									break
								end
							end
						elseif rep_data then
							for id_num, info in pairs(rep_data) do
								if found_recipe then
									break
								end

								for rep_level, level_info in pairs(info) do
									for rep_vendor_id in pairs(level_info) do
										if rep_vendor_id == vendor_id then
											found_recipe = true
										end
									end
								end
							end
						end

						if not found_recipe then
							if supply > -1 then
								recipe:AddLimitedVendor(vendor_id, supply)
							else
								recipe:AddVendor(vendor_id)
							end

							if not recipe:HasFilter("common1", "VENDOR") then
								recipe:AddFilters(F.VENDOR)
								table.insert(output, ("%d: Vendor flag needs to be set."):format(spell_id))
							end
							added_output = true
							table.insert(output, ("Vendor ID missing from \"%s\" %d."):format(recipe and recipe.name or _G.UNKNOWN, spell_id))
						else
							local reported_supply = private.vendor_list[vendor_id].item_list[spell_id]

							if reported_supply == true and supply > -1 then
								recipe:AddLimitedVendor(vendor_id, supply)
								table.insert(output, ("Limited quantity for \"%s\" (%d) found on vendor %d - listed as unlimited quantity."):format(recipe.name, spell_id, vendor_id))
								added_output = true
							elseif type(reported_supply) ~= "boolean" and supply == -1 then
								recipe:AddVendor(vendor_id)
								table.insert(output, ("Unlimited quantity for \"%s\" (%d) found on vendor %d - listed as limited quantity."):format(recipe.name, spell_id, vendor_id))
								added_output = true
							end

							if not recipe:HasFilter("common1", "VENDOR") then
								recipe:AddFilters(F.VENDOR)
								table.insert(output, ("%d: Vendor flag needs to be set."):format(spell_id))
								added_output = true
							end
						end
					else
						for spell_id, recipe in pairs(private.recipe_list) do
							local recipe_type, match_text = (":"):split(item_name, 2)

							if recipe.name == match_text:trim() then
								recipe:SetRecipeItemID(item_id)
							end
						end
						--@debug@
						added_output = true
						table.insert(output, ("Spell ID not found for recipe item %d (%s)"):format(item_id, item_name))
						--@end-debug@
					end
				end
			end
		end

		if added_output then
			self:DisplayTextDump(nil, nil, table.concat(output, "\n"))
		end
		ARLDatamineTT:Hide()
	end
end	-- do

--- Parses all the recipes in the database, and scanning their tooltips.
-- @name AckisRecipeList:TooltipScanDatabase
-- @usage AckisRecipeList:TooltipScanDatabase()
-- @return Entire recipe database has its tooltips scanned.
do
	local output = {}

	function addon:TooltipScanDatabase()
		-- Get internal database
		local recipe_list = LoadRecipe()

		if (not recipe_list) then
			self:Print(L["DATAMINER_NODB_ERROR"])
			return
		end
		table.wipe(output)

		-- Parse the entire recipe database
		for i in pairs(recipe_list) do

			local ttscantext = addon:TooltipScanRecipe(i, false, true)
			if (ttscantext) then
				table.insert(output, ttscantext)
			end
		end
		self:DisplayTextDump(nil, nil, table.concat(output, "\n"))
	end
end	-- do
--- Parses a specific recipe in the database, and scanning its tooltip
-- @name AckisRecipeList:TooltipScanRecipe
-- @param spell_id The [[[http://www.wowpedia.org/SpellLink|Spell ID]]] of the recipe being added to the database
-- @param is_vendor Boolean to determine if we're viewing a vendor or not
-- @param is_largescan Boolean to determine if we're doing a large scan
-- @return Recipe has its tooltips scanned


-- Table to store scanned information. Wiped and re-used every scan.
local scan_data = {}

do
	---------------------------------------------------------------------------------------------------------
	----This table, DO_NOT_SCAN, contains itemid's that will not cache on the servers
	---------------------------------------------------------------------------------------------------------

	local DO_NOT_SCAN = {
		-------------------------------------------------------------------------------
		--Leatherworking
		-------------------------------------------------------------------------------
		[35214] = true,	[32434] = true,	[15769] = true,
		[32431] = true,	[32432] = true,	[35215] = true,	[35521] = true,
		[35520] = true,	[35524] = true,	[35517] = true,	[35528] = true,
		[35527] = true,	[35523] = true,	[35549] = true,	[35218] = true,
		[35217] = true,	[35216] = true,	[35546] = true,	[35541] = true, [15756] = true,
		[15777] = true,  [32433] = true,  [29729] = true,  [29732] = true,   [32744] = true,
		[15773] = true,	[30301] = true,	[15745] = true,	[35302] = true,	[15774] = true,

		-------------------------------------------------------------------------------
		--Tailoring
		-------------------------------------------------------------------------------
		[14477] = true, [14485] = true, [30281] = true, [14478] = true, [14500] = true,
		[32439] = true, [14479] = true, [32447] = true, [14480] = true, [32437] = true,
		[14495] = true, [14505] = true, [35204] = true,  [35205] = true,   [35206] = true,
		[14473] = true, [14488] = true,  [14481] = true,
		[35309] = true,	[30280] = true,	[14492] = true,	[14491] = true,

		-------------------------------------------------------------------------------
		--Jewelcrafting
		-------------------------------------------------------------------------------
		[23130] = true,  [23140] = true, [23137] = true, [23131] = true, [23148] = true,
		[35538] = true, [35201] = true, [35533] = true, [35200] = true,  [23147] = true,
		[23135] = true,  [35203] = true, [35198] = true, [23152] = true,  [23151] = true,
		[23141] = true, [28596] = true,  [28291] = true,	[23153] = true,

		-------------------------------------------------------------------------------
		--Alchemy
		-------------------------------------------------------------------------------
		[22925] = true,  [13480] = true,  [13481] = true,   [13493] = true,	[35295] = true,
		[44568] = true,

		-------------------------------------------------------------------------------
		--Cooking
		-------------------------------------------------------------------------------
		[39644] = true,

		-------------------------------------------------------------------------------
		--Blacksmithing
		-------------------------------------------------------------------------------
		[32441] = true,   [32443] = true,  [12687] = true, [12714] = true,  [12688 ] = true,
		[35211] = true,  [35209] = true, [35210] = true,    [12706] = true,  [7982] = true,
		[12718] = true,		[23621] = true,	[35208] = true,	[12716] = true,	[23632] = true,
		[23633] = true,	[30324] = true,	[23637] = true,	[31393] = true,[22221] = true,
		[12690] = true,	[31394] = true,[31395] = true,[23630] = true,[23629] = true,[7978] = true,
		[41120] = true,	[12717] = true,[22219] = true,[23627] = true,

		-------------------------------------------------------------------------------
		--Engineering
		-------------------------------------------------------------------------------
		[35196] = true,    [21734] = true,  [18292] = true,  [21727] = true,  [21735] = true,  [16053] = true,  [21729] = true,
		[16047] = true,[21730] = true,[21731] = true,[21732] = true,[4411] = true,   [21733] = true,  [21728] = true,
		[35186] = true,	[18655] = true,

		-------------------------------------------------------------------------------
		--Enchanting
		-------------------------------------------------------------------------------
		[16222] = true,	[20734] = true,	[20729] = true,	[20731] = true,	[16246] = true,
	}

	local output = {}

	function addon:TooltipScanRecipe(spell_id, is_vendor, is_largescan)
		local recipe_list = LoadRecipe()

		if not recipe_list then
			self:Print(L["DATAMINER_NODB_ERROR"])
			return
		end
		local recipe = recipe_list[spell_id]

		if not recipe then
			self:Debug("Spell ID %d does not exist in the database.", tonumber(spell_id))
			return
		end
		local recipe_name = recipe.name
		local game_vers = private.game_versions[recipe.genesis]

		table.wipe(output)

		if not game_vers then
			table.insert(output, "No expansion information: " .. tostring(spell_id) .. " " .. recipe_name)
		elseif game_vers > #private.game_version_names then
			table.insert(output, "Expansion information too high: " .. tostring(spell_id) .. " " .. recipe_name)
		end
		local optimal = recipe.optimal_level
		local medium = recipe.medium_level
		local easy = recipe.easy_level
		local trivial = recipe.trivial_level
		local skill_level = recipe.skill_level

		if not optimal then
			table.insert(output, "No skill level information: " .. tostring(spell_id) .. " " .. recipe_name)
		else
			-- Highest level is greater than the skill of the recipe
			if optimal > skill_level then
				table.insert(output, "Skill Level Error (optimal_level > skill_level): " .. tostring(spell_id) .. " " .. recipe_name)
			elseif optimal < skill_level then
				table.insert(output, "Skill Level Error (optimal_level < skill_level): " .. tostring(spell_id) .. " " .. recipe_name)
			end

			-- Level info is messed up
			if optimal > medium or optimal > easy or optimal > trivial or medium > easy or medium > trivial or easy > trivial then
				table.insert(output, "Skill Level Error: " .. tostring(spell_id) .. " " .. recipe_name)
			end
		end
		local recipe_link = _G.GetSpellLink(recipe.spell_id)

		if not recipe_link then
			if recipe.profession ~= private.runeforging_name then		-- Lets hide this output for runeforging.
				self:Printf("Missing spell_link for ID %d (%s).", spell_id, recipe_name)
			end
			return
		end
		ARLDatamineTT:SetOwner(_G.WorldFrame, "ANCHOR_NONE")
		_G.GameTooltip_SetDefaultAnchor(ARLDatamineTT, _G.UIParent)

		ARLDatamineTT:SetHyperlink(recipe_link)

		-- Check to see if this is a recipe tooltip.
		local text = string.lower(_G["ARLDatamineTTTextLeft1"]:GetText())
		local match_text = string.match(text, "%a+: ")

		if not RECIPE_TYPES[match_text] and not (string.find(text, "smelt") or string.find(text, "sunder") or string.find(text, "shatter")) then
			ARLDatamineTT:Hide()
			return
		end
		local reverse_lookup = GetReverseLookup(recipe_list)

		local recipe_item_id = recipe:RecipeItemID()

		table.wipe(scan_data)

		if recipe_item_id then
			if recipe:HasFilter("common1", "TRAINER") and not recipe:HasFilter("common1", "VENDOR") and not recipe:HasFilter("common1", "INSTANCE") and not recipe:HasFilter("common1", "RAID") and not recipe:HasFilter("common1", "WORLD_DROP") then
				table.insert(output, ("Recipe %d (%s): Has Trainer filter flag, but also has a recipe item (%d)."):format(recipe.spell_id, recipe.name, recipe_item_id))
			elseif not DO_NOT_SCAN[recipe_item_id] then
				local item_name, item_link, item_quality = _G.GetItemInfo(recipe_item_id)

				if item_name then
					if item_quality > 0 then
						scan_data.quality = item_quality

						ARLDatamineTT:SetHyperlink(item_link)
						self:ScanToolTip(recipe_name, recipe_list, reverse_lookup, is_vendor)
					else
						table.insert(output, ("Recipe %d (%s): Recipe item quality is 0 (junk), which probably means it has been removed from the game."):format(recipe.spell_id, recipe.name))
					end
				else
					table.insert(output, ("%s: %d"):format(recipe.name, spell_id))

					if _G.Querier then
						table.insert(output, ("    Recipe item not in cache. To fix: /iq %d"):format(recipe_item_id))
					else
						table.insert(output, "    Recipe item not in cache.")
					end
				end
			end
		elseif not recipe_item_id and not recipe:HasFilter("common1", "RETIRED") then
			-- We are dealing with a recipe that does not have an item to learn it from.
			-- Lets check the recipe flags to see if we have a data error and the item should exist
			if recipe:HasFilter("common1", "VENDOR") or recipe:HasFilter("common1", "INSTANCE") or recipe:HasFilter("common1", "RAID") then
				table.insert(output, ("Recipe %d (%s) is missing a recipe item ID."):format(spell_id, recipe.name))
			elseif recipe:HasFilter("common1", "TRAINER") and recipe.quality ~= private.item_qualities["COMMON"] then
				table.insert(output, ("%s: %d"):format(recipe.name, spell_id))
				table.insert(output, ("    Wrong quality: Q.%s - should be Q.COMMON."):format(private.item_quality_names[recipe.quality]))
			end
		end
		ARLDatamineTT:Hide()

		-- Add the flag scan to the table if it's not nil
		local results = self:PrintScanResults()

		if results then
			table.insert(output, results)
		end

		if is_largescan then
			return table.concat(output, "\n")
		else
			self:Print(table.concat(output, "\n"))
		end
	end
end	-- do

-------------------------------------------------------------------------------
-- Tooltip-scanning code
-------------------------------------------------------------------------------
do
	local SPECIALTY_TEXT = {
		["requires spellfire tailoring"] = 26797,
		["requires mooncloth tailoring"] = 26798,
		["requires shadoweave tailoring"] = 26801,
		["requires dragonscale leatherworking"] = 10657,
		["requires elemental leatherworking"] = 10659,
		["requires tribal leatherworking"] = 10661,
		["requires gnomish engineer"] = 20219,
		["requires goblin engineer"] = 20222,
		["requires armorsmith"] = 9788,
		["requires master axesmith"] = 17041,
		["requires master hammersmith"] = 17040,
		["requires master swordsmith"] = 17039,
		["requires weaponsmith"] = 9787,
	}

	local FACTION_TEXT = {
		["thorium brotherhood"] = 98,
		["zandalar tribe"] = 100,
		["argent dawn"] = 96,
		["timbermaw hold"] = 99,
		["cenarion circle"] = 97,
		["the aldor"] = 101,
		["the consortium"] = 105,
		["the scryers"] = 110,
		["the sha'tar"] = 111,
		["the mag'har"] = 108,
		["cenarion expedition"] = 103,
		["honor hold"] = 104,
		["thrallmar"] = 104,
		["the violet eye"] = 114,
		["sporeggar"] = 113,
		["kurenai"] = 108,
		["keepers of time"] = 106,
		["the scale of the sands"] = 109,
		["lower city"] = 107,
		["ashtongue deathsworn"] = 102,
		["alliance vanguard"] = 131,
		["valiance expedition"] = 126,
		["horde expedition"] = 130,
		["the taunka"] = 128,
		["the hand of vengeance"] = 127,
		["explorers' league"] = 125,
		["the kalu'ak"] = 120,
		["shattered sun offensive"] = 112,
		["warsong offensive"] = 129,
		["kirin tor"] = 118,
		["the wyrmrest accord"] = 122,
		["knights of the ebon blade"] = 117,
		["frenzyheart tribe"] = 116,
		["the oracles"] = 121,
		["argent crusade"] = 115,
		["the sons of hodir"] = 119,
	}

	local FACTION_LEVELS = {
		["neutral"] = 0,
		["friendly"] = 1,
		["honored"] = 2,
		["revered"] = 3,
		["exalted"] = 4,
	}

	local CLASS_TYPES = {
		["Death Knight"]	= 21, 	["Druid"]	= 22, 	["Hunter"]	= 23,
		["Mage"]		= 24, 	["Paladin"]	= 25, 	["Priest"]	= 26,
		["Shaman"]		= 27, 	["Rogue"]	= 28, 	["Warlock"]	= 29,
		["Warrior"]		= 30,
	}

	local ORDERED_CLASS_TYPES = {
		[1]	= "Death Knight", 	[2]	= "Druid", 	[3]	= "Hunter",
		[4]	= "Mage", 		[5]	= "Paladin", 	[6]	= "Priest",
		[7]	= "Shaman", 		[8]	= "Rogue", 	[9]	= "Warlock",
		[10]	= "Warrior",
	}

	local ROLE_TYPES = {
		["dps"]		= 51, 	["tank"]	= 52, 	["healer"]	= 53,
		["caster"]	= 54,
	}

	local ORDERED_ROLE_TYPES = {
		[1]	= "dps", 	[2]	= "tank", 	[3]	= "healer",
		[4]	= "caster",
	}

	local ENCHANT_TO_ITEM = {
		["Cloak"]	= "Back",
		["Ring"]	= "Finger",
		["2H Weapon"]	= "Two-Hand",
	}

	local ITEM_TYPES = {
		-- Armor types
		["Cloth"]	= 56, 	["Leather"]	= 57, 	["Mail"]	= 58,
		["Plate"]	= 59, 	["Back"]	= 60, 	["Trinket"]	= 61,
		["Finger"]	= 62, 	["Neck"]	= 63, 	["Shield"]	= 64,

		-- Weapon types
		["One-Hand"]	= 66, 	["Two-Hand"]	= 67, 	["Axe"]		= 68,
		["Sword"]	= 69, 	["Mace"]	= 70, 	["Polearm"]	= 71,
		["Dagger"]	= 72, 	["Staff"]	= 73, 	["Wand"]	= 74,
		["Thrown"]	= 75, 	["Bow"]		= 76, 	["CrossBow"]	= 77,
		["Ammo"]	= 78, 	["Fist Weapon"]	= 79, 	["Gun"]		= 80,
	}

	local ORDERED_ITEM_TYPES = {
		-- Armor types
		[1]	= "Cloth", 	[2]	= "Leather", 	[3]	= "Mail",
		[4]	= "Plate", 	[5]	= "Back", 	[6]	= "Trinket",
		[7]	= "Finger", 	[8]	= "Neck", 	[9]	= "Shield",

		-- Weapon types
		[11]	= "One-Hand", 	[12]	= "Two-Hand", 	[13]	= "Axe",
		[14]	= "Sword", 	[15]	= "Mace", 	[16]	= "Polearm",
		[17]	= "Dagger", 	[18]	= "Staff", 	[19]	= "Wand",
		[20]	= "Thrown", 	[21]	= "Bow", 	[22]	= "CrossBow",
		[23]	= "Ammo", 	[24]	= "Fist Weapon", 	[25]	= "Gun",
	}

	--- Parses the mining tooltip for certain keywords, comparing them with the database flags
	-- @name AckisRecipeList:ScanToolTip
	-- @param name The name of the recipe
	-- @param recipe_list Recipe database
	-- @param reverse_lookup Reverse lookup database
	-- @param is_vendor Boolean to indicate if we're scanning a vendor
	-- @return Scans a tooltip, and outputs the missing or extra filter flags
	function addon:ScanToolTip(name, recipe_list, reverse_lookup, is_vendor)
		scan_data.match_name = name
		scan_data.recipe_list = recipe_list
		scan_data.reverse_lookup = reverse_lookup
		scan_data.is_vendor = is_vendor

		-- Parse all the lines of the tooltip
		for i = 1, ARLDatamineTT:NumLines(), 1 do
			local text_l = _G["ARLDatamineTTTextLeft" .. i]:GetText()
			local text_r = _G["ARLDatamineTTTextRight" .. i]:GetText()
			local text

			if text_r then
				text = ("%s %s"):format(text_l, text_r)
			else
				text = text_l
			end
			text = text:lower()

			-- Check for recipe/item binding
			-- The recipe binding is within the first few lines of the tooltip always
			if string.match(text, "binds when picked up") then
				if (i < 3) then
					scan_data.recipe_bop = true
				else
					scan_data.item_bop = true
				end
			end

			-- Recipe Specialities
			if SPECIALTY_TEXT[text] then
				scan_data.specialty = SPECIALTY_TEXT[text]
			end

			-- Recipe Reputations
			local rep, replevel = string.match(text_l, "Requires (.+) %- (.+)")

			if rep and replevel and FACTION_TEXT[rep] then
				scan_data.repid = FACTION_TEXT[rep]
				scan_data.repidlevel = FACTION_LEVELS[replevel]
			end

			-- Flag so that we don't bother checking for classes if we're sure of the class
			-- AKA +spell hit == caster DPS only no matter what other stats are on it
			-- Saves processing cycles and it won't cause the flags to be overwritten if a non-specific stat is found after
			scan_data.verifiedclass = false

			if not scan_data.verifiedclass then
				-- Certain stats can be considered for a specific role (aka spell hit == caster dps).
				if string.match(text, "strength") and not string.match(text, "strength of the clefthoof") and not string.match(text,  "set:") then
					scan_data.dps = true
				elseif string.match(text, "agility") then
					scan_data.dps = true
				elseif string.match(text, "spirit") or string.match(text, "intellect") then
					scan_data.caster = true
					scan_data.healer = true
				elseif string.match(text, "spell power") then
					scan_data.caster = true
					scan_data.healer = true
				elseif string.match(text, "spell crit") then
					scan_data.caster = true
					scan_data.healer = true
				elseif string.match(text, "spell hit") then
					scan_data.caster = true
					scan_data.verifiedclass = true
				elseif string.match(text, "spell penetration") then
					scan_data.caster = true
					scan_data.verifiedclass = true
				elseif string.match(text, "mana per 5 sec.") or string.match(text, "mana every 5 seconds") then
					scan_data.caster = true
					scan_data.healer = true
				elseif string.match(text, "attack power") then
					scan_data.dps = true
				elseif string.match(text, "expertise") then
					scan_data.dps = true
					scan_data.tank = true
				elseif string.match(text, "melee crit") then
					scan_data.dps = true
				elseif string.match(text, "critical hit") then
					scan_data.dps = true
				elseif string.match(text, "weapon damage") then
					scan_data.dps = true
				elseif string.match(text, "ranged crit") then
					scan_data.dps = true
					scan_data.verifiedclass = true
				elseif string.match(text, "melee haste") then
					scan_data.dps = true
				elseif string.match(text, "ranged haste") then
					scan_data.dps = true
					scan_data.verifiedclass = true
				elseif string.match(text, "melee hit") then
					scan_data.dps = true
				elseif string.match(text, "ranged hit") then
					scan_data.dps = true
					scan_data.verifiedclass = true
				elseif string.match(text, "armor pen") then
					scan_data.dps = true
				elseif string.match(text, "feral attack power") then
					scan_data.tank = true
					scan_data.dps = true
				elseif string.match(text, "defense") and not string.match(text, "defenseless") then
					scan_data.tank = true
					scan_data.verifiedclass = true
				elseif string.match(text, "block") then
					scan_data.tank = true
					scan_data.verifiedclass = true
				elseif string.match(text, "parry") then
					scan_data.tank = true
					scan_data.verifiedclass = true
				elseif string.match(text, "dodge") and not string.match(text,  "set:") then
					scan_data.tank = true
					scan_data.verifiedclass = true
				end
			end

			-- Classes
			local class_type = string.match(text_l, "Classes: (.+)")

			if class_type then
				for idx, class in ipairs(ORDERED_CLASS_TYPES) do
					if string.match(class_type, class) then
						scan_data[class] = true
						scan_data.found_class = true
					end
				end
			end

			-- Armor types
			if ITEM_TYPES[text_l] then
				scan_data[text_l] = true
			elseif text_l == "Held In Off-hand" or text_l == "Off Hand" or text_l == "Main Hand" then	-- Special cases.
				scan_data["One-Hand"] = true
			elseif text_l == "Projectile" then
				scan_data["Ammo"] = true
			end

			if text_r and ITEM_TYPES[text_r] then
				scan_data[text_r] = true
			end

			-- Enchantment voodoo
			local ench_type, _ = string.match(text_l, "Enchant (.+) %- (.+)")

			if ench_type then
				if ITEM_TYPES[ench_type] then
					scan_data[ench_type] = true
				elseif ITEM_TYPES[ENCHANT_TO_ITEM[ench_type]] then
					scan_data[ENCHANT_TO_ITEM[ench_type]] = true
				elseif ench_type == "Weapon" then		-- Special case.
					scan_data["One-Hand"] = true
					scan_data["Two-Hand"] = true
				end
			end
		end	-- for
	end

	-- Flag data for printing. Wiped and re-used.
	local missing_flags = {}
	local extra_flags = {}
	local output = {}

	local ACQUIRE_TO_FILTER_MAP = {
		[A.MOB_DROP]	= F.MOB_DROP,
		[A.QUEST]	= F.QUEST,
		[A.SEASONAL]	= F.SEASONAL,
		[A.WORLD_DROP]	= F.WORLD_DROP,
	}
	local FILTER_TO_ACQUIRE_MAP

	--- Prints out the results of the tooltip scan.
	-- @name AckisRecipeList:PrintScanResults
	function addon:PrintScanResults()
		if not scan_data.match_name then
			return
		end

		-- Parse the recipe database until we get a match on the name
		local recipe_name = string.gsub(scan_data.match_name, "%a+%?: ", "")
		local spell_id = scan_data.reverse_lookup[recipe_name]

		if not spell_id then
			self:Print(recipe_name .. " has no reverse lookup")
			return
		end
		local recipe = scan_data.recipe_list[spell_id]
		local acquire_data = recipe["acquire_data"]

		local FS = private.filter_strings
		local flag_format = "F.%s"

		table.wipe(missing_flags)
		table.wipe(extra_flags)
		table.wipe(output)

		-- If we're a vendor scan,  do some extra checks
		if scan_data.is_vendor then
			-- Check to see if the vendor flag is set
			if not recipe:HasFilter("common1", "VENDOR") then
				table.insert(missing_flags, string.format(flag_format, FS[F.VENDOR]))
			end

			-- Check to see if we're in a PVP zone
			local subzone_text = _G.GetSubZoneText()

			if (subzone_text == "Wintergrasp Fortress" or subzone_text == "Halaa") and not recipe:HasFilter("common1", "PVP") then
				table.insert(missing_flags, string.format(flag_format, FS[F.PVP]))
			elseif recipe:HasFilter("common1", "PVP") and not (subzone_text == "Wintergrasp Fortress" or subzone_text == "Halaa") then
				table.insert(extra_flags, string.format(flag_format, FS[F.PVP]))
			end
		end

		-- -- If we've picked up at least one class flag
		if scan_data.found_class then
			for k, v in ipairs(ORDERED_CLASS_TYPES) do
				if scan_data[v] and not recipe:HasFilter("class1", FS[CLASS_TYPES[v]]) then
					table.insert(missing_flags, string.format(flag_format, FS[CLASS_TYPES[v]]))
				elseif not scan_data[v] and recipe:HasFilter("class1", FS[CLASS_TYPES[v]]) then
					table.insert(extra_flags, string.format(flag_format, FS[CLASS_TYPES[v]]))
				end
			end
		end

		if scan_data.item_bop and not recipe:HasFilter("common1", "IBOP") then
			table.insert(missing_flags, string.format(flag_format, FS[F.IBOP]))

			if recipe:HasFilter("common1", "IBOE") then
				table.insert(extra_flags, string.format(flag_format, FS[F.IBOE]))
			end

			if recipe:HasFilter("common1", "IBOA") then
				table.insert(extra_flags, string.format(flag_format, FS[F.IBOA]))
			end
		elseif not recipe:HasFilter("common1", "IBOE") and not scan_data.item_bop then
			table.insert(missing_flags, string.format(flag_format, FS[F.IBOE]))

			if recipe:HasFilter("common1", "IBOP") then
				table.insert(extra_flags, string.format(flag_format, FS[F.IBOP]))
			end

			if recipe:HasFilter("common1", "IBOA") then
				table.insert(extra_flags, string.format(flag_format, FS[F.IBOA]))
			end
		end

		if scan_data.recipe_bop and not recipe:HasFilter("common1", "RBOP") then
			table.insert(missing_flags, string.format(flag_format, FS[F.RBOP]))

			if recipe:HasFilter("common1", "RBOE") then
				table.insert(extra_flags, string.format(flag_format, FS[F.RBOE]))
			end

			if recipe:HasFilter("common1", "RBOA") then
				table.insert(extra_flags, string.format(flag_format, FS[F.RBOA]))
			end

		elseif not recipe:HasFilter("common1", "TRAINER") and not recipe:HasFilter("common1", "RBOE") and not scan_data.recipe_bop then
			table.insert(missing_flags, string.format(flag_format, FS[F.RBOE]))

			if recipe:HasFilter("common1", "RBOP") then
				table.insert(extra_flags, string.format(flag_format, FS[F.RBOP]))
			end

			if recipe:HasFilter("common1", "RBOA") then
				table.insert(extra_flags, string.format(flag_format, FS[F.RBOA]))
			end
		end

		for k, v in ipairs(ORDERED_ROLE_TYPES) do
			local role_string = FS[ROLE_TYPES[v]]

			if scan_data[v] and not recipe:HasFilter("common1", role_string) then
				table.insert(missing_flags, string.format(flag_format, role_string))
			elseif not scan_data[v] and recipe:HasFilter("common1", role_string) then
				table.insert(extra_flags, string.format(flag_format, role_string))
			end
		end

		for k, v in ipairs(ORDERED_ITEM_TYPES) do
			if scan_data[v] and not recipe:HasFilter("item1", FS[ITEM_TYPES[v]]) then
				table.insert(missing_flags, string.format(flag_format, FS[ITEM_TYPES[v]]))
			elseif not scan_data[v] and recipe:HasFilter("item1", FS[ITEM_TYPES[v]]) then
				table.insert(extra_flags, string.format(flag_format, FS[ITEM_TYPES[v]]))
			end
		end

		-- Reputations
		local repid = scan_data.repid
		local found_problem = false

		if repid and not recipe:HasFilter("reputation1", FS[repid]) and not recipe:HasFilter("reputation2", FS[repid]) then
			table.insert(missing_flags, repid)

			local rep_data = acquire_data[A.REPUTATION]

			if rep_data then
				for rep_id, rep_info in pairs(rep_data) do
					for rep_level, level_info in pairs(rep_info) do
						if rep_level ~= scan_data.repidlevel then
							table.insert(output, "    Wrong reputation level.")
						end
					end
				end
			end
		end

		-- Make sure the recipe's filter flags match with its acquire types.
		if not FILTER_TO_ACQUIRE_MAP then
			FILTER_TO_ACQUIRE_MAP = {}

			for k, v in pairs(ACQUIRE_TO_FILTER_MAP) do
				FILTER_TO_ACQUIRE_MAP[v] = k
			end
		end

		for acquire_type in pairs(acquire_data) do
			local flag = ACQUIRE_TO_FILTER_MAP[acquire_type]

			if flag and not recipe:HasFilter("common1", FS[flag]) then
				table.insert(missing_flags, string.format(flag_format, FS[flag]))
			end
		end

		if (acquire_data[A.VENDOR] or acquire_data[A.REPUTATION]) and not recipe:HasFilter("common1", "VENDOR") then
			table.insert(missing_flags, string.format(flag_format, FS[F.VENDOR]))
		end

		if recipe:HasFilter("common1", "VENDOR") and not (acquire_data[A.VENDOR] or acquire_data[A.REPUTATION]) then
			table.insert(extra_flags, string.format(flag_format, FS[F.VENDOR]))
		end

		if acquire_data[A.TRAINER] and not recipe:HasFilter("common1", "TRAINER") then
			table.insert(missing_flags, string.format(flag_format, FS[F.TRAINER]))
		end

		if recipe:HasFilter("common1", "TRAINER") and not acquire_data[A.TRAINER] and not acquire_data[A.CUSTOM] then
			table.insert(extra_flags, string.format(flag_format, FS[F.TRAINER]))
		end

		for flag, acquire_type in pairs(FILTER_TO_ACQUIRE_MAP) do
			if recipe:HasFilter("common1", FS[flag]) and not acquire_data[acquire_type] then
				table.insert(extra_flags, string.format(flag_format, FS[flag]))
			end
		end

		if #missing_flags > 0 or #extra_flags > 0 then
			found_problem = true

			-- Add a string of the missing flag numbers
			if #missing_flags > 0 then
				table.insert(output, "    Missing flags: " .. table.concat(missing_flags, ", "))
			end

			-- Add a string of the extra flag numbers
			if #extra_flags > 0 then
				table.insert(output, "    Extra flags: " .. table.concat(extra_flags, ", "))
			end
			local found_type = false

			for k, v in ipairs(ORDERED_ITEM_TYPES) do
				if scan_data[v] then
					found_type = true
					break
				end
			end

			if not found_type then
				table.insert(output, "    Missing: item type flag")
			end
		end

		-- Check to see if we have a horde/alliance flag,  all recipes must have one of these
		if not recipe:HasFilter("common1", "ALLIANCE") and not recipe:HasFilter("common1", "HORDE") then
			found_problem = true
			table.insert(output, "    Horde or Alliance not selected.")
		end

		-- Check to see if we have an obtain method flag,  all recipes must have at least one of these
		if (not recipe:HasFilter("common1", "TRAINER") and not recipe:HasFilter("common1", "VENDOR") and not recipe:HasFilter("common1", "INSTANCE") and not recipe:HasFilter("common1", "RAID")
		    and not recipe:HasFilter("common1", "SEASONAL") and not recipe:HasFilter("common1", "QUEST") and not recipe:HasFilter("common1", "PVP") and not recipe:HasFilter("common1", "WORLD_DROP")
		    and not recipe:HasFilter("common1", "MOB_DROP") and not recipe:HasFilter("common1", "DISC")) then
			found_problem = true
			table.insert(output, "    No obtain flag.")
		end

		-- Check for recipe binding information,  all recipes must have one of these
		if not recipe:HasFilter("common1", "RBOE") and not recipe:HasFilter("common1", "RBOP") and not recipe:HasFilter("common1", "RBOA") then
			found_problem = true
			table.insert(output, "    No recipe binding information.")
		end

		-- Check for item binding information,  all recipes must have one of these
		if not recipe:HasFilter("common1", "IBOE") and not recipe:HasFilter("common1", "IBOP") and not recipe:HasFilter("common1", "IBOA") then
			found_problem = true
			table.insert(output, "    No item binding information.")
		end

		-- We need to code this better.  Some items (aka bags) won't have a role at all.
		-- Check for player role flags
		if not scan_data.tank and not scan_data.healer and not scan_data.caster and not scan_data.dps and not NO_ROLE_FLAG[spell_id] then
			found_problem = true
			table.insert(output, "    No player role flag.")
		end

		if scan_data.specialty then
			if not recipe.specialty then
				found_problem = true
				table.insert(output, string.format("    Missing Specialty: %s", scan_data.specialty))
			elseif recipe.specialty ~= scan_data.specialty then
				table.insert(output, string.format("    Wrong Specialty: %s - should be %s ", recipe.specialty, scan_data.specialty))
			end
		elseif recipe.specialty then
			found_problem = true
			table.insert(output, string.format("    Extra Specialty: %s", recipe.specialty))
		end

		if scan_data.quality and scan_data.quality ~= recipe.quality then
			local QS = private.item_quality_names

			found_problem = true
			table.insert(output, ("    Wrong quality: Q.%s - should be Q.%s."):format(QS[recipe.quality], QS[scan_data.quality]))
		end

		if found_problem then
			table.insert(output, 1, string.format("%s: <a href=\"http://www.wowhead.com/?spell=%d\">%d</a>", recipe_name, spell_id, spell_id))
			return table.concat(output, "\n")
		else
			return
		end
	end
end

-------------------------------------------------------------------------------
-- Look up table of spell IDs for recipes which do not have a player flag
-- BASICALLY A TEMPORARY STORAGE FOR IDS, SO WE CAN SEE CLEANER SCANS AND WHAT NOT,
-- WE'LL GO BACK HERE LATER DOWN THE ROAD.
-------------------------------------------------------------------------------
NO_ROLE_FLAG = {
	--------------------------------------------------------------------------------------------
	-----ASSORTED CRAP
	--------------------------------------------------------------------------------------------
	[30344] = true, 		[30341] = true, 		[32814] = true, 		[23066] = true,
	[26421] = true, 		[36955] = true, 		[19788] = true, 		[23129] = true,
	[26422] = true, 		[12715] = true, 		[12899] = true, 		[56459] = true,
	[30307] = true, 		[26423] = true, 		[3918] = true,	 		[23067] = true,
	[30308] = true, 		[3953] = true,			[15255] = true, 		[26424] = true,
	[30548] = true, 		[44157] = true, 		[56462] = true, 		[19567] = true,
	[30552] = true, 		[23068] = true, 		[44155] = true, 		[28327] = true,
	[26425] = true, 		[56461] = true, 		[68067] = true, 		[15633] = true,
	[23096] = true, 		[12589] = true, 		[26442] = true, 		[26426] = true,
	[67920] = true, 		[9273] = true,			[3926] = true,			[6458] = true,
	[19793] = true, 		[55252] = true, 		[9271] = true,			[26427] = true,
	[26443] = true, 		[30551] = true, 		[23486] = true, 		[3922] = true,
	[3924] = true,			[12590] = true, 		[3928] = true,			[3942] = true,
	[26428] = true, 		[3952] = true,	 		[22704] = true, 		[12902] = true,
	[30569] = true, 		[15628] = true, 		[12895] = true, 		[21940] = true,
	[56349] = true, 		[12584] = true, 		[56477] = true, 		[30348] = true,
	[26416] = true, 		[53281] = true, 		[23507] = true,			[12075] = true,
	[12079] = true,			[26746] = true,			[56000] = true,

	-----------------------------------------------------------------------------------------
	---JEWELCRAFTING
	-----------------------------------------------------------------------------------------
	[55401] = true,	[53995] = true,	[66432] = true,	[25255] = true,
	[66497] = true, [53996] = true, [56074] = true, [56202] = true,
	[66434] = true, [32801] = true, [28948] = true, [53997] = true,
	[39963] = true, [32866] = true, [66501] = true, [39452] = true,
	[53934] = true, [66502] = true, [55405] = true, [58954] = true,
	[56205] = true, [37855] = true, [38175] = true, [25305] = true,
	[25321] = true, [66505] = true, [54000] = true, [39742] = true,
	[56206] = true, [66506] = true, [32869] = true, [54001] = true,
	[56079] = true, [32259] = true, [32870] = true, [56208] = true,
	[32807] = true, [32871] = true, [26873] = true, [31061] = true,
	[32872] = true, [42591] = true, [46779] = true, [56530] = true,
	[42592] = true, [31062] = true, [56083] = true, [32874] = true,
	[56531] = true, [31063] = true, [56085] = true, [38503] = true,
	[56086] = true, [28938] = true, [38504] = true, [31064] = true,
	[31096] = true, [55384] = true, [25278] = true, [68253] = true,
	[43493] = true, [26925] = true, [31065] = true, [46403] = true,
	[53852] = true, [53916] = true, [39722] = true, [25615] = true,
	[28924] = true, [26926] = true, [53853] = true, [36526] = true,
	[31098] = true, [55388] = true, [46405] = true, [53854] = true,
	[39724] = true, [55389] = true, [26927] = true, [31051] = true,
	[53919] = true, [39725] = true, [55390] = true, [53856] = true,
	[53920] = true, [53952] = true, [26880] = true, [53857] = true,
	[31052] = true, [34069] = true, [25490] = true, [55393] = true,
	[39451] = true, [53956] = true, [44794] = true, [36524] = true,
	[55395] = true, [38068] = true, [28944] = true, [53957] = true,
	[31066] = true, [31082] = true, [66428] = true, [26909] = true,
	[55396] = true, [31097] = true, [25284] = true, [56084] = true,
	[62941] = true, [66431] = true, [53894] = true, [53958] = true,
	[58146] = true, [31099] = true, [47053] = true, [31067] = true,
	[28917] = true, [26903] = true, [36525] = true, [66503] = true,
	[56197] = true, [47054] = true, [53961] = true, [39715] = true,
	[31092] = true, [53960] = true, [31077] = true, [31101] = true,
	[39719] = true, [55399] = true, [31113] = true, [32808] = true,
	[53917] = true, [63743] = true, [39720] = true, [39721] = true,
	[42590] = true, [58149] = true, [56199] = true, [47056] = true,
	[28950] = true, [47280] = true, [32809] = true, [31072] = true,
	[25318] = true, [66429] = true, [62242] = true,
	---------------------------------------------------------------------------------------

	---------------------------------------------------------------------------------------
	---COOKING
	---------------------------------------------------------------------------------------
	[62050]= true, [22761]= true, [62051]= true, [8607]= true,
	[18238]= true, [6413]= true, [6417]= true, [42296]= true,
	[45557]= true, [6501]= true, [45558]= true, [18239]= true,
	[7752]= true, [7828]= true, [45560]= true, [64358]= true,
	[57421]= true, [45561]= true, [13028]= true, [2543]= true,
	[2545]= true, [25659]= true, [58512]= true, [45565]= true,
	[42305]= true, [45566]= true, [62350]= true, [7753]= true,
	[45695]= true, [9513]= true, [18244]= true, [20626]= true,
	[45569]= true, [43779]= true, [18245]= true, [45571]= true,
	[18246]= true, [37836]= true, [57433]= true, [20916]= true,
	[58521]= true, [18247]= true, [57435]= true, [7754]= true,
	[53056]= true, [58523]= true, [57437]= true, [57438]= true,
	[58525]= true, [45570]= true, [2538]= true, [2540]= true,
	[2548]= true, [33290]= true, [45562]= true, [15906]= true,
	[18241]= true, [45559]= true, [45551]= true, [57443]= true,
	[58527]= true, [43758]= true, [58528]= true, [8238]= true,
	[7751]= true, [7755]= true, [43761]= true, [7827]= true,
	[45552]= true, [45553]= true, [66038] = true, [66034] = true,
	---------------------------------------------------------------------------------------

	---------------------------------------------------------------------------------------
	---BLACKSMITHING
	---------------------------------------------------------------------------------------
	[10015] = true, [55202] = true, [16726] = true, [24399] = true,
	[15293] = true, [32655] = true, [3320] = true, [40033] = true,
	[34545] = true, [61010] = true, [16639] = true, [3115] = true,
	[3116] = true, [3117] = true, [16648] = true, [40034] = true,
	[9920] = true, [9928] = true, [29558] = true, [55839] = true,
	[9964] = true, [7818] = true, [55732] = true, [29694] = true,
	[3326] = true, [9950] = true, [59405] = true, [12260] = true,
	[16983] = true, [16991] = true, [23650] = true, [16640] = true,
	[2741] = true, [3513] = true, [3497] = true, [8768] = true,
	[19666] = true, [2662] = true, [2663] = true, [20201] = true,
	[16664] = true, [15294] = true, [3496] = true, [16645] = true,
	[29569] = true, [2737] = true, [14380] = true, [16992] = true,
	[36125] = true, [19667] = true, [22757] = true, [16665] = true,
	[59406] = true, [2738] = true, [2739] = true, [2740] = true,
	[24913] = true, [9933] = true, [20873] = true, [36126] = true,
	[40035] = true, [8880] = true, [23636] = true, [3292] = true,
	[3293] = true, [21161] = true, [32656] = true, [19668] = true,
	[9921] = true, [7222] = true, [3319] = true, [15295] = true,
	[20874] = true, [20890] = true, [3337] = true, [34546] = true,
	[16970] = true, [16978] = true, [61008] = true, [7224] = true,
	[11454] = true, [19669] = true, [55656] = true, [16667] = true,
	[16731] = true, [61009] = true, [9926] = true, [16984] = true,
	[21913] = true, [40036] = true, [9974] = true, [32657] = true,
	[32285] = true, [23638] = true, [10011] = true, [36131] = true,
	[27830] = true,  [32284] = true, [16732] = true, [15292] = true,
	[15296] = true, [38478] = true, [20876] = true, [38475] = true,
	[23653] = true, [3325] = true, [14379] = true,  [29729] = true,
	[2661] = true,  [29728] = true, [16655] = true, [16993] = true,
	[3491] = true, [3494] = true, [3501] = true, [9959] = true,
	[9983] = true, [36262] = true, [10003] = true, [10007] = true,
	---------------------------------------------------------------------------------------

	---------------------------------------------------------------------------------------
	----INSCRIPTION
	---------------------------------------------------------------------------------------
	[58315] = true, [58331] = true, [58347] = true, [56974] = true, [56990] = true,
	[64256] = true, [64304] = true, [57166] = true, [57709] = true, [57214] = true,
	[57230] = true, [50603] = true, [57262] = true, [59338] = true, [58332] = true,
	[56959] = true, [56975] = true, [56991] = true, [57007] = true, [57023] = true,
	[53462] = true, [64257] = true, [64273] = true, [64289] = true, [57151] = true,
	[57167] = true, [57183] = true, [57710] = true, [57215] = true, [57247] = true,
	[50620] = true, [58301] = true, [59339] = true, [58333] = true, [59387] = true,
	[52840] = true, [59499] = true, [56960] = true, [56976] = true, [57008] = true,
	[57024] = true, [64258] = true, [64274] = true, [57168] = true, [57184] = true,
	[61288] = true, [57216] = true, [57232] = true, [58286] = true, [58302] = true,
	[59340] = true, [48114] = true, [64051] = true, [59484] = true, [59500] = true,
	[56961] = true, [56977] = true, [57009] = true, [57025] = true, [61177] = true,
	[64259] = true, [57121] = true, [64291] = true, [57153] = true, [57169] = true,
	[57185] = true, [57712] = true, [57217] = true, [57233] = true, [58287] = true,
	[58303] = true, [58319] = true, [56946] = true, [56978] = true, [56994] = true,
	[57010] = true, [57026] = true, [64260] = true, [57122] = true, [57154] = true,
	[57170] = true, [57186] = true, [57713] = true, [57218] = true, [57234] = true,
	[57266] = true, [59326] = true, [58320] = true, [58336] = true, [59486] = true,
	[59502] = true, [56963] = true, [56995] = true, [57011] = true, [57027] = true,
	[64261] = true, [64277] = true, [57155] = true, [57187] = true, [57714] = true,
	[57219] = true, [57235] = true, [58289] = true, [58305] = true, [58321] = true,
	[58337] = true, [69385] = true, [57243] = true, [64314] = true, [64313] = true,
	[56958] = true, [59487] = true, [59503] = true, [58288] = true, [56980] = true,
	[56996] = true, [57012] = true, [57028] = true, [59501] = true, [56948] = true,
	[57264] = true, [64246] = true, [64262] = true, [57124] = true, [64294] = true,
	[57156] = true, [57172] = true, [57188] = true, [57715] = true, [57220] = true,
	[57236] = true, [57268] = true, [58306] = true, [58322] = true, [58338] = true,
	[48247] = true, [57133] = true, [57270] = true, [57131] = true, [57129] = true,
	[50604] = true, [59488] = true, [59504] = true, [56965] = true, [56981] = true,
	[56997] = true, [57013] = true, [57029] = true, [64280] = true, [57114] = true,
	[64286] = true, [58299] = true, [57125] = true, [64295] = true, [64311] = true,
	[57164] = true, [57189] = true, [57716] = true, [57221] = true, [57237] = true,
	[57269] = true, [58307] = true, [58323] = true, [58339] = true, [64307] = true,
	[57119] = true, [57246] = true, [68166] = true, [64282] = true, [57250] = true,
	[59489] = true, [56957] = true, [56982] = true, [56998] = true, [57014] = true,
	[57117] = true, [64305] = true, [61677] = true, [52843] = true, [57126] = true,
	[64296] = true, [57158] = true, [64310] = true, [57190] = true, [56956] = true,
	[57238] = true, [60336] = true, [58308] = true, [58324] = true, [58340] = true,
	[64315] = true, [56949] = true, [56950] = true, [64308] = true, [56955] = true,
	[59490] = true, [59315] = true, [56983] = true, [56999] = true, [64267] = true,
	[57031] = true, [56954] = true, [64276] = true, [57249] = true, [64249] = true,
	[56953] = true, [57127] = true, [64297] = true, [57159] = true, [64275] = true,
	[57191] = true, [57207] = true, [57223] = true, [57239] = true, [50612] = true,
	[60337] = true, [57208] = true, [58325] = true, [48121] = true, [56952] = true,
	[56947] = true, [56951] = true, [64268] = true, [57123] = true, [59475] = true,
	[59491] = true, [56968] = true, [56984] = true, [57000] = true, [48248] = true,
	[58565] = true, [58317] = true, [64266] = true, [57252] = true, [64250] = true,
	[57112] = true, [57128] = true, [64298] = true, [57160] = true, [58316] = true,
	[57703] = true, [57719] = true, [57224] = true, [57240] = true, [57272] = true,
	[58310] = true, [58326] = true, [58342] = true, [57201] = true, [56945] = true,
	[57200] = true, [56944] = true, [57711] = true, [56985] = true, [57001] = true,
	[57199] = true, [57033] = true, [56943] = true, [57198] = true, [64251] = true,
	[57113] = true, [64283] = true, [64299] = true, [57161] = true, [57197] = true,
	[57704] = true, [57209] = true, [57225] = true, [57241] = true, [50614] = true,
	[57273] = true, [58311] = true, [58327] = true, [58343] = true, [52738] = true,
	[57196] = true, [64278] = true, [57195] = true, [58318] = true, [64284] = true,
	[57006] = true, [64316] = true, [56986] = true, [57002] = true, [59560] = true,
	[57034] = true, [58341] = true, [57192] = true, [64252] = true, [67600] = true,
	[57130] = true, [64300] = true, [57162] = true, [57244] = true, [57194] = true,
	[57210] = true, [57226] = true, [50599] = true, [57258] = true, [58296] = true,
	[58312] = true, [58328] = true, [58344] = true, [52739] = true, [58298] = true,
	[57274] = true, [57265] = true, [57251] = true, [59478] = true, [56971] = true,
	[56987] = true, [57003] = true, [57019] = true, [57035] = true, [57259] = true,
	[57263] = true, [57271] = true, [64253] = true, [57115] = true, [64285] = true,
	[57242] = true, [57163] = true, [57120] = true, [57706] = true, [57211] = true,
	[57227] = true, [50600] = true, [50616] = true, [58297] = true, [58313] = true,
	[58329] = true, [58345] = true, [50602] = true, [57248] = true, [59559] = true,
	[57157] = true, [57257] = true, [64309] = true, [64317] = true, [56972] = true,
	[56988] = true, [57004] = true, [57020] = true, [57036] = true, [62162] = true,
	[57030] = true, [64254] = true, [57116] = true, [57132] = true, [64302] = true,
	[64318] = true, [64270] = true, [57707] = true, [57212] = true, [57228] = true,
	[50601] = true, [57260] = true, [57276] = true, [58314] = true, [58330] = true,
	[58346] = true, [45382] = true, [57267] = true, [57275] = true, [64312] = true,
	[64279] = true, [50598] = true, [59480] = true, [59496] = true, [56973] = true,
	[56989] = true, [57005] = true, [57021] = true, [50619] = true, [50618] = true,
	[65245] = true, [50617] = true, [64255] = true, [64271] = true, [64287] = true,
	[64303] = true, [57165] = true, [57181] = true, [57708] = true, [57213] = true,
	[57229] = true, [57245] = true, [57261] = true, [57277] = true, [57253] = true,
	[71015] = true,	[71101] = true,	[71102] = true,
	---------------------------------------------------------------------------------------

	---------------------------------------------------------------------------------------
	----ENCHANTING
	---------------------------------------------------------------------------------------
	[46578] = true, [25125] = true, [13612] = true, [13620] = true, [13628] = true,
	[13640] = true, [13644] = true, [13648] = true, [59625] = true, [13700] = true,
	[17181] = true, [27920] = true, [27960] = true, [28016] = true, [45765] = true,
	[14810] = true, [63746] = true, [13836] = true, [20008] = true, [20032] = true,
	[13868] = true, [13421] = true, [13948] = true, [27905] = true, [27945] = true,
	[13501] = true, [7786] = true, [7788] = true, [13529] = true, [32664] = true,
	[44506] = true, [25127] = true, [20025] = true, [20033] = true, [13617] = true,
	[64579] = true, [60619] = true, [13657] = true, [13693] = true, [27914] = true,
	[44555] = true, [27954] = true, [27962] = true, [14807] = true, [32665] = true,
	[7421] = true, [13817] = true, [25072] = true, [44492] = true, [44524] = true,
	[7443] = true, [44556] = true, [25128] = true, [20026] = true, [7457] = true,
	[13378] = true, [34001] = true, [13917] = true, [13933] = true, [13937] = true,
	[13941] = true, [44621] = true, [28003] = true, [28027] = true, [13522] = true,
	[13538] = true, [25081] = true, [44494] = true, [20011] = true, [25129] = true,
	[44590] = true, [47672] = true, [20051] = true, [13622] = true, [13626] = true,
	[69412] = true, [60623] = true, [44383] = true, [13698] = true, [13702] = true,
	[23804] = true, [44623] = true, [28004] = true, [28028] = true, [14293] = true,
	[32667] = true, [13822] = true, [25082] = true, [44576] = true, [20028] = true,
	[20036] = true, [13890] = true, [13898] = true, [60609] = true, [59619] = true,
	[47898] = true, [7745] = true, [27957] = true, [13503] = true, [34006] = true,
	[7793] = true, [7795] = true, [59636] = true, [25083] = true, [20029] = true,
	[13631] = true, [13655] = true, [7861] = true, [7863] = true, [62256] = true,
	[13695] = true, [13858] = true, [44483] = true, [27968] = true, [7857] = true,
	[27967] = true, [42615] = true, [27958] = true, [42613] = true, [25084] = true,
	[20017] = true, [33992] = true, [34008] = true, [13663] = true, [60653] = true,
	[44528] = true, [14809] = true, [27961] = true, [47901] = true, [7418] = true,
	[7420] = true, [44488] = true, [7426] = true, [44616] = true, [13841] = true,
	[44596] = true, [17180] = true, [7454] = true, [33993] = true, [34009] = true,
	[27950] = true, [20031] = true, [13915] = true, [47900] = true, [13943] = true,
	[13947] = true, [13945] = true, [13464] = true, [27927] = true, [7771] = true,
	[20014] = true, [20030] = true, [27947] = true, [33994] = true, [28022] = true,
	[60692] = true, [25130] = true, [34005] = true, [13794] = true, [7748] = true,
	[44588] = true, [15596] = true, [7776] = true, [44584] = true, [13607] = true,
	[13653] = true, [20020] = true,  [70524] = true, [71692] = true,
	---------------------------------------------------------------------------------------

	---------------------------------------------------------------------------------------
	---TAILORING
	---------------------------------------------------------------------------------------
	[12055] = true, [12059] = true, [12071] = true, [46131] = true, [23664] = true,
	[31433] = true, [31441] = true, [7624] = true, [26403] = true, [24902] = true,
	[22866] = true, [56001] = true, [18411] = true, [18419] = true, [18451] = true,
	[28208] = true, [6686] = true, [8766] = true, [23665] = true, [8786] = true,
	[26747] = true, [56002] = true, [26763] = true, [26779] = true, [31434] = true,
	[31450] = true, [60969] = true, [28480] = true, [22867] = true, [56003] = true,
	[18404] = true, [18412] = true, [18420] = true, [12044] = true, [63924] = true,
	[12056] = true, [12064] = true, [12080] = true, [12084] = true, [23666] = true,
	[56004] = true, [26780] = true, [31459] = true, [7892] = true, [60971] = true,
	[28481] = true, [22868] = true, [20848] = true, [56005] = true, [18405] = true,
	[19435] = true, [26086] = true, [18437] = true, [18453] = true, [27659] = true,
	[28210] = true, [40021] = true, [23667] = true, [26773] = true, [26781] = true,
	[26759] = true, [8774] = true, [31460] = true, [24903] = true, [3755] = true,
	[37882] = true, [3757] = true, [3758] = true, [50194] = true, [18446] = true,
	[6692] = true, [28482] = true, [22869] = true, [18439] = true, [20849] = true,
	[18440] = true, [2389] = true, [56007] = true, [18455] = true, [6521] = true,
	[18445] = true, [18406] = true, [18414] = true, [18422] = true, [12045] = true,
	[36318] = true, [18454] = true, [27660] = true, [24091] = true, [12065] = true,
	[12069] = true, [12077] = true, [12081] = true, [12085] = true, [27724] = true,
	[12093] = true, [40060] = true, [26087] = true, [26749] = true, [31373] = true,
	[26750] = true, [26782] = true, [28205] = true, [28209] = true, [36686] = true,
	[3813] = true, [2394] = true, [7629] = true, [18438] = true, [7633] = true,
	[40023] = true, [49677] = true, [26407] = true, [18452] = true, [7643] = true,
	[22870] = true, [26755] = true, [22902] = true, [3869] = true, [55993] = true,
	[8789] = true, [3839] = true, [3841] = true, [3844] = true, [18407] = true,
	[18415] = true, [3847] = true, [3848] = true, [3851] = true, [3852] = true,
	[24092] = true, [3854] = true, [3856] = true, [3857] = true, [8760] = true,
	[27725] = true, [8776] = true, [8780] = true, [8784] = true, [3865] = true,
	[55994] = true, [3868] = true, [26783] = true, [3871] = true, [3872] = true,
	[3873] = true, [31438] = true, [22759] = true, [12091] = true, [8804] = true,
	[8772] = true, [18560] = true, [18447] = true, [44950] = true, [60993] = true,
	[55899] = true, [55898] = true, [55995] = true, [2385] = true, [21945] = true,
	[6688] = true, [12066] = true, [18416] = true, [8465] = true, [12046] = true,
	[18448] = true, [3915] = true, [24093] = true, [8489] = true, [2386] = true,
	[12078] = true, [12082] = true, [60994] = true, [55900] = true, [2392] = true,
	[2393] = true, [23662] = true, [2396] = true, [55996] = true, [2399] = true,
	[26784] = true, [2402] = true, [2403] = true, [2406] = true, [31431] = true,
	[3864] = true, [8782] = true, [37884] = true, [37883] = true, [26751] = true,
	[50647] = true, [8802] = true, [8467] = true, [7893] = true, [40020] = true,
	[41208] = true, [22813] = true, [41207] = true, [37873] = true, [2387] = true,
	[55997] = true, [26085] = true, [8799] = true, [27658] = true, [8483] = true,
	[3914] = true, [18401] = true, [56006] = true, [18441] = true, [18449] = true,
	[28207] = true, [55769] = true, [18413] = true, [18421] = true, [46129] = true,
	[2963] = true, [2964] = true, [12061] = true, [12086] = true, [23663] = true,
	[26745] = true, [55998] = true, [3863] = true, [8764] = true, [31440] = true,
	[31448] = true, [12089] = true, [6695] = true, [40024] = true, [60990] = true,
	[50644] = true, [8793] = true, [46130] = true, [24901] = true, [3870] = true,
	[31437] = true, [44958] = true, [55999] = true, [3845] = true, [6693] = true,
	[3866] = true, [18456] = true, [18410] = true, [18418] = true, [2397] = true,
	[18434] = true, [18450] = true,
	---------------------------------------------------------------------------------------

	---------------------------------------------------------------------------------------
	----LEATHERWORKING
	---------------------------------------------------------------------------------------
	[9198] = true, [23704] = true, [60996] = true, [35549] = true, [22921] = true,
	[50958] = true, [10509] = true, [10525] = true, [19053] = true, [19085] = true,
	[19101] = true, [2149] = true, [36349] = true, [2153] = true, [60997] = true,
	[2159] = true, [2163] = true, [2165] = true, [35582] = true, [50959] = true,
	[46132] = true, [23705] = true, [60998] = true, [22922] = true, [46133] = true,
	[24121] = true, [19054] = true, [19070] = true, [19086] = true, [19102] = true,
	[32465] = true, [32481] = true, [22331] = true, [35520] = true, [35584] = true,
	[9207] = true, [6661] = true, [46134] = true, [23706] = true, [61000] = true,
	[35521] = true, [35585] = true, [50962] = true, [10518] = true, [39997] = true,
	[24122] = true, [19055] = true, [19087] = true, [19103] = true, [32466] = true,
	[32482] = true, [36353] = true, [35522] = true, [35554] = true, [50963] = true,
	[44953] = true, [46136] = true, [23707] = true, [61002] = true, [35523] = true,
	[35555] = true, [35587] = true, [50964] = true, [46137] = true, [24123] = true,
	[19072] = true, [19104] = true, [9064] = true, [9072] = true, [69386] = true,
	[32499] = true, [36355] = true, [7133] = true, [35524] = true, [35588] = true,
	[7153] = true, [9208] = true, [46138] = true, [23708] = true, [69388] = true,
	[35525] = true, [35557] = true, [10487] = true, [10511] = true, [46139] = true,
	[24124] = true, [19073] = true, [19089] = true, [32468] = true, [32500] = true,
	[10647] = true, [28472] = true, [35526] = true, [35558] = true, [45117] = true,
	[60622] = true, [23709] = true, [35527] = true, [3774] = true, [3778] = true,
	[3780] = true, [40003] = true, [24125] = true, [19058] = true, [19074] = true,
	[32485] = true, [32501] = true, [36359] = true, [3816] = true, [3818] = true,
	[28473] = true, [35528] = true, [35560] = true, [9193] = true, [40004] = true,
	[23710] = true, [8322] = true, [35529] = true, [35561] = true, [50970] = true,
	[2881] = true, [10520] = true, [40005] = true, [10544] = true, [10552] = true,
	[10560] = true, [19091] = true, [19107] = true, [32454] = true, [32502] = true,
	[28474] = true, [35530] = true, [50971] = true, [62448] = true, [7953] = true,
	[41157] = true, [36074] = true, [24846] = true, [35531] = true, [22928] = true,
	[28219] = true, [41158] = true, [36075] = true, [32487] = true, [32503] = true,
	[7126] = true, [9146] = true, [35532] = true, [35564] = true, [9194] = true,
	[9202] = true, [24655] = true, [36076] = true, [24847] = true, [35533] = true,
	[5244] = true, [10529] = true, [28220] = true, [19093] = true, [32456] = true,
	[52733] = true, [2158] = true, [2160] = true, [2162] = true, [2166] = true,
	[24848] = true, [35535] = true, [35567] = true, [28221] = true, [19094] = true,
	[44359] = true, [57690] = true, [32457] = true, [32473] = true, [32489] = true,
	[9147] = true, [35536] = true, [35568] = true, [9195] = true, [41163] = true,
	[6703] = true, [24849] = true, [20853] = true, [10482] = true, [10490] = true,
	[28222] = true, [10546] = true, [10554] = true, [41164] = true, [10570] = true,
	[19095] = true, [57692] = true, [32458] = true, [10650] = true, [35538] = true,
	[42731] = true, [24850] = true, [20854] = true, [44970] = true, [28223] = true,
	[19048] = true, [19064] = true, [19080] = true, [57694] = true, [9148] = true,
	[35540] = true, [35572] = true, [24851] = true, [20855] = true, [45100] = true,
	[10531] = true, [28224] = true, [57696] = true, [10619] = true, [19081] = true,
	[22727] = true, [35539] = true, [19079] = true, [10566] = true, [19065] = true,
	[10574] = true, [19097] = true, [60647] = true, [3753] = true, [9065] = true,
	[44343] = true, [3763] = true, [3767] = true, [3773] = true, [3775] = true,
	[3777] = true, [3779] = true, [32455] = true, [24940] = true, [19050] = true,
	[19082] = true, [23190] = true, [32461] = true, [60999] = true, [3817] = true,
	[44344] = true, [9149] = true, [35544] = true, [9197] = true, [19077] = true,
	[9060] = true, [19076] = true, [35573] = true, [40001] = true, [19066] = true,
	[19090] = true, [19098] = true, [22711] = true, [57699] = true, [9059] = true,
	[32480] = true, [64661] = true, [10562] = true, [40002] = true, [50936] = true,
	[6705] = true, [9058] = true, [24703] = true, [22923] = true, [60645] = true,
	[35577] = true, [2152] = true, [10556] = true, [10572] = true, [44770] = true,
	[36077] = true, [19059] = true, [19075] = true, [35580] = true, [20648] = true,
	[2169] = true, [60643] = true, [19088] = true, [6702] = true, [6704] = true,
	[35543] = true, [7149] = true, [36078] = true, [36079] = true, [23703] = true,
	[35534] = true, [57701] = true, [46135] = true, [19047] = true, [20649] = true,
	[40006] = true, [3765] = true, [41156] = true, [3771] = true, [50956] = true,
	[19061] = true, [20650] = true, [32467] = true, [35537] = true, [22926] = true,
	[4096] = true, [19062] = true, [44768] = true, [35576] = true, [19063] = true,
	[9062] = true, [9070] = true, [32495] = true,
	---------------------------------------------------------------------------------------

	---------------------------------------------------------------------------------------
	-----ALCHEMY
	---------------------------------------------------------------------------------------
	[53898] = true, [28579] = true, [17566] = true, [53771] = true, [7256] = true,
	[53899] = true, [11452] = true, [11460] = true, [11468] = true, [3170] = true,
	[3172] = true, [3174] = true, [3176] = true, [53836] = true, [53900] = true,
	[28564] = true, [28580] = true, [54220] = true, [17551] = true, [53773] = true,
	[53837] = true, [62213] = true, [54221] = true, [53774] = true, [53838] = true,
	[6617] = true, [53902] = true, [28581] = true, [2331] = true, [2335] = true,
	[2337] = true, [53775] = true, [53839] = true, [11453] = true, [7836] = true,
	[24266] = true, [41500] = true, [53904] = true, [53936] = true, [28566] = true,
	[28582] = true, [3452] = true, [3454] = true, [33732] = true, [17553] = true,
	[22732] = true, [53777] = true, [53905] = true, [53937] = true, [33733] = true,
	[62409] = true, [25146] = true, [41502] = true, [53938] = true, [28551] = true,
	[28567] = true, [28583] = true, [62410] = true, [7181] = true, [17570] = true,
	[17634] = true, [53779] = true, [7257] = true, [53939] = true, [11478] = true,
	[17187] = true, [53812] = true, [28552] = true, [28568] = true, [28584] = true,
	[60366] = true, [57425] = true, [17635] = true, [53781] = true, [60367] = true,
	[4508] = true, [53782] = true, [24365] = true, [6618] = true, [53942] = true,
	[28569] = true, [28585] = true, [29688] = true, [57427] = true, [17556] = true,
	[17572] = true, [17636] = true, [53783] = true, [11479] = true, [7837] = true,
	[7841] = true, [53784] = true, [24366] = true, [53848] = true, [28554] = true,
	[28570] = true, [28586] = true, [58868] = true, [33741] = true, [24367] = true,
	[28555] = true, [28571] = true, [17574] = true, [17638] = true, [7258] = true,
	[11448] = true, [11456] = true, [11464] = true, [11480] = true, [3171] = true,
	[3173] = true, [3175] = true, [24368] = true, [28572] = true, [28588] = true,
	[45061] = true, [66658] = true, [17559] = true, [17575] = true, [66659] = true,
	[66660] = true, [63732] = true, [28573] = true, [66662] = true, [17560] = true,
	[2330] = true, [2332] = true, [2334] = true, [66663] = true, [11457] = true,
	[11465] = true, [11473] = true, [66664] = true, [3449] = true, [3451] = true,
	[3453] = true, [17561] = true, [17577] = true, [28543] = true, [28575] = true,
	[7179] = true, [17562] = true, [17578] = true, [7255] = true, [7259] = true,
	[11458] = true, [11466] = true, [38962] = true, [53776] = true, [12609] = true,
	[53780] = true, [60893] = true, [58871] = true, [3448] = true, [3450] = true,
	[3447] = true, [4942] = true, [28576] = true, [15833] = true, [17576] = true,
	[41503] = true, [60350] = true, [41501] = true, [17563] = true, [42736] = true,
	[54213] = true, [41458] = true, [6624] = true, [39636] = true, [28577] = true,
	[32765] = true, [38070] = true, [17564] = true, [17580] = true, [53895] = true,
	[11451] = true, [39637] = true, [28546] = true, [28562] = true, [28578] = true,
	[60354] = true, [32766] = true, [17565] = true, [22808] = true, [39639] = true,
	[60355] = true, [56519] = true,
	---------------------------------------------------------------------------------------

	---------------------------------------------------------------------------------------
	----ENGINEERING
	---------------------------------------------------------------------------------------
	[23081] = true, [12586] = true, [12594] = true, [12622] = true, [30347] = true,
	[44391] = true, [30547] = true, [56464] = true, [12754] = true, [12758] = true,
	[23489] = true, [23082] = true, [8243] = true, [9269] = true, [30316] = true,
	[30332] = true, [12906] = true, [8339] = true, [26420] = true, [56514] = true,
	[39971] = true, [12599] = true, [12603] = true, [12607] = true, [43676] = true,
	[12619] = true, [54998] = true, [30349] = true, [56468] = true, [12755] = true,
	[12759] = true, [63750] = true, [39973] = true, [56465] = true, [56469] = true,
	[30318] = true, [30334] = true, [55016] = true, [12903] = true, [54793] = true,
	[19795] = true, [8895] = true, [19819] = true, [30558] = true, [39895] = true,
	[23069] = true, [23077] = true, [12596] = true, [30303] = true, [56471] = true,
	[12624] = true, [19796] = true, [56472] = true, [12760] = true, [63770] = true,
	[23070] = true, [23078] = true, [61471] = true, [30304] = true, [30312] = true,
	[12908] = true, [12597] = true, [3930] = true, [24356] = true, [67839] = true,
	[67326] = true, [56474] = true, [30560] = true, [30568] = true, [30314] = true,
	[3929] = true, [56475] = true, [19830] = true, [3946] = true, [54736] = true,
	[30563] = true, [24357] = true, [30337] = true, [12591] = true, [19814] = true,
	[60866] = true, [12716] = true, [23071] = true, [23079] = true, [12585] = true,
	[3923] = true, [3925] = true, [30305] = true, [12621] = true, [30329] = true,
	[3931] = true, [3932] = true, [3933] = true, [3936] = true, [3937] = true,
	[3938] = true, [3939] = true, [3941] = true, [56473] = true, [3944] = true,
	[3945] = true, [3947] = true, [54999] = true, [3949] = true, [3950] = true,
	[26417] = true, [12717] = true, [3954] = true, [3955] = true, [3957] = true,
	[3958] = true, [3961] = true, [3962] = true, [3963] = true, [3965] = true,
	[3966] = true, [3967] = true, [3968] = true, [3969] = true, [3971] = true,
	[3972] = true, [3973] = true, [7430] = true, [3977] = true, [3978] = true,
	[3979] = true, [19790] = true, [23080] = true, [12620] = true, [30309] = true,
	[30346] = true, [12905] = true, [41307] = true, [8334] = true, [56460] = true,
	[56476] = true, [12718] = true, [26418] = true, [19791] = true, [3960] = true,
	[19815] = true, [13240] = true, [19831] = true, [30570] = true, [19799] = true,
	[30306] = true, [30310] = true, [26011] = true, [36954] = true, [3919] = true,
	[3920] = true, [19800] = true, [12617] = true, [30311] = true, [56463] = true,
	[72952] = true, [72953] = true,
	---------------------------------------------------------------------------------------
}