--[[
	This module's purpose is to assign petIDs to teams and queue where a petID has
	been reassigned (by the server, by caging and relearning a pet, etc) or when a
	missing pet is learned (convert a speciesID placeholder to a live petID).

	UpdateSanctuary should be called after an UpdateOwned during the UpdateUI.

	When a pet is caged, or a total petID reassignment happens by the server, the pet will
	still exist but with a new petID. This module will store enough stats from a petID
	(level, maxHealth, power, speed, rarity) to find its new petID when needed.

	ONLY pets that are in a team or in the leveling queue are stored here.

	[petID] = {count,exists,speciesID,level,maxHealth,power,speed,rarity}
		count is the number of instances this petID is used; when it reaches 0 it's removed.
		exists is set in UpdateOwned and is true when the petID is known to exist.
]]

local _,L = ...
local rematch = Rematch
local settings, saved
local sanctuary

rematch.sanctuaryCandidates = {} -- list of petID candidates for potential replacement

rematch:InitModule(function()
	settings = RematchSettings
	saved = RematchSaved
	sanctuary = settings.Sanctuary
	-- convert 3.x sanctuary to new format
	if settings.SanctuaryPets then
		for _,pet in ipairs(settings.SanctuaryPets) do
			local petID,speciesID,level,maxHealth,power,speed = strsplit(",",pet)
			if petID:len()>0 then
				-- old sanctuary didn't include rarity; rarity will be pulled on next UpdateSanctuary
				settings.Sanctuary[petID] = {0,nil,tonumber(speciesID),tonumber(level),tonumber(maxHealth),tonumber(power),tonumber(speed),0}
			end
		end
		settings.SanctuaryPets = nil -- remove old sanctuary
	end
end)

-- adds a petID to the sanctuary; if update is true then it will update its stats if
-- it already existed.
function rematch:AddToSanctuary(petID,update)
	if petID and petID~=0 then
		local existed = true
		if type(sanctuary[petID])~="table" then
			if rematch:GetIDType(petID)=="pet" then
				local speciesID,_,level = C_PetJournal.GetPetInfoByPetID(petID)
				local _, maxHealth, power, speed, rarity = C_PetJournal.GetPetStats(petID)
				sanctuary[petID] = {0,true,speciesID,level,maxHealth,power,speed,rarity}
			else
				sanctuary[petID] = {0} -- this petID has no stats (likely a speciesiD)
			end
			existed = false
		end
		sanctuary[petID][1] = sanctuary[petID][1] + 1 -- increment occurance of petID
		if existed and update then
			rematch:UpdatePetInSanctuary(petID)
		end
	end
end

-- this updates the stats of a single petID in the sanctuary
-- called during AddToSanctuary if a pet already existed and in UpdateUI to update
-- loadout and summoned pets if they exist.
-- if level has a value, then no need to re-pull value
function rematch:UpdatePetInSanctuary(petID)
	if type(petID)=="string" and sanctuary[petID] then
		local	_,_,level = C_PetJournal.GetPetInfoByPetID(petID)
		if level then
			local _, maxHealth, power, speed, rarity = C_PetJournal.GetPetStats(petID)
			sanctuary[petID][4] = level
			sanctuary[petID][5] = maxHealth
			sanctuary[petID][6] = power
			sanctuary[petID][7] = speed
			sanctuary[petID][8] = rarity
		end
	end
end

-- this is safe to call anytime, but should be called after an UpdateOwned() which
-- flags whether a petID is known to exist
function rematch:UpdateSanctuary(force)
	if rematch.sanctuaryNeedsUpdated or force then
		-- reset counters for all pets
		for petID,info in pairs(sanctuary) do
			sanctuary[petID][1] = 0 -- team counter
		end
		-- go through all teams and add any un-sanctuary'ed pet
		for key,team in pairs(saved) do
			for i=1,3 do
				rematch:AddToSanctuary(team[i][1])
			end
		end
		-- go through the leveling queue and add any un-sanctuary'ed pet
		for _,petID in pairs(settings.LevelingQueue) do
			rematch:AddToSanctuary(petID)
		end
		-- this removes any pets that no longer belong to a team or are in the queue
		for petID,info in pairs(sanctuary) do
			if sanctuary[petID][1]==0 then
				sanctuary[petID] = nil
			end
		end
		-- now go through and see if any invalid petIDs need found
		for petID,info in pairs(sanctuary) do
			if not info[2] then -- if this pet is known not to exist
				local idType = rematch:GetIDType(petID)
				if idType=="pet" then
					local _,_,level = C_PetJournal.GetPetInfoByPetID(petID)
					if not level then -- confirm petID doesn't exist before finding a replacement
						rematch:FindReplacementPet(petID,sanctuary[petID][3])
					end
				elseif idType=="species" then -- a numeric petID that's not 0 is a species with no owned versions
					rematch:FindReplacementPet(nil,petID)
				end
			else -- if pet exists, it's (very likely) a valid petID; update its stats
				rematch:UpdatePetInSanctuary(petID)
			end
		end
		rematch.sanctuaryNeedsUpdated = nil
	end
end

