/*
===========================================================================

Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").

Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/
#include "precompiled.h"
#pragma hdrstop
#include "sys_lobby.h"
#include "sys_voicechat.h"




/*
========================
idLobby::SaveDisconnectedUser
========================
*/
void idLobby::SaveDisconnectedUser( const lobbyUser_t& user )
{
	bool found = false;
	for( int i = 0; i < disconnectedUsers.Num(); i++ )
	{
		if( user.lobbyUserID.CompareIgnoreLobbyType( disconnectedUsers[i].lobbyUserID ) )
		{
			found = true;
			memcpy( disconnectedUsers[i].gamertag, user.gamertag, sizeof( user.gamertag ) );
		}
	}
	if( !found )
	{
		disconnectedUser_t& du = disconnectedUsers.Alloc();
		du.lobbyUserID = user.lobbyUserID;
		memcpy( du.gamertag, user.gamertag, sizeof( user.gamertag ) );
	}
}

/*
========================
idLobby::AllocUser
========================
*/
lobbyUser_t* idLobby::AllocUser( const lobbyUser_t& defaults )
{
	if( !verify( freeUsers.Num() > 0 ) )
	{
		idLib::Error( "Out of session users" );		// This shouldn't be possible
	}

	lobbyUser_t* user = freeUsers[freeUsers.Num() - 1];
	freeUsers.SetNum( freeUsers.Num() - 1 );

	// Set defaults
	*user = defaults;

	userList.Append( user );

	assert( userList.Num() == userPool.Max() - freeUsers.Num() );

	return user;
}

/*
========================
idLobby::FreeUser
========================
*/
void idLobby::FreeUser( lobbyUser_t* user )
{
	if( !verify( user != NULL ) )
	{
		return;
	}

	if( !VerifyUser( user ) )
	{
		return;
	}

	SaveDisconnectedUser( *user );

	verify( userList.Remove( user ) );

	freeUsers.Append( user );
}

/*
========================
idLobby::FreeAllUsers
========================
*/
void idLobby::FreeAllUsers()
{
	for( int i = userList.Num() - 1; i >= 0; i-- )
	{
		FreeUser( userList[i] );
	}

	assert( userList.Num() == 0 );
	assert( freeUsers.Num() == userPool.Max() );
}

/*
========================
idLobby::RegisterUser
========================
*/
void idLobby::RegisterUser( lobbyUser_t* lobbyUser )
{
	if( lobbyUser->isBot )
	{
		return;
	}

	// Register the user with the various managers
	bool isLocal = IsSessionUserLocal( lobbyUser );

	if( lobbyBackend != NULL )
	{
		lobbyBackend->RegisterUser( lobbyUser, isLocal );
	}

	sessionCB->GetVoiceChat()->RegisterTalker( lobbyUser, lobbyType, isLocal );
}

/*
========================
idLobby::UnregisterUser
========================
*/
void idLobby::UnregisterUser( lobbyUser_t* lobbyUser )
{
	if( lobbyUser->isBot )
	{
		return;
	}

	if( lobbyUser->IsDisconnected() )
	{
		return;
	}

	bool isLocal = IsSessionUserLocal( lobbyUser );

	if( lobbyBackend != NULL )
	{
		lobbyBackend->UnregisterUser( lobbyUser, isLocal );
	}

	sessionCB->GetVoiceChat()->UnregisterTalker( lobbyUser, lobbyType, isLocal );
}

/*
========================
idLobby::VerifyUser
========================
*/
bool idLobby::VerifyUser( const lobbyUser_t* lobbyUser ) const
{
	if( !verify( userList.FindIndex( const_cast<lobbyUser_t*>( lobbyUser ) ) != -1 ) )
	{
		return false;
	}

	return true;
}

/*
========================
idLobby::IsSessionUserLocal
========================
*/
bool idLobby::IsSessionUserLocal( const lobbyUser_t* lobbyUser ) const
{
	if( !verify( lobbyUser != NULL ) )
	{
		return false;
	}

	if( !VerifyUser( lobbyUser ) )
	{
		return false;
	}

	// This user is local if the peerIndex matches what our peerIndex is on the host
	return ( lobbyUser->peerIndex == peerIndexOnHost );
}

/*
========================
idLobby::IsSessionUserIndexLocal
========================
*/
bool idLobby::IsSessionUserIndexLocal( int i ) const
{
	return IsSessionUserLocal( GetLobbyUser( i ) );
}

/*
========================
idLobby::GetLobbyUserIndexByID
========================
*/
int idLobby::GetLobbyUserIndexByID( lobbyUserID_t lobbyUserId, bool ignoreLobbyType ) const
{
	if( !lobbyUserId.IsValid() )
	{
		return -1;
	}
	assert( lobbyUserId.GetLobbyType() == lobbyType || ignoreLobbyType );

	for( int i = 0; i < GetNumLobbyUsers(); ++ i )
	{
		if( ignoreLobbyType )
		{
			if( GetLobbyUser( i )->lobbyUserID.CompareIgnoreLobbyType( lobbyUserId ) )
			{
				return i;
			}
			continue;
		}
		if( GetLobbyUser( i )->lobbyUserID == lobbyUserId )
		{
			return i;
		}
	}
	return -1;
}

/*
========================
idLobby::GetLobbyUserByID
========================
*/
lobbyUser_t*	 idLobby::GetLobbyUserByID( lobbyUserID_t lobbyUserID, bool ignoreLobbyType )
{
	int index = GetLobbyUserIndexByID( lobbyUserID, ignoreLobbyType );

	if( index == -1 )
	{
		return NULL;
	}

	return GetLobbyUser( index );
}


