Quantcast
local addon, ns = ...

ns.DB = setmetatable({}, {__index = function(t, k)
	return _G[addon .. "DB"][k]
end})

ns.Events = CreateFrame("Frame", addon .. "Frame", UIParent)

ns.Functions = {}

ns.Locals = function()
	return ns.DB, ns.Events, ns.Functions, ns.Strings, ns.L
end

local db, events, F, S, L = ns:Locals()

-------------------- * // ** GENERAL FUNCTIONS ** // * --------------------
function F:count(table)
	local counter = 0
	for k, v in pairs(table) do
		counter = counter + 1
	end
	return counter
end

function F:InTable(table, value)
	local found = false

	for k, v in pairs(table) do
		if v == value then
			return k
		end
	end

	return false
end

F.pairsByKeys = function(_, t, f)
	local a = {}
		for n in pairs(t) do table.insert(a, n) end
		table.sort(a, f)
		local i = 0      -- iterator variable
		local iter = function ()   -- iterator function
			i = i + 1
			if a[i] == nil then return nil
			else return a[i], t[a[i]]
			end
		end
	return iter
end

function F:Print(msg)
	local prefix = "|cff71C671(LMT) |r"
	print(prefix .. msg)
end

function F:SendChatMessage(msg, rw)
	local channel
	rw = rw or false

	for i = 1, GetNumGroupMembers() do
		if GetRaidRosterInfo(i) == GetUnitName("PLAYER") then
			channel = ((UnitInRaid("PLAYER") and select(2, GetRaidRosterInfo(i))>0) and rw) and "RAID_WARNING" or (UnitInRaid("PLAYER") and "RAID" or "PARTY")
		end
	end

	SendChatMessage("(LMT) " .. msg, channel)
end

-------------------- * // ** ADDON FUNCTIONS ** // * --------------------
function ns:ValidateDatabase()
	local currentVersion = GetAddOnMetadata(addon, "Version")
	local defaults = {
		reserves = {},
		settings = {
			AssignToRandomTimer = 10,
			ConfirmAssignToRandom = false,
			ConfirmRollAndAssign = false,
			HideConfirmation = false,
			RollAndAssignTimer = 10,
			SortByName = true
		},
		version = currentVersion
	}

	if db and not db.version then
		-- Fixing db.version 1.0
			db.AssignToRandomTimer = db.AssignToRandomTimer or db.RandomTimer
			db.RollAndAssignTimer = db.RollAndAssignTimer or db.RollTimer
			db.ConfirmAssignToRandom = db.ConfirmAssignToRandom or db.ConfirmRandom
	-- elseif db and db.version < currentVersion then
		-- if db.version == 2.0 then
		-- end
	end

	LootMasterToolsDB = LootMasterToolsDB or defaults

	for k, v in pairs(db.settings) do
		if defaults.settings[k] == nil then
			db.settings[k] = nil
		end
	end

	for k, v in pairs(defaults.settings) do
		if db.settings[k] == nil then
			db.settings[k] = v
		end
	end

	LootMasterToolsDB.version = (db.version and db.version == currentVersion) and db.version or currentVersion
end

-------------------- * // ** Loot.lua ** // * --------------------

local rollers = {}
local slots = {}

function F:AssignLoot(type, item, winner, open_rolls)
	local itemLink, winnerName, losers, tie_announce
	if type == "roll" then
		itemLink =  F:GetRollItemLink(item)
		winner, losers, tie_announce = F:GetRollWinner(item, itemLink)

		if winner == "" then
			F:Print(string.format(S.LootError1, itemLink))
			return
		end

		winnerName = winner[1]
	elseif type == "loot" then
		itemLink = item
		winnerName = winner
	elseif type == "random" then
		itemLink =  item
		winner, losers, tie_announce = F:GetRandomWinner(item, itemLink)
		winnerName = winner[1]
	end

	local itemSlot = F:GetLootSlot(itemLink)

	if not winnerName or not itemSlot then return end

	local itemQTY

	for i = 1, MAX_RAID_MEMBERS do
		if GetMasterLootCandidate(itemSlot, i) == winnerName then
			itemQTY = F:CountQuantity(itemLink)

			GiveMasterLoot(itemSlot, i)
			slots[itemSlot] = {itemLink, itemQTY, type, tie_announce, winnerName, winner, losers, open_rolls}
			break
		end
	end
end

function events:LOOT_SLOT_CLEARED(event, slot, ...)
	if slots[slot] == nil then return end

	local itemLink = slots[slot][1]
	local itemQTY = slots[slot][2]
	local type = slots[slot][3]
	local tie_announce = slots[slot][4]
	local winnerName = slots[slot][5]
	local winner = slots[slot][6]
	local losers = slots[slot][7]
	local open_rolls = slots[slot][8]


	if F:CountQuantity(itemLink) < itemQTY then
		if type == "roll" then
			if tie_announce then
				F:SendChatMessage(string.format(S.TieBreaker, tie_announce, itemLink), true)
				F:SendChatMessage(string.format(S.Winner, winnerName, winner[3]))
				F:SendChatMessage(S.Losers .. F:ParseLosers(losers, winner[2]))
			end
		elseif type == "random" then
			F:SendChatMessage(string.format(S.RandomLootAnnouncement, itemLink), true)
			if tie_announce then
				F:SendChatMessage(string.format(S.TieBreaker, tie_announce, itemLink), true)
			end
			F:SendChatMessage(string.format(S.Winner, winnerName, winner[3]))
			F:SendChatMessage(S.Losers .. F:ParseLosers(losers, winner[2]))
		end

		for k, v in pairs(open_rolls) do
			if itemLink == v then
				open_rolls[k] = nil
				break
			end
		end
	else
		F:Print(string.format(S.LootError2, itemLink, winnerName))
	end

	if F:count(open_rolls) == 0 then
		events:UnregisterEvent("LOOT_ROLLS_COMPLETE")
	end

	slots[slot] = nil
