///////////////////////////////////////////////////////////////////////////////
/// @file deNetUser.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 "deNetUser.hpp"
#include "dePacket.hpp"
#include "deNetMsg.hpp"
#include "deConnection.hpp"
#include "deThreadPool.hpp"
#include <TIME.H>

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

DWORD	HandleNetUser( void * pData )
{
	// get user from pointer
	deNetUser * user = *reinterpret_cast<deNetUser**>( pData );
	deASSERT( user );

	// local variables
	eNetError	r			= CODE_OK;

	deNetUser::eNetStatus	status = user->GetNetStatus();

	while(  (	
				status == deNetUser::STATUS_CREATED || 
				status == deNetUser::STATUS_CONNECTED
			)	/* && r == CODE_OK */ )
	{
		// handle this user
		r = user->OnTick();

		// get status
		status = user->GetNetStatus();
	}

	NET_LOG( ">> deNetUser Service Thread Done <<\n" );

	return 0;
}

_inline deNetUser::deNetUser(void)
{
	m_SessionTime		= 0;
	m_SessionTimeDelta	= 0;
	m_NetHandle			= DN_INVALID_NETID;
	m_Connection		= NULL;
	m_Status			= STATUS_UNKNOWN;
	m_Type				= TYPE_UNKNOWN;
};

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

	if( m_Status < STATUS_DESTROYED )
	{
		this->Destroy( NULL );
	}
}

eNetError deNetUser::OnTick( void )
{
	// sleep
	SleepEx( 5000, FALSE );

	// exit
	return CODE_OK;
}

_inline eNetError deNetUser::HandleNetMsg( deNetMsg * pMsg )
{
	if( pMsg )
	{	
		// ensure this is thread-safe and locked
		ENSURE_LOCK( this, this->GetSynchObject() );

		eNetError r = CODE_OK;

		deConnectionServerTime *	time	= NULL;
		deConnectionLost  *			lost	= NULL;
		deConnectionReply *			reply	= NULL;
		deConnectionRequest *		request	= NULL;

		// handle the msg
		switch( pMsg->GetMsgID() )
		{
		case deNetMsg::MSG_CONNECTION_LOST:
			NET_LOG( ">> Connection was lost/reset <<\n" );
			
			// we lost a connection
			lost = reinterpret_cast<deConnectionLost*>( pMsg->GetData() );

			// call virtual
			r = this->OnConnectionLost( lost );
			break;

		case deNetMsg::MSG_CONNECTION_REQUEST:
			NET_LOG( ">> Connection Request Recieved <<\n" );

			// we recieved an authorization request
			request = reinterpret_cast<deConnectionRequest*>( pMsg->GetData() );
			
			// call virtual
			r = this->OnConnectionRequest( request );
			break;

		case deNetMsg::MSG_CONNECTION_REPLY:

			NET_LOG( ">> Connection Reply Recieved <<\n" );

			// we got back an authorization reply
			reply = reinterpret_cast<deConnectionReply*>( pMsg->GetData() );
			
			// call virtual
			r = this->OnConnectionReply( reply );
			break;

		case deNetMsg::MSG_SERVER_TIME:

			NET_LOG( "-- Server Time Update Recieved --\n" );

			// we recieved a server time update
			time = reinterpret_cast<deConnectionServerTime*>( pMsg->GetData() );

			this->UpdateSessionTime( time->Time );
			r = CODE_OK;
			break;
			
		default:
			r = CODE_INVALID_PARAM;
			break;
		}		
		
		if( r != CODE_OK )
		{
			NET_LOG_ERROR( r );
		}

		return r;
	}
	else
	{
		return CODE_INVALID_PARAM;
	}
}

