--[[ ---------------------------------------------------------------------------
Created:		7/27/2010 5:07:08 AM
Last Modified:	2011-04-29T08:44:39Z
Author:			orgevo
Version:		v1.3a

SearchResults:	Contains all logic for handling data received from search modules.

AtlasLootAdvancedSearch is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

AtlasLootAdvancedSearch is distributed WITHOUT ANY WARRANTY; without even the 
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with AtlasLootAdvancedSearch.	If not, see <http://www.gnu.org/licenses/>.

----------------------------------------------------------------------------- ]]
--[[
	@TODO:
		- the only time we really **need** to initialize the zone table is when search modules are enabled/disabled.
--]]
local ADDON_TAG					= "AtlasLootAdvancedSearch"
local AtlasLootAdvancedSearch	= LibStub("AceAddon-3.0"):GetAddon(ADDON_TAG);
local alas_addon				= AtlasLootAdvancedSearch;
local al_addon					= AtlasLoot;
local SEARCHRESULTS_DEBUG		= false
local ENABLE_LOGGING			= alas_addon.ENABLE_LOGGING and SEARCHRESULTS_DEBUG
local SEARCHRESULT_TAG			= "SearchResult";
local FILTERED_SEARCHRESULT_TAG = "FilteredSearchResult";

-- localization
local L						= LibStub("AceLocale-3.0"):GetLocale(ADDON_TAG, true)
local AL					= LibStub("AceLocale-3.0"):GetLocale("AtlasLoot");
local ADDON_NAME			= L["ADDON_NAME"]

-- Colors
local GREY = alas_addon.GREY;
local DARKGREY = alas_addon.DARKGREY;
local RED = alas_addon.RED;
local WHITE = alas_addon.WHITE;
local GREEN = alas_addon.GREEN;
local PURPLE = alas_addon.PURPLE;
local BLUE = alas_addon.BLUE;
local ORANGE = alas_addon.ORANGE;
local BROWN = alas_addon.BROWN;

-- cached globals
local tinsert, tconcat, tsort = table.insert, table.concat, table.sort
local strlen, strmatch, strfind, strsub, strformat, strlower, strupper, spad = string.len, string.match, string.find, string.sub, string.format, string.lower, string.upper, string.rep
local pairs, next, type = pairs, next, type
local tostring = tostring

--[===[@debug@
local TEST_MODE = false;
local USE_TEST_DATA = TEST_MODE --and not IsAddOnLoaded("AtlasLoot_Cataclysm");
--@end-debug@]===]


-- print wrappers
-- t_print (testing): what I use while developing....most of these will be commented out in the repository versions
-- dev_print (development): outputs only when AtlasLootAdvancedSearch.dev_mode is true
-- print (release): messages that should be displayed to end-users, shadows the global versions
local function r_print(str) alas_addon:Print(str); end
local function r_printf(str,...) alas_addon:Print(strformat(str,...)); end

local dbgProxy = alas_addon.DebugProxy;
local function isDevMode()			return dbgProxy and dbgProxy:isDevMode() end
local function isInternal()			return dbgProxy and dbgProxy:isInternal() end
local function t_print(str)			if dbgProxy and ENABLE_LOGGING then dbgProxy:t_print(str) end end
local function t_printf(str,...)	if dbgProxy and ENABLE_LOGGING then dbgProxy:t_printf(str, ...) end end
local function dev_print(str)		if dbgProxy and ENABLE_LOGGING then dbgProxy:dev_print(str) end end
local function dev_printf(str, ...)	if dbgProxy and ENABLE_LOGGING then dbgProxy:dev_printf(str,...) end end
local function d_print(str)			if dbgProxy and ENABLE_LOGGING then dbgProxy:d_print(str) end end
local function d_printf(str,...)	if dbgProxy and ENABLE_LOGGING then dbgProxy:d_printf(str,...) end end

local function funcEnter(obj, funcName, formatStr, ...)
	if alas_addon.DebugProxy.isEnabled and ENABLE_LOGGING then
		if formatStr then alas_addon.DebugProxy:TraceEnter(obj,funcName,strformat(formatStr,...));
		else alas_addon.DebugProxy:TraceEnter(obj,funcName); end
	end