/*
========================
idLobby::CreateLobbyUserFromLocalUser
This functions just defaults the session users to the signin manager local users
========================
*/
lobbyUser_t idLobby::CreateLobbyUserFromLocalUser( const idLocalUser* localUser )
{

	lobbyUser_t lobbyUser;
	idStr::Copynz( lobbyUser.gamertag, localUser->GetGamerTag(), sizeof( lobbyUser.gamertag ) );
	lobbyUser.peerIndex			= -1;
	lobbyUser.lobbyUserID		= lobbyUserID_t( localUser->GetLocalUserHandle(), lobbyType );	// Generate the lobby using a combination of local user id, and lobby type
	lobbyUser.disconnecting		= false;

	// If we are in a game lobby (or dedicated game state lobby), and we have a party lobby running, assume we can grab the party token from our equivalent user in the party.
	if( ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) && sessionCB->GetPartyLobby().IsLobbyActive() )
	{
		if( ( sessionCB->GetPartyLobby().parms.matchFlags & MATCH_REQUIRE_PARTY_LOBBY ) && !( sessionCB->GetPartyLobby().parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) )
		{
			// copy some things from my party user
			const int myPartyUserIndex = sessionCB->GetPartyLobby().GetLobbyUserIndexByLocalUserHandle( lobbyUser.lobbyUserID.GetLocalUserHandle() );

			if( verify( myPartyUserIndex >= 0 ) )  		// Just in case
			{
				lobbyUser_t* myPartyUser = sessionCB->GetPartyLobby().GetLobbyUser( myPartyUserIndex );
				if( myPartyUser != NULL )
				{
					lobbyUser.partyToken = myPartyUser->partyToken;
				}
			}
		}
	}

	lobbyUser.UpdateClientMutableData( localUser );

	NET_VERBOSE_PRINT( "NET: CreateLobbyUserFromLocalUser: party %08x name %s (%s)\n", lobbyUser.partyToken, lobbyUser.gamertag, GetLobbyName() );

	return lobbyUser;
}

/*
========================
idLobby::InitSessionUsersFromLocalUsers
This functions just defaults the session users to the signin manager local users
========================
*/
void idLobby::InitSessionUsersFromLocalUsers( bool onlineMatch )
{
	assert( lobbyBackend != NULL );

	// First, clear all session users of this session type
	FreeAllUsers();

	// Copy all local users from sign in mgr to the session user list
	for( int i = 0; i < sessionCB->GetSignInManager().GetNumLocalUsers(); i++ )
	{
		idLocalUser* localUser = sessionCB->GetSignInManager().GetLocalUserByIndex( i );

		// Make sure this user can join lobbies
		if( onlineMatch && !localUser->CanPlayOnline() )
		{
			continue;
		}

		lobbyUser_t lobbyUser = CreateLobbyUserFromLocalUser( localUser );

		// Append this new session user to the session user list
		lobbyUser_t* createdUser = AllocUser( lobbyUser );

		// Get the migration game data if this is a migrated hosting
		if( verify( createdUser != NULL ) && migrationInfo.persistUntilGameEndsData.wasMigratedHost )
		{
			createdUser->migrationGameData = migrationInfo.persistUntilGameEndsData.ourGameData;
			NET_VERBOSE_PRINT( "NET: Migration game data set for local user %s at index %d \n", createdUser->gamertag, migrationInfo.persistUntilGameEndsData.ourGameData );
		}
	}
}

/*
========================
idLobby::GetLobbyUserIndexByLocalUserHandle
Takes a local user handle, and converts to a session user
========================
*/
int idLobby::GetLobbyUserIndexByLocalUserHandle( const localUserHandle_t localUserHandle ) const
{
	// Find the session user that uses this input device index
	for( int i = 0; i < GetNumLobbyUsers(); i++ )
	{
		if( !IsSessionUserIndexLocal( i ) )
		{
			continue;	// We only want local users
		}
		if( GetLobbyUser( i )->lobbyUserID.GetLocalUserHandle() == localUserHandle )
		{
			return i;		// Found it
		}
	}

	return -1;
}

/*
========================
idLobby::GetLocalUserFromLobbyUserIndex
This takes a session user, and converts to a local user
========================
*/
idLocalUser* idLobby::GetLocalUserFromLobbyUserIndex( int lobbyUserIndex )
{
	if( lobbyUserIndex < 0 || lobbyUserIndex >= GetNumLobbyUsers() )
	{
		return NULL;
	}

	if( !IsSessionUserIndexLocal( lobbyUserIndex ) )
	{
		return NULL;
	}

	lobbyUser_t* lobbyUser = GetLobbyUser( lobbyUserIndex );

	if( lobbyUser == NULL )
	{
		return NULL;
	}

	return sessionCB->GetSignInManager().GetLocalUserByHandle( lobbyUser->lobbyUserID.GetLocalUserHandle() );
}

/*
========================
idLobby::GetSessionUserFromLocalUser
Takes a local user, and converts to a session user
========================
*/
lobbyUser_t* idLobby::GetSessionUserFromLocalUser( const idLocalUser* localUser )
{
	if( localUser == NULL )
	{
		return NULL;
	}

	int sessionUserIndex = GetLobbyUserIndexByLocalUserHandle( localUser->GetLocalUserHandle() );

	if( sessionUserIndex != -1 )
	{
		assert( IsSessionUserIndexLocal( sessionUserIndex ) );
		return GetLobbyUser( sessionUserIndex );
	}

	return NULL;
}

