///////////////////////////////////////////////////////////////////////////////
/// @file deMasterServer.cpp
///
/// @brief 
///
/// @author trajar
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Jun 2002
/// @author trajar
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

#include "deMasterServer.hpp"
#include "deNetMsg.hpp"
#include <SEARCH.H>

#ifdef	_DEBUG
 #include	<crtdbg.h> 
 #define new new(_NORMAL_BLOCK,__FILE__, __LINE__)
#endif

_inline deMasterServer::deMasterServer(void)
{
	m_Type			= TYPE_MASTER_SERVER;
	m_iNumSlaves	= 0;

	if( deNet_GetMaxConnections() <= 0 )
	{
		m_aSlaves = NULL;
	}
	else
	{
		m_aSlaves		= new pdeConnection[ deNet_GetMaxConnections() ];
		deASSERT( m_aSlaves );
		memset( m_aSlaves, NULL, sizeof(deConnection*) * deNet_GetMaxConnections() );
	}
}

_inline deMasterServer::~deMasterServer(void)
{
	if( m_Status < STATUS_DESTROYED )
	{
		this->Destroy( NULL );
	}
	
	SAFE_DELETE_ARRAY( m_aSlaves );	
}

eNetError deMasterServer::OnTick( void )
{
	eNetError r = CODE_OK;

	// lock this object
	this->GetSynchObject()->Lock( this );

	static WORD	iTimeID		= 0;
	WORD		i			= 0;
	WORD		j			= 0;
	DWORD		iNum		= m_iNumSlaves + m_iNumClients;
	hNetUser *	users		= NULL;

	if( iNum > 0 )
	{
		users = new hNetUser[ iNum ];

		// set up slave server(s)
		for( i=0; i<m_iNumSlaves; i++ )
		{
			if( m_aSlaves[i] )
				users[i] = m_aSlaves[i]->GetConnDesc()->m_hRemoteUser;
			else
				users[i] = DN_INVALID_NETID;
		}

		// get correct index
		if( m_iNumSlaves > 0 )
			i = m_iNumSlaves - 1;
		else
			i = 0;

		// set up client(s)
		for( j=0; j<m_iNumClients; j++ )
		{
			if( m_aClients[j] )
				users[ j + i ] = m_aClients[j]->GetConnDesc()->m_hRemoteUser;
			else
				users[ j + i ] = DN_INVALID_NETID;
		}

		m_SessionTime		+= deNet_GetTime();
		m_SessionTimeDelta	= 0;

		// set up ping deNetMsg
		deConnectionPing ping;
		ping.dwFlags		= deConnectionPing::FLAG_KEEPALIVE | deConnectionPing::FLAG_RETURN_PING;
		ping.fTimeRecieved	= 0.0f;
		ping.fTimeSent		= GetSessionTime();
		ping.hNetID			= this->GetNetHandle();

		// unclock this object
		this->GetSynchObject()->Unlock();

		// send to all attached users
		r = this->SendToUsers(	deNetMsg::MSG_PING, users, iNum,
								&ping, sizeof(deConnectionPing), NULL );

		// lock it
		this->GetSynchObject()->Lock( this );

		// update server time
		m_SessionTime		+= deNet_GetTime();
		m_SessionTimeDelta	= 0;

		// unlock
		this->GetSynchObject()->Unlock();

		// sleep (1 sec)
		SleepEx( 1000, FALSE );

		// lock
		this->GetSynchObject()->Lock( this );

		// update server time
		m_SessionTime		+= deNet_GetTime();
		m_SessionTimeDelta	= 0;

		// set up server time update
		deConnectionServerTime time;
		
		// incremet time
		iTimeID++;
		time.dwFlags		= NULL;
		time.Time			= m_SessionTime;
		time.iUpdateID		= iTimeID;

		// unlock
		this->GetSynchObject()->Unlock();

		// send to all attached users
		r = this->SendToUsers(	deNetMsg::MSG_SERVER_TIME, users, iNum,
								&time, sizeof(deConnectionServerTime), NULL );

		SAFE_DELETE_ARRAY( users );
	}

	if( !this->GetSynchObject()->IsLocked() )
	{
		// lock
		this->GetSynchObject()->Lock( this );
	}

	// update server time
	m_SessionTime		+= deNet_GetTime();
	m_SessionTimeDelta	= 0;

	// unlock
	this->GetSynchObject()->Unlock();

	// sleep (1 sec)
	SleepEx( 1000, FALSE );

	// exit
	return r;
}

_inline deMasterServer::GetNumSlaves(void)
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	return m_iNumSlaves;
}

eNetError deMasterServer::OnInit( int pType, DWORD pFlags )
{
	// ensure we have our array
	if( m_aSlaves == NULL )
	{
		deASSERT( deNet_GetMaxConnections() > 0 );
		m_aSlaves		= new pdeConnection[ deNet_GetMaxConnections() ];
		deASSERT( m_aSlaves );
		memset( m_aSlaves, NULL, sizeof(deConnection*) * deNet_GetMaxConnections() );
	}

	return deServer::OnInit( pType, pFlags );
}