eNetError deNetUser::Destroy( DWORD pFlags )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	eNetError r = CODE_OK;

	if( this->GetNetStatus() == STATUS_CONNECTED )
	{
		r = this->CloseConnection( pFlags );
	}

	if( r != CODE_OK )
		return r;
	else
		r = this->OnDestroy( pFlags );

	if( r == CODE_OK )
	{
		// shutdown service thread
		if( m_ServiceThread )
		{
			m_ServiceThread->Sleep();
			m_ServiceThread = NULL;
		}

		m_Status	= STATUS_DESTROYED;
		m_NetHandle	= DN_INVALID_NETID;
	}

	return r;
}

eNetError deNetUser::Init( int pType, DWORD pFlags )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	m_Status = STATUS_UNKNOWN;

	eNetError r = deNet_CreateConnection( &m_Connection, pType, pFlags );

	if( r == CODE_OK )
	{
		m_Connection->GetSynchObject()->Lock( this );

		// set the address to the connection's local
		m_Address = m_Connection->GetConnDesc()->m_LocalAddress;

		m_Connection->SetOwner( this );

		m_Connection->GetSynchObject()->Unlock();		
	}
	else
	{
		m_Connection	= NULL;
		m_Status		= STATUS_UNKNOWN;
		return r;
	}

	r = this->OnInit( pFlags );

	if( r == CODE_OK )
	{
		m_Status = STATUS_CREATED;

		// start deNetUser thread
		deNetUser * user = this;
		m_ServiceThread = deThreadPool::GetMgr()->ActivateIdleThread(	HandleNetUser, 
																		&user, sizeof(deNetUser*),
																		NULL, true );

		if( m_ServiceThread == NULL )
		{
			NET_LOG( "** Unable to create deNetUser service thread **\n" );
			return CODE_RESOURCE_ERROR;
		}
	}

	return r;
}

eNetError deNetUser::CloseConnection( DWORD dwFlags )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	eNetError r = CODE_OK;

	if( m_Connection->IsReady() )
	{
		// setup disconnect msg
		deConnectionDisconnect disconnect;
		disconnect.Reason = deConnectionDisconnect::REASON_CONNECT_CLOSED_BY_NETUSER;

		// tell everyone we are closing connection(s)
		r = this->SendToAll(	deNetMsg::MSG_DISCONNECT, 
								&disconnect, sizeof(deConnectionDisconnect), NULL );

		r = this->OnCloseConnection( dwFlags );

		if( r == CODE_OK )
		{
			if( m_Connection &&  m_Connection->IsReady() )
				r = m_Connection->Disconnect( dwFlags );
			else 
				r = CODE_OK;		
		}
	}

	m_Status = STATUS_CLOSED;

	return r;  
}

deConnection * deNetUser::GetConnectionByUser( hNetUser pUser )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	deConnection * conn = NULL;

	if( m_Connection->GetConnDesc()->m_hRemoteUser == pUser )
		conn = m_Connection;

	return conn;
}

_inline bool deNetUser::IsNetIDUnique( hNetUser pUser )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	return pUser != NULL && this->GetConnectionByUser( pUser ) == NULL;
}

hNetUser deNetUser::GenerateNetID(void)
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	struct randDWORD
	{
		union
		{
			struct
			{
				WORD	iUpper;
				WORD	iLower;
			};

			hNetUser	hUser;
		};
	};

	randDWORD myDWORD;
	myDWORD.hUser = DN_INVALID_NETID;

	// seed it
	srand( time(NULL) );

	while( this->GetConnectionByUser( myDWORD.hUser ) || myDWORD.hUser == DN_INVALID_NETID )
	{
		myDWORD.iUpper = rand();
		myDWORD.iLower = rand();
	}

	return myDWORD.hUser;
}

_inline eNetError deNetUser::ReleaseNetID( hNetUser pUser )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	return CODE_OK;
}

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

	return m_NetHandle;
}

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

	return m_Status;
}

_inline deNetAddress * deNetUser::GetAddress( void )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	return &m_Address;
}

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

	return m_Type;
}