/*
========================
idLobby::RemoveUsersWithDisconnectedPeers
Go through each user, and remove the ones that have a peer marked as disconnected
NOTE - This should only be called from the host.  The host will call RemoveSessionUsersByIDList,
which will forward the action to the connected peers.
========================
*/
void idLobby::RemoveUsersWithDisconnectedPeers()
{
	if( !verify( IsHost() ) )
	{
		// We're not allowed to do this unless we are the host of this session type
		// If we are the host, RemoveSessionUsersByIDList will forward the call to peers.
		return;
	}

	idList< lobbyUserID_t > removeList;
	for( int u = 0; u < GetNumLobbyUsers(); u++ )
	{
		lobbyUser_t* user = GetLobbyUser( u );
		if( !verify( user != NULL ) )
		{
			continue;
		}
		if( IsSessionUserIndexLocal( u ) || user->IsDisconnected() )
		{
			continue;
		}

		if( user->peerIndex == -1 )
		{
			// Wanting to know if this actually happens.
			// If this is a user on the hosts machine, IsSessionUserIndexLocal should catch it above.
			assert( false );
			// The user is on the host.
			// The host's peer is disconnected via other mechanisms that I don't have a firm
			// grasp on yet.
			continue;
		}

		if( user->peerIndex >= peers.Num() )
		{
			// TTimo - I am hitting this in ~12 client games for some reason?
			// only throwing an assertion in debug, with no crashing, so adding a warning verbose
			idLib::Warning( "idLobby::RemoveUsersWithDisconnectedPeers: user %d %s is out of range in the peers list (%d elements)", u, user->gamertag, peers.Num() );
			continue;
		}
		peer_t& peer = peers[ user->peerIndex ];
		if( peer.GetConnectionState() != CONNECTION_ESTABLISHED )
		{
			removeList.Append( user->lobbyUserID );
		}
	}

	RemoveSessionUsersByIDList( removeList );
}

/*
========================
idLobby::RemoveSessionUsersByIDList
This is the choke point for removing users from a session.
It will handle all the housekeeping of removing from various platform lists (xsession user tracking, etc).
Called from both host and client.
========================
*/
void idLobby::RemoveSessionUsersByIDList( idList< lobbyUserID_t >& usersToRemoveByID )
{
	assert( lobbyBackend != NULL || usersToRemoveByID.Num() == 0 );

	for( int i = 0; i < usersToRemoveByID.Num(); i++ )
	{
		for( int u = 0; u < GetNumLobbyUsers(); u++ )
		{
			lobbyUser_t* user = GetLobbyUser( u );

			if( user->IsDisconnected() )
			{
				// User already disconnected from session  but not removed from the list.
				// This will happen when users are removed during the game.
				continue;
			}

			if( user->lobbyUserID == usersToRemoveByID[i] )
			{
				if( lobbyType == TYPE_GAME )
				{
					idLib::Printf( "NET: %s left the game.\n", user->gamertag );
				}
				else if( lobbyType == TYPE_PARTY )
				{
					idLib::Printf( "NET: %s left the party.\n", user->gamertag );
				}

				UnregisterUser( user );

				// Save the user so we can still get his gamertag, which may be needed for
				// a disconnection HUD message.
				SaveDisconnectedUser( *user );
				FreeUser( user );

				break;
			}
		}
	}

	if( usersToRemoveByID.Num() > 0 && IsHost() )
	{
		if( lobbyBackend != NULL )
		{
			lobbyBackend->UpdateLobbySkill( GetAverageSessionLevel() );
		}

		// If we are the host, send a message to all peers with a list of users who have disconnected
		byte buffer[ idPacketProcessor::MAX_MSG_SIZE ];
		idBitMsg msg( buffer, sizeof( buffer ) );
		msg.WriteByte( usersToRemoveByID.Num() );

		for( int i = 0; i < usersToRemoveByID.Num(); i++ )
		{
			usersToRemoveByID[i].WriteToMsg( msg );
		}
		for( int p = 0; p < peers.Num(); p++ )
		{
			QueueReliableMessage( p, RELIABLE_USER_DISCONNECTED, msg.GetReadData(), msg.GetSize() );
		}
	}
}

/*
========================
idLobby::SendPeersMicStatusToNewUsers
Sends each current user mic status to the newly added peer.
========================
*/
void idLobby::SendPeersMicStatusToNewUsers( int peerNumber )
{
	if( !IsHost() )
	{
		return;
	}

	byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
	idBitMsg outmsg( buffer, sizeof( buffer ) );

	// Count up how many users will be in the msg
	int numUsersInMsg = 0;

	for( int i = 0; i < GetNumLobbyUsers(); ++i )
	{
		lobbyUser_t* user = GetLobbyUser( i );

		if( user->isBot )
		{
			continue;
		}

		if( user->peerIndex == peerNumber )
		{
			continue;
		}

		numUsersInMsg++;
	}

	if( numUsersInMsg == 0 )
	{
		return;		// Nothing to do
	}

	outmsg.WriteLong( numUsersInMsg );

	for( int i = 0; i < GetNumLobbyUsers(); ++i )
	{
		lobbyUser_t* user = GetLobbyUser( i );

		if( user->isBot )
		{
			continue;
		}

		if( user->peerIndex == peerNumber )
		{
			continue;
		}

		int talkerIndex = sessionCB->GetVoiceChat()->FindTalkerByUserId( user->lobbyUserID, lobbyType );
		bool state = sessionCB->GetVoiceChat()->GetHeadsetState( talkerIndex );

		idLib::Printf( "Packing headset state %d for user %d %s\n", state, i, user->gamertag );
		user->lobbyUserID.WriteToMsg( outmsg );
		outmsg.WriteBool( state );
	}


	idLib::Printf( "Sending headset states to new peer %d\n", peerNumber );
	QueueReliableMessage( peerNumber, RELIABLE_HEADSET_STATE, outmsg.GetReadData(), outmsg.GetSize() );
}