-- in the event of a petID reassignment, or when learning a new speciesID, this will
-- replace all instances of petID (or speciesID if petID not given) in teams
-- when petID has a value, only exact matches will be considered
-- when petID has no value, any of the found speciesID will be considered
function rematch:FindReplacementPet(petID,speciesID)

	local count = type(speciesID)=="number" and C_PetJournal.GetNumCollectedInfo(speciesID)

	if not count or count==0 then
		return -- pet still not known, can leave
	end

	local sanctuaryPet = petID and sanctuary[petID]
	local candidates = rematch.sanctuaryCandidates
	wipe(candidates)

	-- if only one version of this speciesID exists, our work is easy
	if count==1 then
		local _,candidatePetID = C_PetJournal.FindPetIDByName((select(1,C_PetJournal.GetPetInfoBySpeciesID(speciesID))))
		if not petID then -- if no petID was given, we're looking to replace an as-yet unlearned species
			tinsert(candidates,candidatePetID)
		else -- a petID was given, we want to replace only with a pet that matches stats
			local _,_,level = C_PetJournal.GetPetInfoByPetID(candidatePetID)
			local _,maxHealth,power,speed,rarity = C_PetJournal.GetPetStats(candidatePetID)
			if level==sanctuaryPet[4] and maxHealth==sanctuaryPet[5] and power==sanctuaryPet[6] and speed==sanctuaryPet[7] and rarity==sanctuaryPet[8] then
				tinsert(candidates,candidatePetID)
			end
		end
	else
		-- if there are more than 1 copies of the species, we'll need to dig through the roster for the best
		for candidatePetID in rematch.Roster:AllOwnedPets() do
			local candidateSpeciesID,_,level = C_PetJournal.GetPetInfoByPetID(candidatePetID)
			if candidateSpeciesID==speciesID then -- we found one of the intended species
				if petID then -- if petID passed, we're looking for a precise version of this pet
					if level==sanctuaryPet[4] then
						local _, maxHealth, power, speed, rarity = C_PetJournal.GetPetStats(candidatePetID)
						if maxHealth==sanctuaryPet[5] and power==sanctuaryPet[6] and speed==sanctuaryPet[7] and rarity==sanctuaryPet[8] then
							tinsert(candidates,candidatePetID)
						end
					end
				else -- no petID was passed, we want to find/replace the best version of the species just learned
					tinsert(candidates,candidatePetID)
				end
				-- decrement the count to stop looking once we found all copies of the speciesID
				count = count-1
				if count==0 then
					break
				end
			end
		end
	end

	if #candidates==0 then
		return -- no candidates found :(
	end

	-- at this point at least one candidate was found to replace this petID

	local replaced -- becomes true if any pet was replaced

	-- check teams
	for key,team in pairs(saved) do
		for i=1,3 do
			if team[i][1]==(petID or speciesID) then
				if rematch:ReplaceCandidateInTeam(key,petID or speciesID) then
					replaced = true
				end
			end
		end
	end

	-- a pet was replaced, update the UI
	if replaced and (rematch.Frame:IsVisible() or rematch.Journal:IsVisible()) then
		-- in the case of server petID reassignment, this can happen hundreds of times!
		-- wait until they're all done before doing an UpdateUI
		rematch:StartTimer("UpdateUI",0.1,rematch.UpdateUI)
	end

end

function rematch:ReplaceCandidateInTeam(key,petID)
	local candidates = rematch.sanctuaryCandidates
	local team = saved[key]
	for i=1,#candidates do
		-- check to make sure candidate is not already in a team before replacing
		local found
		for j=1,3 do
			if team[j][1]==candidates[i] then
				found = true
			end
		end
		-- this candidate was not found already in the team, safe to replace
		if not found then
			for j=1,3 do
				if team[j][1]==petID then
					team[j][1] = candidates[i]
					return true
				end
			end
		end
	end
end

-- when all of the above fails to find a replacement, pet loading will load
-- a temporary replacement: the highest level of a speciesID it can find
-- the optional teammates are other petIDs on a team; if passed the temporary petID
-- will not be one of these
function rematch:FindTemporaryPetID(speciesID,teammate1,teammate2,teammate3)
	if speciesID then
		local count = C_PetJournal.GetNumCollectedInfo(speciesID)
		if not count then
			return -- this speciesID probably isn't valid
		elseif count==1 then -- only one of this species, return sole petID owned
			return select(2,C_PetJournal.FindPetIDByName((C_PetJournal.GetPetInfoBySpeciesID(speciesID))))
		elseif count>1 then
			-- there's more than one of this speciesID, go through all owned pets
			local bestPetID
			local bestLevel = 0
			for candidatePetID in rematch.Roster:AllOwnedPets() do
				local candidateSpeciesID,_,level = C_PetJournal.GetPetInfoByPetID(candidatePetID)
				if candidateSpeciesID==speciesID and level>bestLevel and candidatePetID~=teammate1 and candidatePetID~=teammate2 and candidatePetID~=teammate3 then
					bestPetID = candidatePetID
					bestLevel = level
					count = count-1
					if count==0 then
						break
					end
				end
			end
			return bestPetID
		end
	end
end