end
local function funcExit(obj, funcName, formatStr, ...)
	if alas_addon.DebugProxy.isEnabled and ENABLE_LOGGING then
		if formatStr then alas_addon.DebugProxy:TraceExit(obj,funcName,strformat(formatStr,...));
		else alas_addon.DebugProxy:TraceExit(obj,funcName); end
	end
end
local function t_printSlow(str)			if alas_addon.ALLOW_SLOW_LOGS then t_print(str)		end end
local function t_printfSlow(str,...)	if alas_addon.ALLOW_SLOW_LOGS then t_printf(str,...)	end end

function toggledebug_searchresults()
	SEARCHRESULTS_DEBUG = not SEARCHRESULTS_DEBUG;
end

-- specify TRUE for forceNewCopy if you do not want to use the cached version [if it exists]
function AtlasLootAdvancedSearch:BuildZoneTable(baseTbl, filterIdx, forceNewCopy)
--[===[@debug@
--funcEnter(alas_addon,"BuildZoneTable","baseTbl:%s",tostring(baseTbl));
--@end-debug@]===]
	local result_tbl = baseTbl;
	local function feedZoneTable( atlasLootDataBase )
--[[
	ok, this is one super-nasty data-mining operation....i'll try to make it as clear as possible (mainly so that
	I will remember what the hell all this means the next time I need to modify it  :P).  OK, in the table we're
	working on, here's how the data is laid out:
					{
						[1] =
dungeonRealmsTbl		{																				
realmKey					[AL["Classic Instances"] ] =
realmInstances				{
dungeonIdx						[1] =
dungeonSubZones					{
(next is an implicit array index, which we'll indicate by surrounding brackets with braces for the purpose of this example)
subZoneKey							{[1]}
subZoneTbl							{
										BabbleZone["The Deadmines"], "TheDeadmines1", "Table"
									},
								},
dungeonIdx						[2] =
dungeonSubZones					{
subZoneKey							[BabbleZone["Dire Maul"] ] =
subZoneTbl/subZoneDataArray			{
subZoneInfoTbl[1]						{ BabbleZone["Dire Maul (East)"], "DireMaulEast", "Submenu" },
subZoneInfoTbl[2]						{ BabbleZone["Dire Maul (West)"], "DireMaulWest", "Submenu" },
subZoneInfoTbl[3]						{
	subZoneDisplayName						BabbleZone["Dire Maul (North)"],
	subZoneLookupKey						"DireMaulNorth",
	subZoneLookupType						"Submenu"
										},
									},
								},
							}
						}
						[4] =
dungeonRealmsTbl		{
realmKey					{[1]}
realmInstances				{
dungeonIdx						{[1]}
dungeonSubZones					AL["Crafting"],
dungeonIdx						{[2]}
dungeonSubZones					"CRAFTINGMENU",
dungeonIdx						{[3]}
dungeonSubZones					"Table"
							},
						},
					}
	Notice that I've marked each part with the variable it gets assigned to in the following for-loop-nest.
--]]
		if atlasLootDataBase then
			local dungeonRealmsTbl = atlasLootDataBase;
			-- realmKey, dungeonRealmsTbl is like [0] = originalWoW, [1] = BC, [2] = WotLK
			for realmKey, realmInstances in pairs(dungeonRealmsTbl) do				
				local realmInstanceArray;
				if type(realmKey) == "number" then
					realmInstanceArray = { [realmInstances[1]] = { realmInstances } }
				else
					realmInstanceArray = realmInstances;
				end			
			dev_printf("realmKey:%s(%s)  type(realmInstances):%s", tostring(realmKey), type(realmKey), type(realmInstances));
				
				for dungeonIdx, dungeonSubZones in pairs(realmInstanceArray) do
					if type(dungeonSubZones) ~= "table" then
						dev_printf("  %i) %s(%s)", dungeonIdx, tostring(dungeonSubZones), type(dungeonSubZones));
						dungeonSubZones = { dungeonSubZones }
					end
					
			dev_printf("  dungeonIdx:%s   zones:%i", tostring(dungeonIdx), #dungeonSubZones);
					for subZoneKey, subZoneTbl in pairs(dungeonSubZones) do
			
						local subZoneDataArray;
						-- instanceZoneKey will either be a number or a name, depending on whether the instance has one or more subzones
						if type(subZoneKey) == "number" then
							subZoneDataArray = { subZoneTbl }
						else			
							subZoneDataArray = subZoneTbl;
						end
			dev_printf("    subZoneKey:%s(%s)  num tables:%i", tostring(subZoneKey), type(subZoneKey), #subZoneDataArray);
						
						for zoneDataIdx = 1, #subZoneDataArray do
							local subZoneInfoTbl = subZoneDataArray[zoneDataIdx];
							
							-- this will be the display name for the instance
							local subZoneDisplayName = subZoneInfoTbl[1];

							-- this key will be used to lookup the table of bosses for this dungeon or dungeon subzone.  From there, we can
							-- find the AtlasLoot menu key used for each boss's loot table
							local subZoneLookupKey = subZoneInfoTbl[2];

							-- as far as I can tell, there are two possible values: "Table" and "Submenu"
							local subZoneLookupType = subZoneInfoTbl[3];
			dev_printf("      subZoneLookupKey:%s   subZoneDisplayName:%s   subZoneLookupType:%s", tostring(subZoneLookupKey),tostring(subZoneDisplayName),tostring(subZoneLookupType));
							-- this value is the key we need to lookup the loot table in the AtlasLoot_Data mega-table, as
							-- well as for looking up its diplay name in the AtlasLoot_TableNames table.
							local sectionLootKey;
							
							
							-- It would be great if we could simply assign the subZoneDisplayName to the subZoneLookupKey and call it a day, but it
							-- requires a tad bit more massaging.  For any dungeons or bosses/ecnounters which have multiple loot tables (such as
							-- 10m v 25m raids, normal vs. heroic mode for raids, etc.), only one key is stored in the AtlasLoot static data tables.
							-- The table identifiers for every dungeon, boss, and encounter must first be sent through certain functions in AtlasLoot
							-- to generate the IDs needed to display the loot for all flavors of the dungeon, boss, or encounter.
							-- The only information we'll need to remember is the lookup key & friendly name for both the loot table it comes from, as
							-- well as the dungeon that the loot table belongs to.  This persistence is handled by our ZoneTable (and related) structs
							local currentZone = result_tbl:AddLootTable(subZoneLookupKey, subZoneDisplayName, true);
						
							if subZoneLookupType == "Table" then
								-- this item is associated with a page of loot or a submodule								
								--feedZoneTable(
--								currentZone:CreateChildTables();
								currentZone:AddLootTableVariations(currentZone);
							else
							
--[[
	Then we have the table we use to look up the list of keys which correspond to the sections displayed in the
	Atlas Loot browser once the user selects a specific loot table (i.e. dungeon, collection, etc.) - AtlasLoot_DewDropDown_SubTables.
	It's laid out like so, and the tables containing the data about each section may or may not have an empty string for the value of the
	first element in the table:
					{
subZoneLookupKey		["Scholomance"] =
dungeonSections			{
(1)sectionInfoTbl			{ "", "SCHOLOBloodStewardofKirtonos" },
(2)sectionInfoTbl			{
	sectionDisplayName			"",
	sectionLootKey				"SCHOLOKirtonostheHerald"
							},
						},
subZoneLookupKey		["TrialoftheCrusader_A"] =
dungeonSections			{
(1)sectionInfoTbl			{
	sectionDisplayName			BabbleBoss["The Beasts of Northrend"],
	sectionLootKey				"TrialoftheCrusaderNorthrendBeasts_A"
							},
(2)sectionInfoTbl			{
	sectionDisplayName			AL["Faction Champions"],
	sectionLootKey				"TrialoftheCrusaderFactionChampions_A"
							},
						}
					}
--]]
									
								local dungeonSections = AtlasLoot_DewDropDown_SubTables[subZoneLookupKey];
								for bossIdx = 1, #dungeonSections do
									sectionInfoTbl = dungeonSections[bossIdx];

									-- FINALLY, we're here!!!   :P
									sectionDisplayName = sectionInfoTbl[1];
									sectionLootKey = sectionInfoTbl[2];
									
									currentZone:AddLootTable(sectionLootKey, sectionDisplayName, true, true);
								end
							end
						end
					end
				end
			end
		else
			t_printf("Failed to build zone table - AtlasLoot_DewDropDown not found!");
		end
	end	-- local function

	if baseTbl then
		local lootTableID = result_tbl:GetDataID();
		d_printf("BuildZoneTable from base %s(%s): source atlas table:%s", tostring(lootTableID), type(lootTableID), tostring(AtlasLoot_Data[lootTableID]));
		if lootTableID then
			-- if the data's already loaded, we'll using that....
			local count;
			if AtlasLoot_Data[lootTableID] then
				for idx, row in ipairs(AtlasLoot_Data[lootTableID]) do
					if not filterIdx or type(filterIdx) ~= "number" then
						feedZoneTable(row);
					elseif idx == filterIdx then
						feedZoneTable(row);
						break;
					end
				end
			end
		end
	else
		local populateZoneTable = false;
		result_tbl = self:CreateZoneTableRoot(forceNewCopy);
		
		if result_tbl and result_tbl:GetNumZones() == 0 then
			populateZoneTable = true;
		end
if USE_TEST_DATA then filterIdx = 10; end
		if populateZoneTable and AtlasLoot_DewDropDown then
t_printf("populateZoneTable:%s   AtlasLoot_DewDropDown:%s   result_tbl:%s", tostring(populateZoneTable), tostring(AtlasLoot_DewDropDown), tostring(result_tbl));
			--@non-debug@
			if self:IsVerboseOutputEnabled() then
				r_print("Populating zone table cache...");
			end
			--@end-non-debug@
			--[===[@debug@
			r_print("Populating zone table cache...");
			--@end-debug@]===]
			
			local dropDownCount = #AtlasLoot_DewDropDown;
			for i = 1, dropDownCount do
				if not filterIdx or type(filterIdx) ~= "number" or i == filterIdx or i == 10 then
					feedZoneTable(AtlasLoot_DewDropDown[i]);
				end	-- if not filterIdx or type(filterIdx) ~= "number" or i == filterIdx then
			end	-- for loop
			
			--[===[@debug@
			if alas_addon.dev_mode then
				local tblName = "root";
				if result_tbl.GetCaption and result_tbl:GetCaption() ~= "" then tbnName = result_tbl:GetCaption()
				elseif result_tbl.GetDataID ~= nil and result_tbl:GetDataID() ~= "" then tblName = result_tbl:GetDataID() end
				bt(result_tbl, tblName);
			end
			--@end-debug@]===]
		end
	end
--[===[@debug@
--funcExit(alas_addon,"BuildZoneTable");
--@end-debug@]===]
	return result_tbl;
end


-- replacement for AtlasLoot:GetSearchResultsPage
function AtlasLootAdvancedSearch:ALAS_GetSearchResultPage(AtlasLoot, page)
--[===[@debug@
funcEnter(self, "ALAS_GetSearchResultsPage", " page:%i", page);
--@end-debug@]===]
error("This function should not be getting called - GetSearchResultsPage doesn't appear to require hooking any longer.");
	if not alas_addon.CategorizedSearchResults then
		alas_addon.CategorizedSearchResults = AtlasLoot_CategorizeWishList(AtlasLootCharDB["SearchResult"]);
	end
	local SearchResult = alas_addon.CategorizedSearchResults;
	
	-- Copied straight from the AtlasLoot version (hate to have to do this, but not other way to get around that local "SearchResult" table he's got in Search.lua
	
	-- Calc for maximal pages
	local pageMax = math.ceil(#SearchResult / 30);
	if page < 1 then page = 1 end
	if page > pageMax then page = pageMax end
-- currentPage is a local in AtlasLoot's Search.lua
--	currentPage = page;

	-- Table copy
    local k=1;
	local result = {};
	local start = (page - 1) * 30 + 1;
	for i = start, start + 29 do
		if not SearchResult[i] then break end
--        t_printf(" i:%i   k:%i  SearchResult[%i][1]:%i   %s", i, k, i, SearchResult[i][1], SearchResult[i][4]);
        SearchResult[i][1] = k;
		table.insert(result, SearchResult[i]);
        k=k+1;
	end
--[===[@debug@
funcExit(self, "ALAS_GetSearchResultPage");
--@end-debug@]===]
	return result, pageMax;
end


-- hook for AtlasLoot_GetWishListSubheading
-- get the text or id associated (whichever was registered) with the heading this list item should be inserted under.
-- (Primarily called from ALAS_CategorizeWishList)
local get_wishlist_semaphore = 0;
function AtlasLootAdvancedSearch:ALAS_GetLootItemParentHeader(LootTableID)
--debug item names
--funcEnter(self, "ALAS_GetLootItemParentHeader", " LootTableID:%s", LootTableID);

	local result;
	if LootTableID then
		local zoneTable = alas_addon:BuildZoneTable();
		-- the whole reason for rolling my own solution was that there were some cases where AtlasLoot_GetWishListSubheading
		-- wasn't finding the correct value, and I wasn't able to figure out anyway to hook in at a less intrusive level.
		-- (for example, any boss that exists only in heroic version of dungeons).
		-- Also, I needed the loot item data converted into something that could be easily consumed by outside code [clients] without
		-- requiring them to know every detail of the internal workings of the data construct.
		dev_printf("Beginning search for category for '%s'", tostring(LootTableID));
		local categoryObj = zoneTable:GetLootTable(LootTableID);

		-- AtlasLoot_GetWishListSubHeading doesn't find tables for bosses which only exist in heroic 5m dungeons, so
		-- check for that now
		if not categoryObj then
			categoryObj = zoneTable:GetLootTable(LootTableID .. "HEROIC");
--[===[@debug@
			if categoryObj then
--				t_printf("ALAS_GetLootTableOwnerName found an entry that would have been skipped:%s [%s]", categoryObj:GetCaption(), categoryObj:GetDataID());
			end
--@end-debug@]===]
		end

		-- Originally I was thinking of placing the code for filtering out items from the other faction, but after a bit of thinking, I
		-- believe there is a better place....returning nil from here will simply cause it to add those items to the "Unknown" category
		if categoryObj then
			result = categoryObj:GetCaption();
		end
	end
--debug item names
--funcExit(self, "ALAS_GetLootItemParentHeader", " result:%s", tostring(result));
	return result;
end


--[[
ITEM_QUALITY0_DESC = "Poor";
ITEM_QUALITY1_DESC = "Common";
ITEM_QUALITY2_DESC = "Uncommon";
ITEM_QUALITY3_DESC = "Rare";
ITEM_QUALITY4_DESC = "Epic";
ITEM_QUALITY5_DESC = "Legendary";
ITEM_QUALITY6_DESC = "Artifact";
ITEM_QUALITY7_DESC = "Heirloom";
--]]
local COLORIDX_POOR			= 0;
local COLORIDX_COMMON		= 1;
local COLORIDX_UNCOMMON		= 2;
local COLORIDX_RARE			= 3;
local COLORIDX_EPIC			= 4;
local COLORIDX_LEGENDARY	= 5;
local COLORIDX_HEADER		= 6;
local COLORIDX_ARTIFACT		= 7;
local COLORIDX_HEIRLOOM		= 7;
local colorQualityIndex = {
--[[
	[DARKGREY]	= COLORIDX_POOR,
	[WHITE]		= COLORIDX_COMMON,
	[GREEN]		= COLORIDX_UNCOMMON,
	[BLUE]		= COLORIDX_RARE,
	[PURPLE]	= COLORIDX_EPIC,
	[ORANGE]	= COLORIDX_LEGENDARY,
	[BROWN]		= COLORIDX_ARTIFACT,
	[BROWN]		= COLORIDX_HEIRLOOM,
	[RED]		= COLORIDX_HEADER,
--]]
	["=q0="]	= COLORIDX_POOR,
	["=q1="]	= COLORIDX_COMMON,
	["=q2="]	= COLORIDX_UNCOMMON,
	["=q3="]	= COLORIDX_RARE,
	["=q4="]	= COLORIDX_EPIC,
	["=q5="]	= COLORIDX_LEGENDARY,
	["=q6="]	= COLORIDX_HEADER,
	["=q7="]	= COLORIDX_ARTIFACT,
	["=q7="]	= COLORIDX_HEIRLOOM,
}

TAG_25		= AL["25 Man"];
TAG_HEROIC	= AL["Heroic"];
TAG_25HEROIC	= TAG_25 .. "-" .. TAG_HEROIC;


--[[	Sort_CompareLootCategories
 *	Functor for sorting category headers in search results.  Sorts in the following order:
 * - normal / 10m
 * - heroic / 10m
 * - normal / 25m
 * - heroic / 25m
 *
 *	@return	bool
 *				- true if the first category should be sorted before the second category
 *
--]]
local function Sort_CompareLootCategories(catA, catB)
	local sortAFirst = true;
	if catA == nil then
		sortAFirst = false;
	elseif catB ~= nil then
		local is25ManHeroicA = strfind(catA, TAG_25) ~= nil and strfind(catA, TAG_HEROIC) ~= nil;
		local is25ManHeroicB = strfind(catB, TAG_25) ~= nil and strfind(catB, TAG_HEROIC) ~= nil;

		local is25ManA = (not is25ManHeroicA) and (strfind(catA, TAG_25) ~= nil);
		local is25ManB = (not is25ManHeroicB) and (strfind(catB, TAG_25) ~= nil);

		local isHeroicA = (not is25ManHeroicA) and (not is25ManA) and (strfind(catA, TAG_HEROIC) ~= nil);
		local isHeroicB = (not is25ManHeroicB) and (not is25ManB) and (strfind(catB, TAG_HEROIC) ~= nil);
		if is25ManHeroicA ~= is25ManHeroicB then
			sortAFirst = is25ManHeroicB;
		elseif is25ManA ~= is25ManB then
			sortAFirst = is25ManB;
		elseif isHeroicA ~= isHeroicB then
			sortAFirst = isHeroicB;
		else
			sortAFirst = (catA < catB);
		end
	end
	return sortAFirst;
end

--[[	Sort_CompareLootTable
 * 
 * Functor for sorting items within each subheader in search results.  Sorts first by item quality, then alphabetically.
 *
 * @return	bool
 *				- true if the first item should come before the second item
]]
local PRIMORDIAL_SARONITE_ID = 49908;
local sortOrder = 1;
local function Sort_CompareLootTable(tblA, tblB)
	local sortAFirst = true;

	if ( tblA == nil ) then
		sortAFirst = false;
	elseif ( tblB ~= nil) then
		if tblA[2] == 0 then
			sortAFirst = false;
		elseif tblB[2] ~= 0 then
			-- for some reason, AtlasLoot has Primordial Saronite listed as q4, which is "Epic", but it's actual quality is q3, or "Rare"
			-- (but only for the saronites that drop from bosses; the saronites you buy from the frost emblem vendor is marked correctly)
			itemColorA = string.lower(string.sub(tblA[4], 1, 4));
			itemColorB = string.lower(string.sub(tblB[4], 1, 4));
			if tblA[2] == PRIMORDIAL_SARONITE_ID then itemColorA = "=q3="; end
			if tblB[2] == PRIMORDIAL_SARONITE_ID then itemColorB = "=q3="; end
			if itemColorA ~= itemColorB and colorQualityIndex[itemColorB] and colorQualityIndex[itemColorA] then
				sortAFirst = colorQualityIndex[itemColorA] > colorQualityIndex[itemColorB];
			else
				sortAFirst = tblA[4] < tblB[4];
			end
		end
	end
	
	if sortOrder < 0 then
		sortAFirst = not sortAFirst;
	end
	return sortAFirst;
end

-- my version of AtlasLoot_CategorizeWishList
--[[
--@todo cata: it appears that loot sorting is being handled in a much more robust fashion, and this function hook can probably be removed entirely
function AtlasLootAdvancedSearch:ALAS_CategorizeWishList(wlTable)
--[===[@debug@
funcEnter(self, "ALAS_CategorizeWishList");
--@end-debug@]===]
	-- categories is what holds the strings that you actually see in the browser.
	-- subheadings is a flat map of AtlasLoot_TableNames key => AtlasLoot_TableNames element 1 (localized name)
	
	local subheadings, categories, result = {}, {}, {};

	for idx, v in pairs(wlTable) do
		if v[8] and v[8] ~= "" then
			local dataID = strsplit("|", v[8]);
			
			-- Build subheading table
			if not subheadings[dataID] then
				subheadings[dataID] = AtlasLoot_GetWishListSubheading(dataID);
				-- If search failed, replace ID like "Aldor2" to "Aldor1" and try again
				if not subheadings[dataID] and string.find(dataID, "^%a+%d?$") then
					subheadings[dataID] = AtlasLoot_GetWishListSubheading(strsub(dataID, 1, strlen(dataID) - 1).."1");
				end

				-- If still cant find it, mark it with Unknown
				if not subheadings[dataID] then
					d_printf("failed to find category for %s", dataID);
					subheadings[dataID] = AL["Unknown"] 
				end
			end
			-- Build category tables
			if not categories[ subheadings[dataID] ] then
				dev_printf("%i) adding category %s", idx, subheadings[dataID]);
				categories[ subheadings[dataID] ] = {}
			end
			table.insert(categories[ subheadings[dataID] ], v);
		end
	end
	
	-- sort the category headers, but first need to move them into a table which uses numeric keys
	local categoryNames = {}
	for categoryName, categoryItems in pairs(categories) do
		categoryNames[#categoryNames + 1] = categoryName;
	end
	tsort(categoryNames, Sort_CompareLootCategories);
	
	-- Sort the data within each category, then flatten the category by adding to the results table in a linear fashion
	for categoryIdx = 1, #categoryNames do
		categoryName = categoryNames[categoryIdx];
		categoryItems = categories[categoryName];

--		DevTools_Dump(categoryItems);
		-- Add a empty line between categories when in a same column
		if #result > 1 and #result % 15 > 0 then table.insert(result, { 0, 0, "", "", "" }) end
		-- If a subheading is on the last row of a column, push it to next column
		if (#result + 1) % 15 == 0 then table.insert(result, { 0, 0, "", "", "" }) end
		-- Subheading
		table.insert(result, { 0, 0, "INV_Box_01", "=q6="..categoryName, "" });
		
		-- Sort first then add items
		table.sort(categoryItems, Sort_CompareLootTable); -- not works?
		for i = 1, #categoryItems do table.insert(result, categoryItems[i]) end
	end

--[===[@debug@
funcExit(self, "ALAS_CategorizeWishList");
--@end-debug@]===]
	return result;
end
--]]

local currentPage = 1;

-- my version of AtlasLoot:ShowSearchResult
--[[
@fixme cata: the search results are handled much more robustly now, don't think this function is necessary any longer
function AtlasLootAdvancedSearch:ALAS_OnShowLastSearchResult()
	local anchorFrame;
	if AtlasLootItemsFrame and AtlasLootItemsFrame.refresh and #AtlasLootItemsFrame.refresh > 3 then
		anchorFrame = AtlasLootItemsFrame.refresh[4];
	end
	AtlasLoot_ShowItemsFrame(SEARCHRESULT_TAG, "SearchResultPage"..currentPage, (AL["Search Result: %s"]):format(AtlasLootCharDB.LastSearchedText or ""), anchorFrame);
	
	-- AtlasLoot:ShowSearchResults() called AtlasLoot_ShowItemsFrame, which hides the filter checkbox
	-- when the datasource is SearchResults, so we need to show it again.
	if AtlasLootFilterCheck then
		AtlasLootFilterCheck:Show();
	end
end


--@TODO: need to double-check that this is the correct logic.
function AtlasLootAdvancedSearch:ALAS_OnNavButtonClicked(this)
	if AtlasLootItemsFrame.refresh and AtlasLootItemsFrame.refresh[4] then
		t_printf("AtlasLootAdvancedSearch:ALAS_OnNavButtonClicked  this.lootpage:%s", this.lootpage);
		if strsub(this.lootpage, 1, 16) == "SearchResultPage" then
			currentPage = tonumber(strsub(this.lootpage, 17));
			AtlasLoot_ShowItemsFrame(SEARCHRESULT_TAG, this.lootpage, (AL["Search Result: %s"]):format(AtlasLootCharDB.LastSearchedText or ""), AtlasLootItemsFrame.refresh[4]);
		elseif strsub(this.lootpage, 1, 12) == "WishListPage" then
			AtlasLoot_ShowItemsFrame("WishList", this.lootpage, AL["Wishlist"], AtlasLootItemsFrame.refresh[4]);
		else
			AtlasLoot_ShowItemsFrame(this.lootpage, AtlasLootItemsFrame.refresh[2], "", AtlasLootItemsFrame.refresh[4]);
		end
    else
		--Fallback for if the requested loot page is a menu and does not have a .refresh instance
		currentPage = tonumber(strsub(this.lootpage, 17)) or 1;
		AtlasLoot_ShowItemsFrame(this.lootpage, "", "", AtlasFrame);
	end
	
	-- calling AtlasLoot_ShowItemsFrame will result in hiding the checkbox, so we'll need to reshow it.
	AtlasLootFilterCheck:Show();
end
--]]