/*
========================
idLobby::SendNewUsersToPeers
Sends a range of users to the current list of peers.
The host calls this when he receives new users, to forward the list to the other peers.
========================
*/
void idLobby::SendNewUsersToPeers( int skipPeer, int userStart, int numUsers )
{
	if( !IsHost() )
	{
		return;
	}

	assert( GetNumLobbyUsers() - userStart == numUsers );

	byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
	idBitMsg outmsg( buffer, sizeof( buffer ) );

	// Write number of users
	outmsg.WriteByte( numUsers );

	// Fill up the msg with all the users
	for( int u = userStart; u < GetNumLobbyUsers(); u++ )
	{
		GetLobbyUser( u )->WriteToMsg( outmsg );
	}

	// Send the msg to all peers (except the skipPeer, or peers not connected to this session type)
	for( int p = 0; p < peers.Num(); p++ )
	{
		if( p == skipPeer || peers[p].GetConnectionState() != CONNECTION_ESTABLISHED )
		{
			continue;	// If they are not connected in this session type, don't send anything to them.
		}
		QueueReliableMessage( p, RELIABLE_USER_CONNECTED, outmsg.GetReadData(), outmsg.GetSize() );
	}
}

/*
========================
idLobby::AllocLobbyUserSlotForBot
========================
*/
lobbyUserID_t idLobby::AllocLobbyUserSlotForBot( const char* botName )
{
	lobbyUser_t botSessionUser;
	botSessionUser.peerIndex = peerIndexOnHost;
	botSessionUser.isBot = true;
	botSessionUser.disconnecting = false;
	idStr::Copynz( botSessionUser.gamertag, botName, sizeof( botSessionUser.gamertag ) );

	localUserHandle_t localUserHandle( session->GetSignInManager().GetUniqueLocalUserHandle( botSessionUser.gamertag ) );
	botSessionUser.lobbyUserID = lobbyUserID_t( localUserHandle, lobbyType );

	lobbyUser_t* botUser = NULL;

	int sessionUserIndex = -1;

	// First, try to replace a disconnected user
	for( int i = 0; i < GetNumLobbyUsers(); ++i )
	{
		if( IsLobbyUserDisconnected( i ) )
		{
			lobbyUser_t* user = GetLobbyUser( i );
			if( verify( user != NULL ) )
			{
				*user = botSessionUser;
				botUser = user;
				sessionUserIndex = i;
				break;
			}
		}
	}

	if( botUser == NULL )
	{
		if( freeUsers.Num() == 0 )
		{
			idLib::Warning( "NET: Out Of Session Users - Can't Add Bot %s!", botName );
			return lobbyUserID_t();
		}
		botUser = AllocUser( botSessionUser );
		sessionUserIndex = userList.Num() - 1;
	}

	if( !verify( botUser != NULL ) )
	{
		idLib::Warning( "NET: Can't Find Session Slot For Bot!" );
		return lobbyUserID_t();
	}
	else
	{
		NET_VERBOSE_PRINT( "NET: Created Bot %s At Index %d \n", botUser->gamertag, sessionUserIndex );
	}

	SendNewUsersToPeers( peerIndexOnHost, userList.Num() - 1, 1 ); // bot has been added to the lobby user list - update the peers so that they can see the bot too.

	return GetLobbyUser( sessionUserIndex )->lobbyUserID;
}

/*
========================
idLobby::RemoveBotFromLobbyUserList
========================
*/
void idLobby::RemoveBotFromLobbyUserList( lobbyUserID_t lobbyUserID )
{
	const int index = GetLobbyUserIndexByID( lobbyUserID );

	lobbyUser_t* botUser = GetLobbyUser( index );
	if( botUser == NULL )
	{
		assert( false );
		idLib::Warning( "RemoveBotFromLobbyUserList: Invalid User Index!" );
		return;
	}

	if( !botUser->isBot )
	{
		idLib::Warning( "RemoveBotFromLobbyUserList: User Index Is Not A Bot!" ); // don't accidentally disconnect a human player.
		return;
	}

	botUser->isBot = false;
	botUser->lobbyUserID = lobbyUserID_t();

	FreeUser( botUser );
}

/*
========================
idLobby::GetLobbyUserIsBot
========================
*/
bool idLobby::GetLobbyUserIsBot( lobbyUserID_t lobbyUserID ) const
{
	const int index = GetLobbyUserIndexByID( lobbyUserID );

	const lobbyUser_t* botLobbyUser = GetLobbyUser( index );
	if( botLobbyUser == NULL )
	{
		return false;
	}

	return botLobbyUser->isBot;
}

