-- Upvalues
local R = Rarity

-- Lua API
local table = table

-- WOW API
local C_Calendar = C_Calendar
local GetContainerNumSlots = _G.C_Container.GetContainerNumSlots
local GetContainerItemID = _G.C_Container.GetContainerItemID
local GetContainerItemInfo = _G.C_Container.GetContainerItemInfo
local GetNumSavedInstances = GetNumSavedInstances
local GetSavedInstanceInfo = GetSavedInstanceInfo
local GetNumRandomDungeons = GetNumRandomDungeons
local GetLFGRandomDungeonInfo = GetLFGRandomDungeonInfo
local GetLFGDungeonInfo = GetLFGDungeonInfo
local GetLFGDungeonRewards = GetLFGDungeonRewards
local GetStatistic = GetStatistic
local InCombatLockdown = InCombatLockdown
local GetAchievementNumCriteria = GetAchievementNumCriteria

-- Constants
local NUM_BAG_SLOTS = _G.NUM_BAG_SLOTS
local BOSS_DEAD = _G.BOSS_DEAD

-- Why is this so inconsistently named? Does it have a special meaning?
local rarity_stats = {}

-------------------------------------------------------------------------------------
-- Scan your bags to see if you are in possession of any of the items we want. This is used for BOSS and FISHING methods,
-- and also works as a second line of defense in case other methods fail to notice the item.
-------------------------------------------------------------------------------------
function R:ScanBags()
	table.wipe(Rarity.bagitems)
	for i = 0, NUM_BAG_SLOTS do
		local numSlots = GetContainerNumSlots(i)
		if numSlots then
			for ii = 1, numSlots do
				local id = GetContainerItemID(i, ii)
				if id then
					local containerItemInfo = GetContainerItemInfo(i, ii)
					local qty = containerItemInfo.stackCount
					if qty and qty > 0 then
						if not Rarity.bagitems[id] then
							Rarity.bagitems[id] = 0
						end
						Rarity.bagitems[id] = Rarity.bagitems[id] + qty
					end
				end
			end
		end
	end
end

function R:ScanCalendar(reason)
	self:Debug("Scanning calendar (" .. reason .. ")")

	table.wipe(Rarity.activeHolidayEvents)
	local today = C_DateAndTime.GetCurrentCalendarTime()
	local month, day, year = today.month, today.monthDay, today.year
	local monthInfo = C_Calendar.GetMonthInfo() -- This is the CALENDAR date (as selected)
	local curMonth, curYear = monthInfo.month, monthInfo.year
	local monthOffset = -12 * (curYear - year) + month - curMonth
	local numEvents = C_Calendar.GetNumDayEvents(monthOffset, day)
	local numLoaded = 0

	for i = 1, numEvents, 1 do
		local calendarEvent = C_Calendar.GetDayEvent(monthOffset, day, i)
		assert(calendarEvent.eventID, "Calendar event IDs should now be available in all WOW product lines")

		if calendarEvent.calendarType == "HOLIDAY" then
			Rarity.activeHolidayEvents[calendarEvent.eventID] = calendarEvent
		end
	end
end

function R:ScanInstanceLocks(reason)
	self:Debug("Scanning instance locks (" .. reason .. ")")

	local scanTip = Rarity.GUI.scanTip

	table.wipe(Rarity.lockouts)
	local savedInstances = GetNumSavedInstances()
	for i = 1, savedInstances do
		local instanceName, instanceID, instanceReset, instanceDifficulty, locked, extended, instanceIDMostSig =
			GetSavedInstanceInfo(i)

		-- Legacy code (deprecated)
		if instanceReset > 0 then
			scanTip:ClearLines()
			scanTip:SetInstanceLockEncountersComplete(i)
			for line = 2, scanTip:NumLines() do
				local txtRight = _G["__Rarity_ScanTipTextRight" .. line]:GetText()
				if txtRight then
					if txtRight == BOSS_DEAD then
						self.lockouts[_G["__Rarity_ScanTipTextLeft" .. line]:GetText()] = true
					end
				end
			end
		end

		-- Detailed lockouts saving (stub - I'm leaving the legacy code above untouched, even if it's partly identical)
		if instanceReset > 0 then -- Lockout isn't expired -> Scan it and store the defeated encounter names
			scanTip:ClearLines()
			scanTip:SetInstanceLockEncountersComplete(i)
			for line = 2, scanTip:NumLines() do
				local txtRight = _G["__Rarity_ScanTipTextRight" .. line]:GetText()
				if txtRight then
					if txtRight == BOSS_DEAD then
						local encounterName = _G["__Rarity_ScanTipTextLeft" .. line]:GetText()
						self.lockouts[encounterName] = true
						-- Create containers if this is the first lockout for a given instance
						self.lockouts_detailed[encounterName] = self.lockouts_detailed[encounterName] or {}
						self.lockouts_detailed[encounterName][instanceDifficulty] = self.lockouts_detailed[encounterName][instanceDifficulty]
							or {}
						-- Add this lockout to the container
						self.lockouts_detailed[encounterName][instanceDifficulty] = true
					end
				end
			end
		end
	end

	table.wipe(Rarity.lockouts_holiday)
	local num = GetNumRandomDungeons()
	for i = 1, num do
		local dungeonID, name = GetLFGRandomDungeonInfo(i)
		local _, _, _, _, _, _, _, _, _, _, _, _, _, desc, isHoliday = GetLFGDungeonInfo(dungeonID)
		if isHoliday and dungeonID ~= 828 then -- 828 = ??
			local doneToday = GetLFGDungeonRewards(dungeonID)
			self.lockouts_holiday[dungeonID] = doneToday
		end
	end

	-- This code lists every LFG dungeon ID in the game (up through 1000)
	-- for instanceID = 0, 1000 do
	--	local dungeonName, typeId, subtypeId, minLvl, maxLvl, recLvl, minRecLvl, maxRecLvl, expansionId, groupId, textureName, difficulty, maxPlayers, dungeonDesc, isHoliday = GetLFGDungeonInfo(instanceID)
	--		if dungeonName then
	--			self:Print("instanceID = " .. instanceID .. " Name = " .. dungeonName .. " typeID = " .. tostring(typeId) .. " minLvl = " .. minLvl .. " maxLvl = " .. maxLvl .. " recLvl = " .. recLvl .. " groupId = " .. tostring(groupId) .." maxPlayers = " .. tostring(maxPlayers))
	--		end
	-- end