_inline deSynchObject * deNetUser::GetSynchObject( void )
{
	return &m_SynchObject;
}

#ifdef DN_SESSION_TIME_INT64
	_inline s64 deNetUser::GetSessionTime( void )
	{
		// ensure this is thread-safe and locked
		ENSURE_LOCK( this, this->GetSynchObject() );

		return m_SessionTime;
	}
	_inline s64 deNetUser::GetApproxSessionTime( void )
	{
		// ensure this is thread-safe and locked
		ENSURE_LOCK( this, this->GetSynchObject() );

		return m_SessionTime + m_SessionTimeDelta;
	}
	_inline s64 deNetUser::GetSessionTimeDelta( void )
	{
		// ensure this is thread-safe and locked
		ENSURE_LOCK( this, this->GetSynchObject() );

		return m_SessionTimeDelta;
	}
	_inline void deNetUser::UpdateSessionTime( s64 pTime )
	{
		// ensure this is thread-safe and locked
		ENSURE_LOCK( this, this->GetSynchObject() );

		m_SessionTime		= pTime;
		m_SessionTimeDelta	= 0;
	}
#else
	_inline float deNetUser::GetSessionTime( void )
	{
		// ensure this is thread-safe and locked
		ENSURE_LOCK( this, this->GetSynchObject() );

		return m_SessionTime;
	}
	_inline float deNetUser::GetApproxSessionTime( void )
	{
		// ensure this is thread-safe and locked
		ENSURE_LOCK( this, this->GetSynchObject() );

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

		return m_SessionTime +  m_SessionTimeDelta;
	}
	_inline float deNetUser::GetSessionTimeDelta( void )
	{
		// ensure this is thread-safe and locked
		ENSURE_LOCK( this, this->GetSynchObject() );

		return m_SessionTimeDelta;
	}
	_inline void deNetUser::UpdateSessionTime( float pTime )
	{
		// ensure this is thread-safe and locked
		ENSURE_LOCK( this, this->GetSynchObject() );

		m_SessionTime		= pTime;
		m_SessionTimeDelta	= 0;
	}
#endif


_inline void deNetUser::AssignNetHandle( hNetUser pUser )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	this->m_NetHandle = pUser;
	deConnectionDesc * desc = const_cast<deConnectionDesc*>( m_Connection->GetConnDesc() );
	deASSERT( desc );
	desc->m_hLocalUser = pUser;
}

eNetError deNetUser::SendTo( WORD pNetMsgID, hNetUser hUser, void * pData, DWORD pDataSize, DWORD dwFlags )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	if( hUser == DN_INVALID_NETID )
		return CODE_INVALID_NET_ID;

	eNetError r = CODE_OK;

	// get connection
	deConnection * conn = this->GetConnectionByUser( hUser );

	if( conn )
	{
		r = conn->Send( pNetMsgID, pData, pDataSize, dwFlags );
	}
	else
		r = CODE_INVALID_PARAM;

	return r;
}

eNetError deNetUser::SendToUsers( WORD pMsgID, hNetUser * pUsers, DWORD pNumUsers, void * pData, DWORD pDataSize, DWORD dwFlags )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	eNetError r = CODE_OK;

	for( DWORD i = 0; i<pNumUsers; i++ )
	{
		if( (*pUsers) != NULL )
		{
			r = this->SendTo( pMsgID, *pUsers, pData, pDataSize, dwFlags );
		}

		if( r == CODE_OK )
		{
			pUsers++;
		}
		else
		{
			break;
		}
	}

	return r;
}

eNetError deNetUser::SendToAll( WORD pMsgID, void * pData, DWORD pDataSize, DWORD dwFlags )
{
	if( m_Connection )
		return this->SendTo( pMsgID, m_Connection->GetConnDesc()->m_hRemoteUser, pData, pDataSize, dwFlags );
	else
		return CODE_INVALID_CONNECTION;
}