/*
========================
idLobby::AddUsersFromMsg
Called on peer and host.
Simply parses a msg, and adds any new users from it to our own user list.
If we are the host, we will forward this to all peers except the peer that we just received it from.
========================
*/
void idLobby::AddUsersFromMsg( idBitMsg& msg, int fromPeer )
{
	int userStart	= GetNumLobbyUsers();
	int numNewUsers = msg.ReadByte();

	assert( lobbyBackend != NULL );

	// Add the new users to our own list
	for( int u = 0; u < numNewUsers; u++ )
	{
		lobbyUser_t newUser;

		// Read in the new user
		newUser.ReadFromMsg( msg );

		// Initialize their peerIndex and userID if we are the host
		// (we'll send these back to them in the initial connect)
		if( IsHost() )
		{
			if( fromPeer != -1 )  		// -1 means this is the host adding his own users, and this stuff is already computed
			{
				// local users will already have this information filled out.
				newUser.address		= peers[ fromPeer ].address;
				newUser.peerIndex	= fromPeer;
				if( lobbyType == TYPE_PARTY )
				{
					newUser.partyToken = GetPartyTokenAsHost();
				}
			}
		}
		else
		{
			assert( fromPeer == host );
			// The host sends us all user addresses, except his local users, so we compute that here
			if( newUser.peerIndex == -1 )
			{
				newUser.address	= peers[ fromPeer ].address;
			}
		}

		idLib::Printf( "NET: %s joined (%s) [partyToken = %08x].\n", newUser.gamertag, GetLobbyName(), newUser.partyToken );

		lobbyUser_t* appendedUser = NULL;

		// First, try to replace a disconnected user
		for( int i = 0; i < GetNumLobbyUsers(); i++ )
		{
			lobbyUser_t* user = GetLobbyUser( i );

			if( user->IsDisconnected() )
			{
				userStart = i;
				*user = newUser;
				appendedUser = user;
				break;
			}
		}

		// Add them to our list
		if( appendedUser == NULL )
		{
			appendedUser = AllocUser( newUser );
		}

		// Run platform-specific handler after adding
		assert( appendedUser->peerIndex == newUser.peerIndex );		// paranoia
		assert( appendedUser->lobbyUserID == newUser.lobbyUserID );	// paranoia
		RegisterUser( appendedUser );
	}

	// Forward list of the new users to all other peers
	if( IsHost() )
	{
		SendNewUsersToPeers( fromPeer, userStart, numNewUsers );

		// Set the lobbies skill level
		lobbyBackend->UpdateLobbySkill( GetAverageSessionLevel() );
	}

	idLib::Printf( "---------------- %s --------------------\n", GetLobbyName() );
	for( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex )
	{
		lobbyUser_t* user = GetLobbyUser( userIndex );
		idLib::Printf( "party %08x user %s\n", user->partyToken, user->gamertag );
	}
	idLib::Printf( "---------------- %s --------------------\n", GetLobbyName() );
}

/*
========================
idLobby::UpdateSessionUserOnPeers
========================
*/
void idLobby::UpdateSessionUserOnPeers( idBitMsg& msg )
{
	for( int p = 0; p < peers.Num(); p++ )
	{
		QueueReliableMessage( p, RELIABLE_UPDATE_SESSION_USER, msg.GetReadData() + msg.GetReadCount(), msg.GetSize() - msg.GetReadCount() );
	}

	HandleUpdateSessionUser( msg );
}

/*
========================
idLobby::HandleHeadsetStateChange
========================
*/
void idLobby::HandleHeadsetStateChange( int fromPeer, idBitMsg& msg )
{
	int userCount = msg.ReadLong();

	for( int i = 0; i < userCount; ++i )
	{
		lobbyUserID_t lobbyUserID;
		lobbyUserID.ReadFromMsg( msg );
		bool state = msg.ReadBool();

		int talkerIndex = sessionCB->GetVoiceChat()->FindTalkerByUserId( lobbyUserID, lobbyType );
		sessionCB->GetVoiceChat()->SetHeadsetState( talkerIndex, state );

		idLib::Printf( "User %d headset status: %d\n", talkerIndex, state );

		// If we are the host, let the other clients know about the headset state of this peer
		if( IsHost() )
		{

			// We should not be receiving a message with a user count > 1 if we are the host
			assert( userCount == 1 );

			byte buffer[ idPacketProcessor::MAX_MSG_SIZE ];
			idBitMsg outMsg( buffer, sizeof( buffer ) );
			outMsg.WriteLong( 1 );
			lobbyUserID.WriteToMsg( outMsg );
			outMsg.WriteBool( state );

			for( int j = 0; j < peers.Num(); ++j )
			{
				// Don't send this to the player that we just received the message from
				if( !peers[ j ].IsConnected() || j == fromPeer )
				{
					continue;
				}

				QueueReliableMessage( j, RELIABLE_HEADSET_STATE, outMsg.GetReadData(), outMsg.GetSize() );
			}
		}
	}
}

/*
========================
idLobby::HandleUpdateSessionUser
========================
*/
void idLobby::HandleUpdateSessionUser( idBitMsg& msg )
{
	// FIXME: Use a user id here
	int sessionUserIndex = msg.ReadByte();

	lobbyUser_t* user = GetLobbyUser( sessionUserIndex );

	if( verify( user != NULL ) )
	{
		user->ReadClientMutableData( msg );
	}
}

/*
========================
idLobby::CreateUserUpdateMessage
========================
*/
void idLobby::CreateUserUpdateMessage( int userIndex, idBitMsg& msg )
{
	lobbyUser_t* user = GetLobbyUser( userIndex );

	if( verify( user != NULL ) )
	{
		msg.WriteByte( userIndex );
		user->WriteClientMutableData( msg );
	}
}