end

-- TODO: Does this really belong here? I don't think so...
function R:BuildStatistics(reason)
	self.Profiling:StartTimer("Collections.BuildStatistics." .. reason or "UnknownReason")
	self:Debug("Building statistics table (" .. reason .. ")")

	local tbl = {}
	Rarity.lastStatCount = 0

	for k, v in pairs(R.stats_to_scan) do
		local s = GetStatistic(k)
		tbl[k] = (tonumber(s or "0") or 0)
		if not Rarity.db.profile.accountWideStatistics then
			Rarity.db.profile.accountWideStatistics = {}
		end
		local charName = Rarity:GetUnitName("player")
		local charGuid = Rarity:GetUnitGUID("player")
		if charName and charGuid then
			if not Rarity.db.profile.accountWideStatistics[charGuid] then
				Rarity.db.profile.accountWideStatistics[charGuid] = {}
			end
			Rarity.db.profile.accountWideStatistics[charGuid].playerName = charName
			Rarity.db.profile.accountWideStatistics[charGuid].server = GetRealmName() or ""
			if not Rarity.db.profile.accountWideStatistics[charGuid].statistics then
				Rarity.db.profile.accountWideStatistics[charGuid].statistics = {}
			end
			Rarity.db.profile.accountWideStatistics[charGuid].statistics[k] = (tonumber(s or "0") or 0)
		end
		Rarity.lastStatCount = Rarity.lastStatCount + 1
	end

	self.Profiling:EndTimer("Collections.BuildStatistics" .. reason or "UnknownReason")
	return tbl
end

function R:ScanStatistics(reason)
	if InCombatLockdown() then
		return
	end -- Don't do this during combat as it has a tendency to run too long

	self.Profiling:StartTimer("Collections.ScanStatistics." .. reason or "UnknownReason")

	if rarity_stats == nil or (Rarity.lastStatCount or 0) <= 0 then
		self:Debug("Building initial statistics table")
		rarity_stats = self:BuildStatistics(reason)
	end

	local newStats = self:BuildStatistics(reason)

	for kk, vv in pairs(Rarity.items_with_stats) do
		if type(vv) == "table" then
			if
				(vv.requiresHorde and R.Caching:IsHorde())
				or (vv.requiresAlliance and not R.Caching:IsHorde())
				or (not vv.requiresHorde and not vv.requiresAlliance)
			then
				if vv.statisticId and type(vv.statisticId) == "table" then
					local count = 0
					local totalCrossAccount = 0

					for kkk, vvv in pairs(vv.statisticId) do
						local newAmount = newStats[vvv] or 0
						local oldAmount = rarity_stats[vvv] or 0
						count = count + newAmount

						-- Count up the total for this statistic across the entire account
						if Rarity.db.profile.accountWideStatistics then
							for playerGuid, playerData in pairs(Rarity.db.profile.accountWideStatistics) do
								if playerData.statistics then
									totalCrossAccount = totalCrossAccount
										+ (Rarity.db.profile.accountWideStatistics[playerGuid].statistics[vvv] or 0)
								end
							end
						end

						-- One of the statistics has gone up; add one attempt for this item
						if newAmount > oldAmount then
							R:Debug("Statistics indicate a new attempt for " .. vv.name)
							vv.attempts = (vv.attempts or 0) + 1
							self:OutputAttempts(vv, true)
						end
					end

					-- We've never seen any attempts for this yet; update to this player's statistic total
					if count > 0 and (vv.attempts or 0) <= 0 then
						-- We seem to have gathered more attempts on this character than accounted for yet; update to new total
						vv.attempts = count
						self:OutputAttempts(vv, true)
					elseif count > 0 and count > (vv.attempts or 0) and vv.doNotUpdateToHighestStat ~= true then -- Some items don't want us doing this (generally when Blizzard has a statistic overcounting bug)
						R:Debug(
							"Statistics for " .. vv.name .. " are higher than current amount. Updating to " .. count
						)
						vv.attempts = count
						self:OutputAttempts(vv, true)
					end

					-- Cross-account statistic total is higher than the one we have; update to new total
					if totalCrossAccount > (vv.attempts or 0) and vv.doNotUpdateToHighestStat ~= true then
						R:Debug(
							"Account-wide statistics for "
								.. vv.name
								.. " are higher than current amount. Updating to "
								.. totalCrossAccount
						)
						vv.attempts = totalCrossAccount
						self:OutputAttempts(vv, true)
					end
				end
			end
		end
	end

	-- Done with scan; update our saved table to the current scan
	rarity_stats = newStats

	if self.db.profile.debugMode then
		R.stats = rarity_stats
	end

	self.Profiling:EndTimer("Collections.ScanStatistics" .. reason or "UnknownReason")
end
