--- Sorting.lua
-- Anything used for sorting (it's a mess...)
local _, addonTable = ...

-- Locals
local Sorting = {}

-- Upvalues
local R = Rarity
local CONSTANTS = addonTable.constants
-- Lua APIs
local type = type
local pairs = pairs
local Round = Round

local catOrder = {
	[CONSTANTS.ITEM_CATEGORIES.HOLIDAY] = 0,
	[CONSTANTS.ITEM_CATEGORIES.CLASSIC] = 1,
	[CONSTANTS.ITEM_CATEGORIES.TBC] = 2,
	[CONSTANTS.ITEM_CATEGORIES.WOTLK] = 3,
	[CONSTANTS.ITEM_CATEGORIES.CATA] = 4,
	[CONSTANTS.ITEM_CATEGORIES.MOP] = 5,
	[CONSTANTS.ITEM_CATEGORIES.WOD] = 6,
	[CONSTANTS.ITEM_CATEGORIES.LEGION] = 7,
	[CONSTANTS.ITEM_CATEGORIES.BFA] = 8,
	[CONSTANTS.ITEM_CATEGORIES.SHADOWLANDS] = 9
}

local function compareCategory(a, b)
	if not a or not b then
		return 0
	end
	if type(a) ~= "table" or type(b) ~= "table" then
		return 0
	end
	if (a.cat or "") == (b.cat or "") then
		return (a.name or "") < (b.name or "")
	end
	return (catOrder[a.cat or 0] or 0) < (catOrder[b.cat or 0] or 0)
end

local function compareDifficulty(a, b)
	if not a or not b then
		return 0
	end
	if type(a) ~= "table" or type(b) ~= "table" then
		return 0
	end

	local item
	item = a
	local dropChance = Rarity.Statistics.GetRealDropPercentage(item)
	local medianLoots = Round(math.log(1 - 0.5) / math.log(1 - dropChance))
	local median1 = medianLoots

	item = b
	dropChance = Rarity.Statistics.GetRealDropPercentage(item)
	medianLoots = Round(math.log(1 - 0.5) / math.log(1 - dropChance))
	local median2 = medianLoots

	return (median1 or 0) < (median2 or 0)
end

local function compareProgress(a, b)
	if not a or not b then
		return 0
	end
	if type(a) ~= "table" or type(b) ~= "table" then
		return 0
	end

	local item
	item = a
	local dropChance = Rarity.Statistics.GetRealDropPercentage(item)
	local medianLoots = Round(math.log(1 - 0.5) / math.log(1 - dropChance))
	local median1 = medianLoots
	local progress1 = 0
	if item.attempts or 0 > 0 then
		progress1 = 100 * (1 - math.pow(1 - dropChance, item.attempts or 0))
	end

	item = b
	dropChance = Rarity.Statistics.GetRealDropPercentage(item)
	medianLoots = Round(math.log(1 - 0.5) / math.log(1 - dropChance))
	local median2 = medianLoots
	local progress2 = 0
	if item.attempts or 0 > 0 then
		progress2 = 100 * (1 - math.pow(1 - dropChance, item.attempts or 0))
	end

	return progress1 > progress2
end

local function compareName(a, b)
	if not a or not b then
		return 0
	end
	if type(a) ~= "table" or type(b) ~= "table" then
		return 0
	end
	return (a.name or "") < (b.name or "")
end

local function compareNum(a, b)
	if not a or not b then
		return 0
	end
	if type(a) ~= "table" or type(b) ~= "table" then
		return 0
	end
	return (a.num or 0) < (b.num or 0)
end

local function compareZone(a, b)
	-- Sort by zone text, unless there are multiple zones. Those go at the bottom, sorted by number of zones.
	-- If the item is in your current zone as well as one or more other zones,
	-- we sort it alphabetically instead of putting it at the bottom.
	if not a or not b then
		return 0
	end
	if type(a) ~= "table" or type(b) ~= "table" then
		return 0
	end
	local zoneInfoA = R.Waypoints:GetZoneInfoForItem(a)
	local zoneInfoB = R.Waypoints:GetZoneInfoForItem(b)
	local zoneTextA, inMyZoneA, zoneColorA, numZonesA = zoneInfoA.zoneText, zoneInfoA.inMyZone, zoneInfoA.zoneColor, zoneInfoA.numZones
	local zoneTextB, inMyZoneB, zoneColorB, numZonesB = zoneInfoB.zoneText, zoneInfoB.inMyZone, zoneInfoB.zoneColor, zoneInfoB.numZones
	if numZonesA > 1 and inMyZoneA ~= true then
		zoneTextA = "ZZZZZZZZZZZZZZ"
	end
	if numZonesB > 1 and inMyZoneB ~= true then
		zoneTextB = "ZZZZZZZZZZZZZZ"
	end
	if numZonesA < 10 and numZonesA > 1 and inMyZoneA ~= true then
		zoneTextA = zoneTextA .. "0"
	end
	if numZonesB < 10 and numZonesB > 1 and inMyZoneB ~= true then
		zoneTextB = zoneTextB .. "0"
	end
	if numZonesA > 1 and inMyZoneA ~= true then
		zoneTextA = zoneTextA .. numZonesA
	end
	if numZonesB > 1 and inMyZoneB ~= true then
		zoneTextB = zoneTextB .. numZonesB
	end
	return (zoneTextA or "") < (zoneTextB or "")
end

function Sorting:SortGroup(group, method)

	if method == CONSTANTS.SORT_METHODS.SORT_NONE then return self:DebugNoOp(group) end

	Rarity.Profiling:StartTimer("SortGroup")
	local sortedGroup = group
	if method == CONSTANTS.SORT_METHODS.SORT_NAME then
		sortedGroup = self:sort(group)
	elseif method == CONSTANTS.SORT_METHODS.SORT_DIFFICULTY then
		sortedGroup = self:sort_difficulty(group)
	elseif method == CONSTANTS.SORT_METHODS.SORT_CATEGORY then
		sortedGroup = self:sort_category(group)
	elseif method == CONSTANTS.SORT_METHODS.SORT_ZONE then
		sortedGroup = self:sort_zone(group)
	elseif method == CONSTANTS.SORT_METHODS.SORT_PROGRESS then
		sortedGroup = self:sort_progress(group)
	end
	Rarity.Profiling:EndTimer("SortGroup")

	return sortedGroup
end

-- Minimum impact NO OP "sort" (for debugging purposes); introduced since disabling sorting entirely didn't work
function Sorting:DebugNoOp(t)

	local nt = {}
	local n = 0

	for k, v in pairs(t) do
		if type(v) == "table" and v.name then
			n = n + 1
			nt[n] = v
		end
	end

	return nt
end

local function sort_copy_with(t, comparator)
	local nt = {}
	local n = 0
	for _, v in pairs(t) do
		if type(v) == "table" and v.name then
			n = n + 1
			nt[n] = v
		end
	end
	table.sort(nt, comparator)
	return nt
end

function Sorting:sort(t)
	return sort_copy_with(t, compareName)
end

function Sorting.sort2(t)
	return sort_copy_with(t, compareNum)
end

function Sorting:sort_difficulty(t)
	return sort_copy_with(t, compareDifficulty)
end

function Sorting:sort_progress(t)
	return sort_copy_with(t, compareProgress)
end

function Sorting:sort_category(t)
	return sort_copy_with(t, compareCategory)
end

function Sorting:sort_zone(t)
	return sort_copy_with(t, compareZone)
end

Rarity.Utils.Sorting = Sorting
return Sorting