/*
========================
idLobby::UpdateLocalSessionUsers
========================
*/
void idLobby::UpdateLocalSessionUsers()
{
	for( int i = 0; i < GetNumLobbyUsers(); i++ )
	{
		idLocalUser* localUser = GetLocalUserFromLobbyUserIndex( i );
		lobbyUser_t* lobbyUser = GetLobbyUser( i );

		if( localUser == NULL || lobbyUser == NULL )
		{
			continue;
		}
		if( !lobbyUser->UpdateClientMutableData( localUser ) )
		{
			continue;
		}

		byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
		idBitMsg msg( buffer, sizeof( buffer ) );

		CreateUserUpdateMessage( i, msg );

		if( IsHost() )
		{
			UpdateSessionUserOnPeers( msg );
		}
		else if( IsPeer() )
		{
			QueueReliableMessage( host, RELIABLE_SESSION_USER_MODIFIED, msg.GetReadData(), msg.GetSize() );
		}
	}
}

/*
========================
idLobby::PeerIndexForSessionUserIndex
========================
*/
int idLobby::PeerIndexForSessionUserIndex( int sessionUserIndex ) const
{
	const lobbyUser_t* user = GetLobbyUser( sessionUserIndex );

	if( !verify( user != NULL ) )
	{
		return -1;
	}

	return user->peerIndex;
}

/*
========================
idLobby::HandleUserConnectFailure
========================
*/
void idLobby::HandleUserConnectFailure( int p, idBitMsg& inMsg, int reliableType )
{
	// Read user to get handle so we can send it back
	inMsg.ReadByte();		// Num users
	lobbyUser_t user;
	user.ReadFromMsg( inMsg );

	// Not enough room, send failure ack
	byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
	idBitMsg msg( buffer, sizeof( buffer ) );
	user.lobbyUserID.GetLocalUserHandle().WriteToMsg( msg );		// Let peer know which user failed to connect

	// Send it
	QueueReliableMessage( p, reliableType, msg.GetReadData(), msg.GetSize() );
}

/*
========================
idLobby::ProcessUserDisconnectMsg
========================
*/
void idLobby::ProcessUserDisconnectMsg( idBitMsg& msg )
{

	idList< lobbyUserID_t > removeList;

	// Convert the msg into a list of id's
	const int numUsers			= msg.ReadByte();

	for( int u = 0; u < numUsers; u++ )
	{
		lobbyUserID_t lobbyUserID;
		lobbyUserID.ReadFromMsg( msg );
		removeList.Append( lobbyUserID );
	}

	RemoveSessionUsersByIDList( removeList );
}

/*
========================
idLobby::CompactDisconnectedUsers
Pack the user list by removing disconnected users
We need to do this, since when in a game, we aren't allowed to remove users from the game session.
========================
*/
void idLobby::CompactDisconnectedUsers()
{
	for( int i = GetNumLobbyUsers() - 1; i >= 0; i-- )
	{
		lobbyUser_t* user = GetLobbyUser( i );
		if( user->IsDisconnected() )
		{
			FreeUser( user );
		}
	}
}

/*
========================
idLobby::RequestLocalUserJoin
Sends a request to the host to join a local user to a session.
If we are the host, we will do it immediately.
========================
*/
void idLobby::RequestLocalUserJoin( idLocalUser* localUser )
{
	assert( IsRunningAsHostOrPeer() );

	// Construct a msg that contains the user connect request
	lobbyUser_t lobbyUser = CreateLobbyUserFromLocalUser( localUser );

	byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
	idBitMsg msg( buffer, sizeof( buffer ) );

	msg.WriteByte( 1 );					// 1 user
	lobbyUser.WriteToMsg( msg );		// Write user

	if( IsHost() )
	{
		AddUsersFromMsg( msg, -1 );
		localUser->SetJoiningLobby( lobbyType, false );
	}
	else
	{
		// Send request to host to add user
		QueueReliableMessage( host, RELIABLE_USER_CONNECT_REQUEST, msg.GetReadData(), msg.GetSize() );
	}
}

/*
========================
idLobby::RequestSessionUserDisconnect
Sends a request to the host to remove a session user from the session.
If we are the host, we will do it immediately.
========================
*/
void idLobby::RequestSessionUserDisconnect( int sessionUserIndex )
{

	if( !IsRunningAsHostOrPeer() )
	{
		// If we are not in an actual running session, just remove it.
		// This is so we accurately reflect the local user list through the session users in the menus, etc.
		// FIXME:
		// This is a total hack, and we should really look at better separation of local users/session users
		// and not rely on session users while in the menus.
		FreeUser( GetLobbyUser( sessionUserIndex ) );
		return;
	}

	lobbyUser_t* lobbyUser = GetLobbyUser( sessionUserIndex );

	if( !verify( lobbyUser != NULL ) )
	{
		return;
	}

	if( lobbyUser->disconnecting == true )
	{
		return;		// Already disconnecting
	}

	byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
	idBitMsg msg( buffer, sizeof( buffer ) );

	msg.WriteByte( 1 );	// 1 user
	lobbyUser->lobbyUserID.WriteToMsg( msg );

	if( IsHost() )
	{
		idBitMsg readMsg;
		readMsg.InitRead( msg.GetReadData(), msg.GetSize() );

		// As the host, just disconnect immediately (we'll still send the notification to all peers though)
		ProcessUserDisconnectMsg( readMsg );
	}
	else
	{
		// Send the message
		QueueReliableMessage( host, RELIABLE_USER_DISCONNECT_REQUEST, msg.GetReadData(), msg.GetSize() );

		// Mark user as disconnecting to make sure we don't keep sending the request
		lobbyUser->disconnecting = true;
	}
}