end

function F:CountQuantity(itemLink)
	local available = 0
	for i = 1, GetNumLootItems() do
		if GetLootSlotLink(i) == itemLink then
			available = available + 1
		end
	end

	return available
end

function F:GetLootSlot(itemLink)
	if not LootFrame:IsVisible() then return end

	for i = 1, GetNumLootItems() do
		local link = GetLootSlotLink(i)

		if link == itemLink then
			return i
		end
	end
end

function F:GetRandomWinner(item, itemLink)
	local winner

	table.wipe(rollers)

	for i = 1, GetNumGroupMembers() do
		rollers[i] = {2, math.floor(math.random(1, 100)), GetRaidRosterInfo(i)}
	end

	return F:ParseRolls(rollers, itemLink)
end

function F:GetRollItemLink(item)
	for i = 1, C_LootHistory.GetNumItems() do
		local rollID,  itemLink = C_LootHistory.GetItem(i)

		if rollID == item then
			return itemLink
		end
	end
end

function F:GetRollWinner(item, itemLink)
	for i = 1, C_LootHistory.GetNumItems() do
		local rollID,  itemLink, numPlayers, isDone = C_LootHistory.GetItem(i)

		if rollID == item and isDone then
			table.wipe(rollers)

			for x = 1, numPlayers do
				local name, _, type, roll = C_LootHistory.GetPlayerInfo(i, x)
				rollers[x] = {type, roll, name}
			end

			return F:ParseRolls(rollers, itemLink)
		end
	end
end

function F:PlayerEligibleForLoot(name, slot)
	for i = 1, GetNumGroupMembers() do
	   local player = GetRaidRosterInfo(i)
	   if player == name then
	   		for x = 1, GetNumGroupMembers() do
				if GetMasterLootCandidate(slot, x) == name then
					return select(7, GetRaidRosterInfo(i)) == GetInstanceInfo() and true
				end
		  end
		end
	end
end

function F:ParseLosers(losers, winnerID)
	losers[winnerID] = nil

	local msg = ""
	for k, v in pairs(losers) do
		if msg == "" then
			msg = v[3] .. " (" .. v[2] .. ")"
		else
			msg = msg .. ", " .. v[3] .. " (" .. v[2] .. ")"
		end
	end

	return msg
end

local priorities = {
	[0] = 1, -- pass
	[1] = 3, -- need
	[2] = 2, -- greed
	[3] = 2 -- disenchant
}

local tied = {}

function F:ParseRolls(rolls, itemLink)
	local winnerName, winnerID
	local highPriority = 0
	local highRoll = 0

	for k, v in pairs(rollers) do
		local priority = priorities[v[1]]
		local roll = v[2]
		local name = v[3]
		local eligible = F:PlayerEligibleForLoot(name, 1)

		if roll and eligible then
			if priority > highPriority then
				table.wipe(tied)
				winnerName = name
				winnerID = k
				highRoll = roll
				highPriority = priority
			elseif priority == highPriority then
				if roll > highRoll then
					table.wipe(tied)
					winnerName = name
					winnerID = k
					highRoll = roll
					highPriority = priority
				elseif roll == highRoll then
					if itemLink and highPriority > 0 then
						tinsert(tied, {name, k})
					else
						local tie_breaker = math.floor(math.random(1, 100))

						while tie_breaker == highRoll do
							tie_breaker = math.floor(math.random(1, 100))
						end

						if tie_breaker > highRoll then
							table.wipe(tied)
							winnerName = name
							winnerID = k
							highRoll = roll
							highPriority = priority
						else
							roll = tie_breaker
						end
					end
				end
			end
		elseif not eligible then
			rollers[k] = nil
		end
	end

	local names

	if not winnerID then
		return ""
	elseif F:count(tied) > 0 then
		tinsert(tied, {winnerName, winnerID})

		highRoll = 0
		for k, v in pairs(tied) do
			roll = math.floor(math.random(1, 100))

			while roll == highRoll do
				roll = math.floor(math.random(1, 100))
			end

			if roll > highRoll then
				winnerName = v[1]
				winnerID = v[2]
				highRoll = roll
			end

			rollers[v[2]] = {2, roll, v[1]}
			names = (k == F:count(tied) and (names .. L[" and "]) or (names and (names .. ", ") or "")) .. v[1]
		end

		table.wipe(tied)
	end

	local winner = {winnerName, winnerID, highRoll}

	return winner, rollers, names
end

function F:ProcessOpenRolls(open_rolls)
	if not LootFrame:IsVisible() then return end

	for i = 1, GetNumLootItems() do
		local itemLink = GetLootSlotLink(i)
		for k, v in pairs(open_rolls) do
			if itemLink == v then
				for x = 1, C_LootHistory.GetNumItems() do
					local rollID, _, _, isDone = C_LootHistory.GetItem(x)

					if rollID == k and isDone then
						F:AssignLoot("roll", rollID, nil, open_rolls)
					end
				end
			end
		end
	end
end