deConnection * deMasterServer::GetConnectionByUser( hNetUser pUser )
{
	deConnection * conn = deServer::GetConnectionByUser( pUser );

	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	if( !conn )
	{
		deConnection ** connections = m_aSlaves;
		for( int i=0; i<m_iNumSlaves; i++ )
		{
			if( (*connections) && (*connections)->GetConnDesc()->m_hRemoteUser == pUser )
			{
				conn = *connections;
				break;
			}
			
			connections++;
		}
	}

	return conn;
}

eNetError deMasterServer::OnDestroy( DWORD pFlags )
{
	deConnection ** connections = m_aSlaves;
	for( int i=0; i<m_iNumSlaves; i++ )
	{
		if( (*connections) && (*connections)->IsReady() )
			(*connections)->Disconnect( NULL );

		(*connections)	= NULL;		
		connections++;
	}

	m_iNumSlaves = 0;

	return deServer::OnDestroy( pFlags );
}

eNetError deMasterServer::AttachSlave( deConnection * pConn )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	if( m_iNumSlaves >= deNet_GetMaxConnections() )
		return CODE_TOO_MANY_CONNECTIONS;

	deConnection ** conns = m_aSlaves;
	for( int i=0; i<m_iNumSlaves; i++ )
	{
		if( (*conns) == NULL || !(*conns)->IsReady() )
		{
			*conns = pConn;
			m_iNumSlaves++;

			// sort array - desc so nulls are at the end
			if( m_iNumSlaves >= deNet_GetMaxConnections() )
				qsort( m_aSlaves, m_iNumSlaves, sizeof(deConnection*), deConnection::CompareConnections );
			else
				qsort( m_aSlaves, m_iNumSlaves + 1, sizeof(deConnection*), deConnection::CompareConnections );

			return CODE_OK;
		}		
		conns++;
	}

	// did we add it?
	if( (*conns) != pConn )
	{
		m_aSlaves[ m_iNumSlaves ] = pConn;
		m_iNumSlaves++;

		return CODE_OK;
	}

	return CODE_INVALID_PARAM;
}

eNetError deMasterServer::CreateServer( void )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	eNetError	r = CODE_OK;
	
	this->AssignNetHandle( this->GenerateNetID() );

	// since this is bound to itself, remote=local
	deConnectionDesc * desc = const_cast<deConnectionDesc*>( m_Connection->GetConnDesc() );
	deASSERT( desc );

	desc->m_hRemoteUser			= desc->m_hLocalUser;
	desc->m_iRemoteUserType		= desc->m_iLocalUserType;
	desc->m_RemoteAddress		= desc->m_LocalAddress;

	m_Status = STATUS_CONNECTED;

	return r;
}

eNetError deMasterServer::OnConnectionLost( deConnectionLost * pLost )
{
	deASSERT( pLost );

	eNetError r = this->OnRemoveConnnection( pLost->pConn );

	if( r != CODE_OK )
		return r;

	// is this the main connection?
	if( pLost->pConn == m_Connection )
	{
		// shut everything down
		return Destroy( NULL );
	}
	else
	{
		// is the connection a slave?
		if( pLost->pConn->GetConnDesc()->m_iRemoteUserType == TYPE_SLAVE_SERVER )
		{
			// we lost a slave server, find it and remove it
			deConnection ** conns = m_aSlaves;
			for( int i=0; i<m_iNumSlaves; i++ )
			{
				if( (*conns) == pLost->pConn )
				{
					if( pLost->pConn->IsReady() )
						pLost->pConn->Disconnect( NULL );

					(*conns) = NULL;
					m_iNumSlaves--;

					// sort array - desc so nulls are at the end
					if( m_iNumSlaves >= deNet_GetMaxConnections() )
						qsort( m_aSlaves, m_iNumSlaves, sizeof(deConnection*), deConnection::CompareConnections );
					else
						qsort( m_aSlaves, m_iNumSlaves + 1, sizeof(deConnection*), deConnection::CompareConnections );

					return CODE_OK;
				}		
				conns++;
			}

			return CODE_INVALID_PARAM;
		}
		else
		{
			// remove the stale/invalid connection
			return this->RemoveClient( pLost->pConn );
		}
	}
}

eNetError deMasterServer::SendToAll( WORD pMsgID, void * pData, DWORD pDataSize, DWORD dwFlags )
{
	eNetError r = deServer::SendToAll( pMsgID, pData, pDataSize, dwFlags );

	// send to slaves
	if( r == CODE_OK )
	{
		deConnection ** connections = m_aSlaves;
		for( int i=0; i<m_iNumSlaves; i++ )
		{
			if( *connections )
				r = this->SendTo( pMsgID, (*connections)->GetConnDesc()->m_hRemoteUser, pData, pDataSize, dwFlags );

			if( r != CODE_OK )
				return r;

			connections++;
		}
	}

	return CODE_OK;
}