bool UPartyBeaconState::SwapTeams(const FUniqueNetIdRepl& PartyLeader, const FUniqueNetIdRepl& OtherPartyLeader)
{
	bool bSuccess = false;

	int32 ResIdx = GetExistingReservation(PartyLeader);
	int32 OtherResIdx = GetExistingReservation(OtherPartyLeader);

	if (ResIdx != INDEX_NONE && OtherResIdx != INDEX_NONE)
	{
		FPartyReservation& PartyRes = Reservations[ResIdx];
		FPartyReservation& OtherPartyRes = Reservations[OtherResIdx];
		if (PartyRes.TeamNum != OtherPartyRes.TeamNum)
		{
			int32 TeamSize = GetNumPlayersOnTeam(PartyRes.TeamNum);
			int32 OtherTeamSize = GetNumPlayersOnTeam(OtherPartyRes.TeamNum);

			// Will the new teams fit
			bool bValidTeamSizeA = (PartyRes.PartyMembers.Num() + (OtherTeamSize - OtherPartyRes.PartyMembers.Num())) <= NumPlayersPerTeam;
			bool bValidTeamSizeB = (OtherPartyRes.PartyMembers.Num() + (TeamSize - PartyRes.PartyMembers.Num())) <= NumPlayersPerTeam;

			if (bValidTeamSizeA && bValidTeamSizeB)
			{
				Swap(PartyRes.TeamNum, OtherPartyRes.TeamNum);
				bSuccess = true;
			}
		}
	}

	return bSuccess;
}
bool UPartyBeaconState::ReconfigureTeamAndPlayerCount(int32 InNumTeams, int32 InNumPlayersPerTeam, int32 InNumReservations)
{
	bool bSuccess = false;

	//Check total existing reservations against new total maximum
	if (NumConsumedReservations < InNumReservations)
	{
		bool bTeamError = false;
		// Check teams with reservations against new team count
		if (NumTeams > InNumTeams)
		{
			// Any team about to be removed can't have players already there
			for (int32 TeamIdx = InNumTeams; TeamIdx < NumTeams; TeamIdx++)
			{
				if (GetNumPlayersOnTeam(TeamIdx) > 0)
				{
					bTeamError = true;
					UE_LOG(LogBeacon, Warning, TEXT("Beacon has players on a team about to be removed."));
				}
			}
		}

		bool bTeamSizeError = false;
		// Check num players per team against new team size
		if (NumPlayersPerTeam > InNumPlayersPerTeam)
		{
			for (int32 TeamIdx = 0; TeamIdx<NumTeams; TeamIdx++)
			{
				if (GetNumPlayersOnTeam(TeamIdx) > InNumPlayersPerTeam)
				{
					bTeamSizeError = true;
					UE_LOG(LogBeacon, Warning, TEXT("Beacon has too many players on a team about to be resized."));
				}
			}
		}

		if (!bTeamError && !bTeamSizeError)
		{
			NumTeams = InNumTeams;
			NumPlayersPerTeam = InNumPlayersPerTeam;
			MaxReservations = InNumReservations;

			InitTeamArray();
			bSuccess = true;

			UE_LOG(LogBeacon, Display,
				TEXT("Reconfiguring to team count (%d), team size (%d)"),
				NumTeams,
				NumPlayersPerTeam);
		}
	}
	else
	{
		UE_LOG(LogBeacon, Warning, TEXT("Beacon has too many consumed reservations for this reconfiguration, ignoring request."));
	}

	return bSuccess;
}
bool UPartyBeaconState::ChangeTeam(const FUniqueNetIdRepl& PartyLeader, int32 NewTeamNum)
{
	bool bSuccess = false;

	if (NewTeamNum >= 0 && NewTeamNum < NumTeams)
	{
		int32 ResIdx = GetExistingReservation(PartyLeader);
		if (ResIdx != INDEX_NONE)
		{
			FPartyReservation& PartyRes = Reservations[ResIdx];
			if (PartyRes.TeamNum != NewTeamNum)
			{
				int32 OtherTeamSize = GetNumPlayersOnTeam(NewTeamNum);
				bool bValidTeamSize = (PartyRes.PartyMembers.Num() + OtherTeamSize) <= NumPlayersPerTeam;

				if (bValidTeamSize)
				{
					PartyRes.TeamNum = NewTeamNum;
					bSuccess = true;
				}
			}
		}
	}

	return bSuccess;
}
int32 UPartyBeaconState::GetMaxAvailableTeamSize() const
{
	int32 MaxFreeSlots = 0;
	// find the largest available free slots within all the teams
	for (int32 TeamIdx = 0; TeamIdx < NumTeams; TeamIdx++)
	{
		MaxFreeSlots = FMath::Max<int32>(MaxFreeSlots, NumPlayersPerTeam - GetNumPlayersOnTeam(TeamIdx));
	}
	return MaxFreeSlots;
}
bool UPartyBeaconState::AreTeamsAvailable(const FPartyReservation& ReservationRequest) const
{
	int32 IncomingPartySize = ReservationRequest.PartyMembers.Num();
	for (int32 TeamIdx = 0; TeamIdx < NumTeams; TeamIdx++)
	{
		const int32 CurrentPlayersOnTeam = GetNumPlayersOnTeam(TeamIdx);
		if ((CurrentPlayersOnTeam + IncomingPartySize) <= NumPlayersPerTeam)
		{
			return true;
		}
	}
	return false;
}
int32 UPartyBeaconState::GetTeamAssignment(const FPartyReservation& Party)
{
	if (NumTeams > 1)
	{
		TArray<FTeamBalanceInfo> PotentialTeamChoices;
		for (int32 TeamIdx = 0; TeamIdx < NumTeams; TeamIdx++)
		{
			const int32 CurrentPlayersOnTeam = GetNumPlayersOnTeam(TeamIdx);
			if ((CurrentPlayersOnTeam + Party.PartyMembers.Num()) <= NumPlayersPerTeam)
			{
				new (PotentialTeamChoices)FTeamBalanceInfo(TeamIdx, CurrentPlayersOnTeam);
			}
		}

		// Grab one from our list of choices
		if (PotentialTeamChoices.Num() > 0)
		{
			if (TeamAssignmentMethod == ETeamAssignmentMethod::Smallest)
			{
				PotentialTeamChoices.Sort(FSortTeamSizeSmallestToLargest());
				return PotentialTeamChoices[0].TeamIdx;
			}
			else if (TeamAssignmentMethod == ETeamAssignmentMethod::BestFit)
			{
				PotentialTeamChoices.Sort(FSortTeamSizeSmallestToLargest());
				return PotentialTeamChoices[PotentialTeamChoices.Num() - 1].TeamIdx;
			}
			else if (TeamAssignmentMethod == ETeamAssignmentMethod::Random)
			{
				int32 TeamIndex = FMath::Rand() % PotentialTeamChoices.Num();
				return PotentialTeamChoices[TeamIndex].TeamIdx;
			}
		}
		else
		{
			UE_LOG(LogBeacon, Warning, TEXT("UPartyBeaconHost::GetTeamAssignment: couldn't find an open team for party members."));
			return INDEX_NONE;
		}
	}

	return ForceTeamNum;
}
EPartyReservationResult::Type APartyBeaconHost::UpdatePartyReservation(const FPartyReservation& ReservationUpdateRequest)
{
	EPartyReservationResult::Type Result = EPartyReservationResult::GeneralError;

	if (!State || GetBeaconState() == EBeaconState::DenyRequests)
	{
		return EPartyReservationResult::ReservationDenied;
	}

	if (ReservationUpdateRequest.IsValid())
	{
		if (!State->IsBeaconFull())
		{
			const int32 ExistingReservationIdx = State->GetExistingReservation(ReservationUpdateRequest.PartyLeader);
			if (ExistingReservationIdx != INDEX_NONE)
			{
				// Count the number of available slots for the existing reservation's team
				TArray<FPartyReservation>& Reservations = State->GetReservations();
				FPartyReservation& ExistingReservation = Reservations[ExistingReservationIdx];
				const int32 NumTeamMembers = GetNumPlayersOnTeam(ExistingReservation.TeamNum);
				const int32 NumAvailableSlotsOnTeam = FMath::Max<int32>(0, GetMaxPlayersPerTeam() - NumTeamMembers);

				// Read the list of new players and remove the ones that have existing reservation entries
				TArray<FPlayerReservation> NewPlayers;
				for (int32 PlayerIdx = 0; PlayerIdx < ReservationUpdateRequest.PartyMembers.Num(); PlayerIdx++)
				{
					const FPlayerReservation& NewPlayerRes = ReservationUpdateRequest.PartyMembers[PlayerIdx];

					FPlayerReservation* PlayerRes = ExistingReservation.PartyMembers.FindByPredicate(
						[NewPlayerRes](const FPlayerReservation& ExistingPlayerRes)
					{
						return NewPlayerRes.UniqueId == ExistingPlayerRes.UniqueId;
					});

					if (!PlayerRes)
					{
						// player reservation doesn't exist so add it as a new player
						NewPlayers.Add(NewPlayerRes);
					}
					else
					{
						// duplicate entry for this player
						UE_LOG(LogBeacon, Log, TEXT("Skipping player %s"),
							*NewPlayerRes.UniqueId.ToString());
					}
				}

				// Validate that adding the new party members to this reservation entry still fits within the team size
				if (NewPlayers.Num() <= NumAvailableSlotsOnTeam)
				{
					if (NewPlayers.Num() > 0)
					{
						// Copy new player entries into existing reservation
						for (int32 PlayerIdx = 0; PlayerIdx < NewPlayers.Num(); PlayerIdx++)
						{
							const FPlayerReservation& PlayerRes = NewPlayers[PlayerIdx];
							ExistingReservation.PartyMembers.Add(PlayerRes);
							// Keep track of newly added players
							NewPlayerAdded(PlayerRes);
						}

						// Update the reservation count before sending the response
						State->NumConsumedReservations += NewPlayers.Num();

						// Tell any UI and/or clients that there has been a change in the reservation state
						SendReservationUpdates();

						// Tell the owner that we've received a reservation so the UI can be updated
						NotifyReservationEventNextFrame(ReservationChanged);
						if (State->IsBeaconFull())
						{
							// If we've hit our limit, fire the delegate so the host can do the
							// next step in getting parties together
							NotifyReservationEventNextFrame(ReservationsFull);
						}

						Result = EPartyReservationResult::ReservationAccepted;
					}
					else
					{
						// Duplicate entries (or zero) so existing reservation not updated
						Result = EPartyReservationResult::ReservationDuplicate;
					}
				}
				else
				{
					// Send an invalid party size response
					Result = EPartyReservationResult::IncorrectPlayerCount;
				}
			}
			else
			{
				// Send a not found reservation response
				Result = EPartyReservationResult::ReservationNotFound;
			}
		}
		else
		{
			// Send a session full response
			Result = EPartyReservationResult::PartyLimitReached;
		}
	}
	else
	{
		// Invalid reservation
		Result = EPartyReservationResult::ReservationInvalid;
	}	

	return Result;
}