
-- Sort lib
local _, app = ...;

-- Concepts:
-- Encapsulates the functionality for performing sort logic against sets of ATT groups

-- Global locals
local ipairs, pairs, tostring, type, string_lower, table_sort, pcall
	= ipairs, pairs, tostring, type, string.lower, table.sort, pcall;

-- App locals

-- Module locals

-- Sorting Logic
local calculateAccessibility = function(source)
	local score = 0;
	if source.nmr then
		score = score + 10;
	end
	if source.nmc then
		score = score + 10;
	end
	if source.rwp then
		score = score + 5;
	end
	if source.e then
		score = score + 1;
	end
	if source.u then
		score = score + 1;
		if source.u < 3 then
			score = score + 100;
		elseif source.u < 4 then
			score = score + 10;
		else
			score = score + 1;
		end
	end
	return score;
end
local sortByAccessibility = function(a, b)
	return calculateAccessibility(a) <= calculateAccessibility(b);
end
local defaultComparison = function(a,b)
	-- If either object doesn't exist
	if a then
		if not b then
			return true;
		end
	elseif b then
		return false;
	else
		-- neither a or b exists, equality returns false
		return false;
	end
	-- If comparing non-tables
	if type(a) ~= "table" or type(b) ~= "table" then
		return a < b;
	end
	local acomp, bcomp;
	-- Maps
	acomp = a.mapID;
	bcomp = b.mapID;
	if acomp then
		if not bcomp then return true; end
	elseif bcomp then
		return false;
	end
	-- Raids/Encounter
	acomp = a.isRaid;
	bcomp = b.isRaid;
	if acomp then
		if not bcomp then return true; end
	elseif bcomp then
		return false;
	end
	-- Headers/Filters
	acomp = a.headerID or a.filterID;
	bcomp = b.headerID or b.filterID;
	if acomp then
		if not bcomp then return true; end
	elseif bcomp then
		return false;
	end
	-- Quests
	acomp = a.questID;
	bcomp = b.questID;
	if acomp then
		if not bcomp then return true; end
	elseif bcomp then
		return false;
	end
	-- Items
	acomp = a.itemID;
	bcomp = b.itemID;
	if acomp then
		if not bcomp then return true; end
	elseif bcomp then
		return false;
	end
	-- Any two similar-type groups via name
	acomp = string_lower(tostring(a.name));
	bcomp = string_lower(tostring(b.name));
	return acomp < bcomp;
end
local defaultTextComparison = function(a,b)
	-- If either object doesn't exist
	if a then
		if not b then
			return true;
		end
	elseif b then
		return false;
	else
		-- neither a or b exists, equality returns false
		return false;
	end
	-- Any two similar-type groups with text
	a = string_lower(tostring(a));
	b = string_lower(tostring(b));
	return a < b;
end
local defaultNameComparison = function(a,b)
	-- If either object doesn't exist
	if a then
		if not b then
			return true;
		end
	elseif b then
		return false;
	else
		-- neither a or b exists, equality returns false
		return false;
	end
	-- Any two similar-type groups with text
	a = string_lower(tostring(a.name));
	b = string_lower(tostring(b.name));
	return a < b;
end
local defaultValueComparison = function(a,b)
	-- If either object doesn't exist
	if a then
		if not b then
			return true;
		end
	elseif b then
		return false;
	else
		-- neither a or b exists, equality returns false
		return false;
	end
	return a < b;
end
local defaultHierarchyComparison = function(a,b)
	-- If either object doesn't exist
	if a then
		if not b then
			return true;
		end
	elseif b then
		return false;
	else
		-- neither a or b exists, equality returns false
		return false;
	end
	local acomp, bcomp;
	acomp = a.g and #a.g or 0;
	bcomp = b.g and #b.g or 0;
	return acomp < bcomp;
end
local defaultTotalComparison = function(a,b)
	-- If either object doesn't exist
	if a then
		if not b then
			return true;
		end
	elseif b then
		return false;
	else
		-- neither a or b exists, equality returns false
		return false;
	end
	local acomp, bcomp;
	acomp = a.total or 0;
	bcomp = b.total or 0;
	return acomp < bcomp;
