//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "Session.h"
#include "strtools.h"
#include "matchmaking.h"
#include "utllinkedlist.h"
#include "tslist.h"
#include "hl2orange.spa.h"
#include "dbg.h"

#ifdef IS_WINDOWS_PC
#include "winlite.h"	// for CloseHandle()
#endif

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

extern IXboxSystem *g_pXboxSystem;

#define ASYNC_OK	0
#define ASYNC_FAIL	1

//-----------------------------------------------------------------------------
// Purpose: CSession class
//-----------------------------------------------------------------------------
CSession::CSession()
{
	m_pParent				= NULL;
	m_hSession				= INVALID_HANDLE_VALUE;
	m_SessionState			= SESSION_STATE_NONE;
	m_pRegistrationResults	= NULL;

	ResetSession();
}

CSession::~CSession()
{
	ResetSession();
}

//-----------------------------------------------------------------------------
// Purpose: Reset a session to it's initial state
//-----------------------------------------------------------------------------
void CSession::ResetSession()
{
	// Cleanup first
	switch( m_SessionState )
	{
	case SESSION_STATE_CREATING:
		CancelCreateSession();
		break;

	case SESSION_STATE_MIGRATING:
		// X360TBD:
		break;
	}

	if ( m_hSession != INVALID_HANDLE_VALUE )
	{
		Msg( "ResetSession: Destroying current session.\n" );

		DestroySession();
		m_hSession = INVALID_HANDLE_VALUE;
	}

	SwitchToState( SESSION_STATE_NONE );

	m_bIsHost		= false;
	m_bIsArbitrated	= false;
	m_bUsingQoS		= false;
	m_bIsSystemLink = false;
	Q_memset( &m_nPlayerSlots, 0, sizeof( m_nPlayerSlots ) );
	Q_memset( &m_SessionInfo, 0, sizeof( m_SessionInfo ) );

	if ( m_pRegistrationResults )
	{
		delete m_pRegistrationResults;
	}

	m_nSessionFlags	= 0;
}

//-----------------------------------------------------------------------------
// Purpose: Set session contexts and properties
//-----------------------------------------------------------------------------
void CSession::SetContext( const uint nContextId, const uint nContextValue, const bool bAsync )
{
	g_pXboxSystem->UserSetContext( XBX_GetPrimaryUserId(), nContextId, nContextValue, bAsync );
}
void CSession::SetProperty( const uint nPropertyId, const uint cbValue, const void *pvValue, const bool bAsync )
{
	g_pXboxSystem->UserSetProperty( XBX_GetPrimaryUserId(), nPropertyId, cbValue, pvValue, bAsync );
}

//-----------------------------------------------------------------------------
// Purpose: Send a notification to GameUI
//-----------------------------------------------------------------------------
void CSession::SendNotification( SESSION_NOTIFY notification )
{
	Assert( m_pParent );
	m_pParent->SessionNotification( notification );
}

//-----------------------------------------------------------------------------
// Purpose: Update the number of player slots filled
//-----------------------------------------------------------------------------
void CSession::UpdateSlots( const CClientInfo *pClient, bool bAddPlayers )
{
	int cPlayers = pClient->m_cPlayers;
	if ( bAddPlayers )
	{
		if ( pClient->m_bInvited )
		{
			// Fill private slots first, then overflow into public slots
			m_nPlayerSlots[SLOTS_FILLEDPRIVATE] += cPlayers;
			pClient->m_numPrivateSlotsUsed = cPlayers;
			cPlayers = 0;
			if ( m_nPlayerSlots[SLOTS_FILLEDPRIVATE] > m_nPlayerSlots[SLOTS_TOTALPRIVATE] )
			{
				cPlayers = m_nPlayerSlots[SLOTS_FILLEDPRIVATE] - m_nPlayerSlots[SLOTS_TOTALPRIVATE];
				pClient->m_numPrivateSlotsUsed -= cPlayers;
				m_nPlayerSlots[SLOTS_FILLEDPRIVATE] = m_nPlayerSlots[SLOTS_TOTALPRIVATE];
			}
		}
		m_nPlayerSlots[SLOTS_FILLEDPUBLIC] += cPlayers;
		if ( m_nPlayerSlots[SLOTS_FILLEDPUBLIC] > m_nPlayerSlots[SLOTS_TOTALPUBLIC] )
		{
			//Handle error
			Warning( "Too many players!\n" );
		}
	}
	else
	{
		// The cast to 'int' is needed since otherwise underflow will wrap around to very large
		// numbers and the 'max' macro will do nothing.
		m_nPlayerSlots[SLOTS_FILLEDPRIVATE] = max( 0, (int)( m_nPlayerSlots[SLOTS_FILLEDPRIVATE] - pClient->m_numPrivateSlotsUsed ) );
		m_nPlayerSlots[SLOTS_FILLEDPUBLIC]  = max( 0, (int)( m_nPlayerSlots[SLOTS_FILLEDPUBLIC]  - ( pClient->m_cPlayers - pClient->m_numPrivateSlotsUsed ) ) );
	}

}