/*
========================
idLobby::SyncLobbyUsersWithLocalUsers
This function will simply try and keep session[lobbyType].users sync'd up with local users.
As local users come and go, this function will detect that, and send msg's to the host that will
add/remove the users from the session.
========================
*/
void idLobby::SyncLobbyUsersWithLocalUsers( bool allowLocalJoins, bool onlineMatch )
{

	if( lobbyBackend == NULL )
	{
		return;
	}

	if( !IsRunningAsHostOrPeer() )
	{
		return;
	}

	if( allowLocalJoins )
	{
		// If we are allowed to do so, allow local users to join the session user list
		for( int i = 0; i < sessionCB->GetSignInManager().GetNumLocalUsers(); i++ )
		{
			idLocalUser* localUser = sessionCB->GetSignInManager().GetLocalUserByIndex( i );

			if( GetSessionUserFromLocalUser( localUser ) != NULL )
			{
				continue;		// Already in the lobby
			}

			if( localUser->IsJoiningLobby( lobbyType ) )
			{
				continue;		// Already joining lobby if this type
			}

			if( onlineMatch && !localUser->CanPlayOnline() )
			{
				continue;		// Not allowed to join this type of lobby
			}

			localUser->SetJoiningLobby( lobbyType, true );		// We'll reset this when we get the ack for this request
			RequestLocalUserJoin( localUser );
		}
	}

	// Find session users that are no longer allowed to be in the list
	for( int i = GetNumLobbyUsers() - 1; i >= 0; i-- )
	{
		if( !IsSessionUserIndexLocal( i ) )
		{
			continue;
		}

		lobbyUser_t* lobbyUser = GetLobbyUser( i );
		if( lobbyUser != NULL && lobbyUser->isBot )
		{
			continue;
		}

		idLocalUser* localUser = GetLocalUserFromLobbyUserIndex( i );

		if( localUser == NULL || ( onlineMatch && !localUser->CanPlayOnline() ) )
		{
			// Either the session user is no longer in the local user list,
			//	or not allowed to join online lobbies.
			RequestSessionUserDisconnect( i );
		}
		else
		{
			localUser->SetJoiningLobby( lobbyType, false );		// Remove joining lobby flag if we are in the lobby
		}
	}
}

/*
========================
idLobby::IsLobbyUserDisconnected
========================
*/
bool idLobby::IsLobbyUserDisconnected( int userIndex ) const
{
	const lobbyUser_t* user = GetLobbyUser( userIndex );
	if( user == NULL )
	{
		return true;
	}

	if( user->isBot )
	{
		return false;
	}

	if( user->IsDisconnected() )
	{
		return true;
	}

	return false;
}

/*
========================
idLobby::IsLobbyUserValid
========================
*/
bool idLobby::IsLobbyUserValid( lobbyUserID_t lobbyUserID ) const
{
	if( !lobbyUserID.IsValid() )
	{
		return false;
	}

	if( GetLobbyUserIndexByID( lobbyUserID ) == -1 )
	{
		return false;
	}

	return true;
}

/*
========================
idLobby::ValidateConnectedUser
========================
*/
bool idLobby::ValidateConnectedUser( const lobbyUser_t* user ) const
{
	if( user == NULL )
	{
		return false;
	}

	if( user->IsDisconnected() )
	{
		return false;
	}

	if( user->peerIndex == -1 )
	{
		return true;		// Host
	}

	if( IsHost() )
	{
		if( user->peerIndex < 0 || user->peerIndex >= peers.Num() )
		{
			return false;
		}

		if( !peers[user->peerIndex].IsConnected() )
		{
			return false;
		}
	}

	return true;
}

/*
========================
idLobby::IsLobbyUserLoaded
========================
*/
bool idLobby::IsLobbyUserLoaded( lobbyUserID_t lobbyUserID ) const
{
	assert( lobbyType == GetActingGameStateLobbyType() );

	int userIndex = GetLobbyUserIndexByID( lobbyUserID );

	if( !verify( userIndex != -1 ) )
	{
		return false;
	}

	const lobbyUser_t* user = GetLobbyUser( userIndex );

	if( user == NULL )
	{
		return false;
	}

	if( user->isBot )
	{
		return true;
	}

	if( !ValidateConnectedUser( user ) )
	{
		return false;
	}

	if( IsSessionUserLocal( user ) )
	{
		return loaded;		// If this is a local user, check the local loaded flag
	}

	if( !verify( user->peerIndex >= 0 && user->peerIndex < peers.Num() ) )
	{
		return false;
	}

	return peers[user->peerIndex].loaded;
}

/*
========================
idLobby::LobbyUserHasFirstFullSnap
========================
*/
bool idLobby::LobbyUserHasFirstFullSnap( lobbyUserID_t lobbyUserID ) const
{
	assert( lobbyType == GetActingGameStateLobbyType() );

	int userIndex = GetLobbyUserIndexByID( lobbyUserID );

	const lobbyUser_t* user = GetLobbyUser( userIndex );

	if( !ValidateConnectedUser( user ) )
	{
		return false;
	}

	if( user->peerIndex == -1 )
	{
		return false;
	}

	if( peers[user->peerIndex].snapProc->GetFullSnapBaseSequence() < idSnapshotProcessor::INITIAL_SNAP_SEQUENCE )
	{
		return false;
	}

	return true;
}