end
local defaultEventStartComparison = function(a,b)
	-- If either object doesn't exist
	if a then
		if not b then
			return true;
		end
	elseif b then
		return false;
	else
		-- neither a or b exists, equality returns false
		return false;
	end
	local acomp, bcomp;
	acomp = a.nextEvent;
	acomp = acomp and acomp.start or 0;
	bcomp = b.nextEvent;
	bcomp = bcomp and bcomp.start or 0;
	return acomp < bcomp;
end
app.SortDefaults = {
	Accessibility = sortByAccessibility,
	Global = defaultComparison,
	Text = defaultTextComparison,
	Name = defaultNameComparison,
	Value = defaultValueComparison,
	-- Sorts objects first by whether they do not have sub-groups [.g] defined
	Hierarchy = defaultHierarchyComparison,
	-- Sorts objects first by how many total collectibles they contain
	Total = defaultTotalComparison,
	-- Sorts objects first by their nextEvent.Start
	EventStart = defaultEventStartComparison,
};
local function Sort(t, compare, nested)
	if t then
		if not compare then compare = defaultComparison; end
		table_sort(t, compare);
		if nested then
			for i=#t,1,-1 do
				Sort(t[i].g, compare, nested);
			end
		end
	end
end
-- Safely-sorts a table using a provided comparison function and whether to propogate to nested groups
-- Wrapping in a pcall since sometimes the sorted values are able to change while being within the sort method. This causes the 'invalid sort order function' error
app.Sort = function(t, compare, nested)
	pcall(Sort, t, compare, nested);
end
local function GetGroupSortValue(group)
	-- sub-groups on top
	-- >= 1
	if group.g then
		local total = group.total;
		if total then
			local progress = group.progress;
			-- completed groups at the very top, ordered by their own total
			if total == progress then
				-- 3 <= p
				return 2 + total;
			-- partially completed next
			elseif progress and progress > 0 then
				-- 1 < p <= 2
				return 1 + (progress / total);
			-- no completion, ordered by their own total in reverse
			-- 0 < p <= 1
			else
				return (1 / total);
			end
		end
	-- collectibles next
	-- >= 0
	elseif group.collectible then
		-- = 0.5
		if group.collected then
			return 0.5;
		else
			-- 0 <= p < 0.5
			return (group.sortProgress or 0) / 2;
		end
	-- trackables next
	-- -1 <= p <= -0.5
	elseif group.trackable then
		if group.saved then
			return -0.5;
		else
			return -1;
		end
	-- remaining last
	-- = -2
	else
		return -2;
	end
end
-- Sorts a group using the provided sortType, whether to recurse through nested groups, and whether sorting should only take place given the group having a conditional field
local function SortGroup(group, sortType, row, recur, conditionField)
	if group.g then
		-- either sort visible groups or by conditional
        if (not conditionField and group.visible) or (conditionField and group[conditionField]) then
			-- app.PrintDebug("sorting",group.key,group.key and group[group.key],"by",sortType,"recur",recur,"condition",conditionField)
			if sortType == "name" then
				app.Sort(group.g);
			elseif sortType == "progress" then
				local progA, progB;
				app.Sort(group.g, function(a, b)
					progA = GetGroupSortValue(a);
					progB = GetGroupSortValue(b);
					return progA > progB;
				end);
			else
				local sortA, sortB;
				local sortFunc = app.SortDefaults[sortType] or
					(sortType and function(a, b)
						sortA = a and tostring(a[sortType]);
						sortB = b and tostring(b[sortType]);
						return sortA < sortB;
					end) or nil;
				app.Sort(group.g, sortFunc);
			end
			-- since this group was sorted, clear any SortInfo which may have caused it
			group.SortInfo = nil;
		end
		-- TODO: Add more sort types?
		if recur then
			for _,o in ipairs(group.g) do
				SortGroup(o, sortType, nil, recur, conditionField);
			end
		end
	end
	if row then
		row:GetParent():GetParent():Update();
		app.print("Finished Sorting.");
	end
end
app.SortGroup = SortGroup;
-- Allows defining SortGroup data which is only executed when the group is actually expanded
app.SortGroupDelayed = function(group, sortType, row, recur, conditionField)
	-- app.PrintDebug("Delayed Sort defined for",group.text)
	group.SortInfo = { sortType, row, recur, conditionField };
end