//-----------------------------------------------------------------------------
// Purpose: Join players on the local client
//-----------------------------------------------------------------------------
void CSession::JoinLocal( const CClientInfo *pClient )
{
	uint  nUserIndex[MAX_PLAYERS_PER_CLIENT] = {0};
	bool  bPrivate[MAX_PLAYERS_PER_CLIENT] = {0};

	for( int i = 0; i < pClient->m_cPlayers; ++i )
	{
		nUserIndex[i] = pClient->m_iControllers[i];
		bPrivate[i] = pClient->m_bInvited;
	}

	// X360TBD: Make async?
	uint ret = g_pXboxSystem->SessionJoinLocal( m_hSession, pClient->m_cPlayers, nUserIndex, bPrivate, false );
	if ( ret != ERROR_SUCCESS )
	{
		// Handle error
		Warning( "Failed to add local players\n" );
	}
	else
	{
		UpdateSlots( pClient, true );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Join players on a remote client
//-----------------------------------------------------------------------------
void CSession::JoinRemote( const CClientInfo *pClient )
{
	XUID  xuids[MAX_PLAYERS_PER_CLIENT] = {0};
	bool  bPrivate[MAX_PLAYERS_PER_CLIENT] = {0};

	for( int i = 0; i < pClient->m_cPlayers; ++i )
	{
		xuids[i] = pClient->m_xuids[i];
		bPrivate[i] = pClient->m_bInvited;
	}

	// X360TBD: Make async?
	uint ret = g_pXboxSystem->SessionJoinRemote( m_hSession, pClient->m_cPlayers, xuids, bPrivate, false );
	if ( ret != ERROR_SUCCESS )
	{
		// Handle error
		Warning( "Join Remote Error\n" );
	}
	else
	{
		UpdateSlots( pClient, true );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Remove players on the local client
//-----------------------------------------------------------------------------
void CSession::RemoveLocal( const CClientInfo *pClient )
{
	uint  nUserIndex[MAX_PLAYERS_PER_CLIENT] = {0};

	for( int i = 0; i < pClient->m_cPlayers; ++i )
	{
		nUserIndex[i] = pClient->m_iControllers[i];
	}

	// X360TBD: Make async?
	uint ret = g_pXboxSystem->SessionLeaveLocal( m_hSession, pClient->m_cPlayers, nUserIndex, false );
	if ( ret != ERROR_SUCCESS )
	{
		// Handle error
		Warning( "Failed to remove local players\n" );
	}
	else
	{
		UpdateSlots( pClient, false );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Remove players on a remote client
//-----------------------------------------------------------------------------
void CSession::RemoveRemote( const CClientInfo *pClient )
{
	XUID  xuids[MAX_PLAYERS_PER_CLIENT] = {0};

	for( int i = 0; i < pClient->m_cPlayers; ++i )
	{
		xuids[i] = pClient->m_xuids[i];
	}

	// X360TBD: Make async?
	uint ret = g_pXboxSystem->SessionLeaveRemote( m_hSession, pClient->m_cPlayers, xuids, false );
	if ( ret != ERROR_SUCCESS )
	{
		// Handle error
		Warning( "Failed to remove remote players\n" );
	}
	else
	{
		UpdateSlots( pClient, false );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Create a new session
//-----------------------------------------------------------------------------
bool CSession::CreateSession()
{
	if( INVALID_HANDLE_VALUE != m_hSession )
	{
		Warning( "CreateSession called on existing session!" );
		DestroySession();
		m_hSession = INVALID_HANDLE_VALUE;
	}

	uint flags = m_nSessionFlags;
	if( m_bIsHost )
	{
		flags |= XSESSION_CREATE_HOST;
	}

	if ( flags & XSESSION_CREATE_USES_ARBITRATION )
	{
		m_bIsArbitrated = true;
	}

	m_hCreateHandle = g_pXboxSystem->CreateAsyncHandle();

	// Create the session
 	uint ret = g_pXboxSystem->CreateSession( flags,
											 XBX_GetPrimaryUserId(),
											 m_nPlayerSlots[SLOTS_TOTALPUBLIC],
											 m_nPlayerSlots[SLOTS_TOTALPRIVATE],
											 &m_SessionNonce,
											 &m_SessionInfo,
											 &m_hSession,
											 true,
											 &m_hCreateHandle );

	if( ret != ERROR_SUCCESS && ret != ERROR_IO_PENDING )
	{
		Warning( "XSessionCreate failed with error %d\n", ret );
		return false;
	}

	SwitchToState( SESSION_STATE_CREATING );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Check for completion while creating a new session
//-----------------------------------------------------------------------------
void CSession::UpdateCreating()
{
	DWORD ret = g_pXboxSystem->GetOverlappedResult( m_hCreateHandle, NULL, false );
	if ( ret == ERROR_IO_INCOMPLETE )
	{
		// Still waiting
		return;
	}
	else
	{
		SESSION_STATE nextState = SESSION_STATE_IDLE;

		// Operation completed
		SESSION_NOTIFY notification = IsHost() ? SESSION_NOTIFY_CREATED_HOST : SESSION_NOTIFY_CREATED_CLIENT;
		if ( ret != ERROR_SUCCESS )
		{
			Warning( "CSession: CreateSession failed. Error %d\n", ret );

			nextState = SESSION_STATE_NONE;
			notification = SESSION_NOTIFY_FAIL_CREATE;
		}

		g_pXboxSystem->ReleaseAsyncHandle( m_hCreateHandle );

		SendNotification( notification );
		SwitchToState( nextState );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Cancel async session creation
//-----------------------------------------------------------------------------
void CSession::CancelCreateSession()
{
	if ( m_SessionState != SESSION_STATE_CREATING )
		return;

	g_pXboxSystem->CancelOverlappedOperation( &m_hCreateHandle );
	g_pXboxSystem->ReleaseAsyncHandle( m_hCreateHandle );

#ifndef POSIX
	if( INVALID_HANDLE_VALUE != m_hSession )
	{
		CloseHandle( m_hSession );
		m_hSession = INVALID_HANDLE_VALUE;
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: Close an existing session
//-----------------------------------------------------------------------------
void CSession::DestroySession()
{	
	// TODO: Make this async
	uint ret = g_pXboxSystem->DeleteSession( m_hSession, false );
	if ( ret != ERROR_SUCCESS )
	{
		Warning( "Failed to delete session with error %d.\n", ret );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Register for arbritation in a ranked match
//-----------------------------------------------------------------------------
void CSession::RegisterForArbitration()
{
	uint bytes = 0;
	m_pRegistrationResults = NULL;

	// Call once to determine size of results buffer
	int ret = g_pXboxSystem->SessionArbitrationRegister( m_hSession, 0, m_SessionNonce, &bytes, NULL, false );
	if ( ret != ERROR_INSUFFICIENT_BUFFER )
	{
		Warning( "Failed registering for arbitration\n" );
		return;
	}

	m_pRegistrationResults = (XSESSION_REGISTRATION_RESULTS*)new byte[ bytes ];

	m_hRegisterHandle = g_pXboxSystem->CreateAsyncHandle();

	ret = g_pXboxSystem->SessionArbitrationRegister( m_hSession, 0, m_SessionNonce, &bytes, m_pRegistrationResults, true, &m_hRegisterHandle );
	if( ret != ERROR_IO_PENDING )
	{
		Warning( "Failed registering for arbitration\n" );
	}

	m_SessionState = SESSION_STATE_REGISTERING;
}

//-----------------------------------------------------------------------------
// Purpose: Check for completion of arbitration registration
//-----------------------------------------------------------------------------
void CSession::UpdateRegistering()
{
	DWORD ret = g_pXboxSystem->GetOverlappedResult( m_hRegisterHandle, NULL, false );
	if ( ret == ERROR_IO_INCOMPLETE )
	{
		// Still waiting
		return;
	}
	else
	{
		SESSION_STATE nextState = SESSION_STATE_IDLE;

		// Operation completed
		SESSION_NOTIFY notification = SESSION_NOTIFY_REGISTER_COMPLETED;
		if ( ret != ERROR_SUCCESS )
		{
			Warning( "CSession: Registration failed. Error %d\n", ret );

			nextState = SESSION_STATE_NONE;
			notification = SESSION_NOTIFY_FAIL_REGISTER;
		}

		g_pXboxSystem->ReleaseAsyncHandle( m_hRegisterHandle );

		SendNotification( notification );
		SwitchToState( nextState );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Migrate the session to a new host
//-----------------------------------------------------------------------------
bool CSession::MigrateHost()
{
	if ( IsHost() )
	{
		// Migrate call will fill this in for us
		Q_memcpy( &m_NewSessionInfo, &m_SessionInfo, sizeof( m_NewSessionInfo ) );
	}

	m_hMigrateHandle = g_pXboxSystem->CreateAsyncHandle();

	int ret = g_pXboxSystem->SessionMigrate( m_hSession, m_nOwnerId, &m_NewSessionInfo, true, &m_hMigrateHandle );
	if ( ret != ERROR_IO_PENDING )
	{
		return false;
	}

	SwitchToState( SESSION_STATE_MIGRATING );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Check for completion while migrating a session
//-----------------------------------------------------------------------------
void CSession::UpdateMigrating()
{
	DWORD ret = g_pXboxSystem->GetOverlappedResult( m_hMigrateHandle, NULL, false );
	if ( ret == ERROR_IO_INCOMPLETE )
	{
		// Still waiting
		return;
	}
	else
	{
		SESSION_STATE nextState = SESSION_STATE_IDLE;

		// Operation completed
		SESSION_NOTIFY notification = SESSION_NOTIFY_MIGRATION_COMPLETED;
		if ( ret != ERROR_SUCCESS )
		{
			Warning( "CSession: MigrateSession failed. Error %d\n", ret );

			nextState = SESSION_STATE_NONE;
			notification = SESSION_NOTIFY_FAIL_MIGRATE;
		}

		g_pXboxSystem->ReleaseAsyncHandle( m_hMigrateHandle );

		SendNotification( notification );
		SwitchToState( nextState );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Change state
//-----------------------------------------------------------------------------
void CSession::SwitchToState( SESSION_STATE newState )
{
	m_SessionState = newState;
}

//-----------------------------------------------------------------------------
// Purpose: Per-frame update
//-----------------------------------------------------------------------------
void CSession::RunFrame()
{
 	switch( m_SessionState )
 	{
 	case SESSION_STATE_CREATING:
		UpdateCreating();
 		break;
 
	case SESSION_STATE_REGISTERING:
		UpdateRegistering();
		break;

	case SESSION_STATE_MIGRATING:
		UpdateMigrating();
		break;

 	default:
 		break;
 	}
}

//-----------------------------------------------------------------------------
// Accessors
//-----------------------------------------------------------------------------
HANDLE CSession::GetSessionHandle()
{
	return m_hSession;
}
void CSession::SetSessionInfo( XSESSION_INFO *pInfo )
{
	memcpy( &m_SessionInfo, pInfo, sizeof( XSESSION_INFO ) );
}
void CSession::SetNewSessionInfo( XSESSION_INFO *pInfo )
{
	memcpy( &m_NewSessionInfo, pInfo, sizeof( XSESSION_INFO ) );
}
void CSession::GetSessionInfo( XSESSION_INFO *pInfo )
{
	Assert( pInfo );
	memcpy( pInfo, &m_SessionInfo, sizeof( XSESSION_INFO ) );
}
void CSession::GetNewSessionInfo( XSESSION_INFO *pInfo )
{
	Assert( pInfo );
	memcpy( pInfo, &m_NewSessionInfo, sizeof( XSESSION_INFO ) );
}
void CSession::SetSessionNonce( int64 nonce )
{
	m_SessionNonce = nonce;
}
uint64 CSession::GetSessionNonce()
{
	return m_SessionNonce;
}
XNKID CSession::GetSessionId()
{
	return m_SessionInfo.sessionID;
}
void CSession::SetSessionSlots(unsigned int nSlot, unsigned int nPlayers)
{
	Assert( nSlot < SLOTS_LAST );
	m_nPlayerSlots[nSlot] = nPlayers;
}
unsigned int CSession::GetSessionSlots( unsigned int nSlot )
{
	Assert( nSlot < SLOTS_LAST ); 
	return m_nPlayerSlots[nSlot];
}
void CSession::SetSessionFlags( uint flags )
{
	m_nSessionFlags = flags;
}
uint CSession::GetSessionFlags()
{
	return m_nSessionFlags;
}
int CSession::GetPlayerCount()
{
	return m_nPlayerSlots[SLOTS_FILLEDPRIVATE] + m_nPlayerSlots[SLOTS_FILLEDPUBLIC];
}
void CSession::SetFlag( uint nFlag )
{
	m_nSessionFlags |= nFlag;
}
void CSession::SetIsHost( bool bHost )
{
	m_bIsHost = bHost;
}
void CSession::SetIsSystemLink( bool bSystemLink )
{
	m_bIsSystemLink = bSystemLink;
}
void CSession::SetOwnerId( uint id )
{
	m_nOwnerId = id;
}
bool CSession::IsHost()
{
	return m_bIsHost;
}
bool CSession::IsFull()
{
	return ( GetSessionSlots( SLOTS_TOTALPRIVATE ) == GetSessionSlots( SLOTS_FILLEDPRIVATE ) &&
		GetSessionSlots( SLOTS_TOTALPUBLIC ) == GetSessionSlots( SLOTS_FILLEDPUBLIC ) );
}
bool CSession::IsArbitrated()
{
	return m_bIsArbitrated;
}
bool CSession::IsSystemLink()
{
	return m_bIsSystemLink;
}
void CSession::SetParent( CMatchmaking *pParent )
{
	m_pParent = pParent;
}
double CSession::GetTime()
{
	return Plat_FloatTime();
}