///////////////////////////////////////////////////////////////////////////////
/// @file deClient.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 "deClient.hpp"
#include "deConnection.hpp"
#include "deNetMsg.hpp"
#include <SEARCH.H>

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

deClient::deClient(void)
{
	m_Type			= TYPE_CLIENT;
	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() );
	}
};

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

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

	if( m_Connection->IsReady() && this->GetNetStatus() == STATUS_CONNECTED )
	{
		// lock this object
		this->GetSynchObject()->Lock( this );

		// update server time delta
		m_SessionTimeDelta += deNet_GetTime();

		hNetUser *	servers = new hNetUser[ m_iNumSlaves + 1 ];

		// set master server
		servers[0]	= m_Connection->GetConnDesc()->m_hRemoteUser;

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

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

		// send to all servers
		r = this->SendToUsers(	deNetMsg::MSG_PING, servers, m_iNumSlaves+1,
								&ping, sizeof(deConnectionPing), NULL );

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

		SAFE_DELETE_ARRAY( servers );
	}

	// sleep
	SleepEx( 2000, FALSE );
	
	// exit
	return r;
}

eNetError deClient::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 CODE_OK;
}

deConnection * deClient::GetConnectionByUser( hNetUser pUser )
{
	deConnection * conn = deNetUser::GetConnectionByUser( pUser );

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

		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 deClient::OnDestroy( DWORD pFlags )
{
	deConnection ** conns = m_aSlaves;
	for( int i=0; i<m_iNumSlaves; i++ )
	{
		if( (*conns) && (*conns)->IsReady() )
			(*conns)->Disconnect( pFlags );

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

	m_iNumSlaves = 0;

	return CODE_OK;
}

eNetError deClient::ConnectTo( deNetAddress * pAdd, void * pData, DWORD iSize )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );
	
	deASSERT( m_Connection );

	eNetError r = m_Connection->ConnectTo( pAdd );

	if( r == CODE_OK )
	{
		// set up unique key for server
		deConnectionDesc * desc = const_cast<deConnectionDesc*>( m_Connection->GetConnDesc() );
		
		// start handshake with server
		deConnectionRequest	request;

		// set up request
		request.Address			= *GetAddress();
		request.iUserType		= GetNetType();
		request.iProviderType	= m_Connection->GetType();
		request.hUserID			= m_Connection->GetOwner()->GetNetHandle();
		
		r = m_Connection->Send(	deNetMsg::MSG_CONNECTION_REQUEST,
								&request, sizeof( deConnectionRequest ),
								deConnection::FLAG_CONFIRM_DELIVERY );

		if( r == CODE_OK )
		{
			m_Status = STATUS_CONNECTED;
		}
	}

	return r;
}

eNetError deClient::OnCloseConnection( DWORD dwFlags )
{
	return CODE_OK;
}

eNetError deClient::OnConnectionLost( deConnectionLost * pLost )
{
	eNetError r = CODE_OK;
	deASSERT( pLost );

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

	if( r != CODE_OK )
		return r;

	if( pLost->pConn == m_Connection )
	{
		if( r == CODE_OK )
		{
			// close it
			m_Connection->Disconnect( NULL );

			// destroy this user
			r = this->Destroy( NULL );
		}

		return r;
	}
	else
	{
		// 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 )
			{
				// close it
				pLost->pConn->Disconnect( NULL );

				// remove it
				(*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;
			}
		}

		// we couldn't find it
		return CODE_INVALID_PARAM;
	}
}

eNetError deClient::OnConnectionRequest( deConnectionRequest * pRequest )
{
	deASSERT( pRequest );
	return CODE_NOT_SUPPORTED_BY_OBJECT;
}

eNetError deClient::OnConnectionReply( deConnectionReply * pReply )
{
	deASSERT( pReply );

	// get the connection we are talking to
	deConnection * conn = this->GetConnectionByUser( pReply->hServerID );
	
	if( pReply->bAccepted && conn )
	{
		NET_LOG( ">> Connection Accepted By Server <<\n" );

		deConnectionDesc * desc = const_cast< deConnectionDesc* >( conn->GetConnDesc() );
		deASSERT( desc );

		m_Status				= STATUS_CONNECTED;
		desc->m_hRemoteUser		= pReply->hServerID;
		desc->m_iRemoteUserType	= pReply->iServerUserType;
		desc->m_RemoteAddress	= pReply->ServerAddress;

		return this->OnNewConnection( m_Connection );
	}
	else
	{
		if( conn )
		{
			NET_LOG( ">> Connection Refused By Server <<\n" );

			return conn->Disconnect( NULL );
		}
		else
			return CODE_INVALID_NET_ID;
	}
}

eNetError deClient::SendToAll( WORD pMsgID, void * pData, DWORD pDataSize, DWORD dwFlags )
{
	eNetError r = deNetUser::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;
}