/*
========================
idLobby::GetLobbyUserIdByOrdinal
========================
*/
lobbyUserID_t idLobby::GetLobbyUserIdByOrdinal( int userIndex ) const
{
	const lobbyUser_t* user = GetLobbyUser( userIndex );
	if( user == NULL )
	{
		return lobbyUserID_t();
	}

	if( user->isBot )
	{
		return user->lobbyUserID;
	}

	if( !ValidateConnectedUser( user ) )
	{
		return lobbyUserID_t();
	}

	return user->lobbyUserID;
}

/*
========================
idLobby::GetLobbyUserIndexFromLobbyUserID
========================
*/
int idLobby::GetLobbyUserIndexFromLobbyUserID( lobbyUserID_t lobbyUserID ) const
{
	return GetLobbyUserIndexByID( lobbyUserID );
}

/*
========================
idLobby::EnableSnapshotsForLobbyUser
========================
*/
void idLobby::EnableSnapshotsForLobbyUser( lobbyUserID_t lobbyUserID )
{
	assert( lobbyType == GetActingGameStateLobbyType() );

	int userIndex = GetLobbyUserIndexByID( lobbyUserID );

	const lobbyUser_t* user = GetLobbyUser( userIndex );

	if( !ValidateConnectedUser( user ) )
	{
		return;
	}

	if( user->peerIndex == -1 )
	{
		return;
	}

	peers[user->peerIndex].pauseSnapshots = false;
}

/*
========================
idLobby::GetAverageSessionLevel
========================
*/
float idLobby::GetAverageSessionLevel()
{
	float	level				= 0.0f;
	int		numActiveMembers	= 0;

	for( int i = 0; i < GetNumLobbyUsers(); i++ )
	{
		const lobbyUser_t* user = GetLobbyUser( i );

		if( user->IsDisconnected() )
		{
			continue;
		}

		level += user->level;
		numActiveMembers++;
	}

	if( numActiveMembers > 0 )
	{
		level /= ( float )numActiveMembers;
	}

	float ret = Max( level, 1.0f );
	NET_VERBOSE_PRINT( "NET: GetAverageSessionLevel %g\n", ret );
	return ret;
}

/*
========================
idLobby::GetAverageLocalUserLevel
========================
*/
float idLobby::GetAverageLocalUserLevel( bool onlineOnly )
{
	float	level				= 0.0f;
	int		numActiveMembers	= 0;

	for( int i = 0; i < sessionCB->GetSignInManager().GetNumLocalUsers(); ++i )
	{
		const idLocalUser* localUser = sessionCB->GetSignInManager().GetLocalUserByIndex( i );

		if( onlineOnly && !localUser->CanPlayOnline() )
		{
			continue;
		}

		const idPlayerProfile* profile = localUser->GetProfile();

		if( profile == NULL )
		{
			continue;
		}

		level += profile->GetLevel();
		numActiveMembers++;
	}

	if( numActiveMembers > 0 )
	{
		level /= ( float )numActiveMembers;
	}

	return Max( level, 1.0f );
}

/*
========================
idLobby::QueueReliablePlayerToPlayerMessage
========================
*/
void idLobby::QueueReliablePlayerToPlayerMessage( int fromSessionUserIndex, int toSessionUserIndex, reliablePlayerToPlayer_t type, const byte* data, int dataLen )
{

	reliablePlayerToPlayerHeader_t info;
	info.fromSessionUserIndex = fromSessionUserIndex;
	info.toSessionUserIndex = toSessionUserIndex;

	// Some kind of pool allocator for packet buffers would be nice.

	byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
	idBitMsg outmsg( buffer, sizeof( buffer ) );
	if( !info.Write( this, outmsg ) )
	{
		idLib::Warning( "NET: Can't queue invalid reliable player to player msg" );
		return;
	}
	outmsg.WriteData( data, dataLen );

	const lobbyUser_t* targetUser = GetLobbyUser( toSessionUserIndex );

	if( !verify( targetUser != NULL ) )
	{
		return;
	}

	const int sendToPeer = IsHost() ? targetUser->peerIndex : host;

	QueueReliableMessage( sendToPeer, RELIABLE_PLAYER_TO_PLAYER_BEGIN + ( int ) type, outmsg.GetReadData(), outmsg.GetSize() );
}

/*
========================
idLobby::KickLobbyUser
========================
*/
void idLobby::KickLobbyUser( lobbyUserID_t lobbyUserID )
{
	if( !IsHost() )
	{
		return;
	}

	const int lobbyUserIndex = GetLobbyUserIndexByID( lobbyUserID );

	lobbyUser_t* user = GetLobbyUser( lobbyUserIndex );

	if( user != NULL && !IsSessionUserLocal( user ) )
	{
		// Send an explicit kick msg, so they know why they were removed
		if( user->peerIndex >= 0 && user->peerIndex < peers.Num() )
		{
			byte buffer[ idPacketProcessor::MAX_MSG_SIZE ];
			idBitMsg msg( buffer, sizeof( buffer ) );
			msg.WriteByte( lobbyUserIndex );
			QueueReliableMessage( user->peerIndex, RELIABLE_KICK_PLAYER, msg.GetReadData(), msg.GetSize() );
		}
	}
}

/*
========================
idLobby::GetNumConnectedUsers
========================
*/
int idLobby::GetNumConnectedUsers() const
{
	int numConnectectUsers = 0;

	for( int i = 0; i < GetNumLobbyUsers(); i++ )
	{
		const lobbyUser_t* user = GetLobbyUser( i );

		if( user->IsDisconnected() )
		{
			continue;
		}

		numConnectectUsers++;
	}

	return numConnectectUsers;
}