///////////////////////////////////////////////////////////////////////////////
/// @file deConnection.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 "deConnection.hpp"
#include "deNetMsg.hpp"
#include "deNetUser.hpp"
#include "deConnectionBin.hpp"

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

// static declaration
//--------------------------------------------------------
DWORD			deConnection::m_MaxPacketSize = 1 << 12;
extern BYTE *	gSendBuffer;
extern DWORD	gSendBufferSize;

// used for qsort
_inline int deConnection::CompareConnections( const void *arg1, const void *arg2 )
{
	const deConnection * l = reinterpret_cast<const deConnection*>( arg1 );
	const deConnection * r = reinterpret_cast<const deConnection*>( arg2 );

	if( l )
	{
		if( r )
		{
			if( l->m_ConnectionDesc.m_hRemoteUser < r->m_ConnectionDesc.m_hRemoteUser )
				return -1;
			else
				return 1;
		}
		else
			return -1;

	}
	else
	{
		if( r )
			return 1;
		else
			return 0;
	}
}

deConnection::deConnection(void)
{
	// reset appname, sessionname
	memcpy( m_ConnectionDesc.m_SessionName, deNet_GetAppName(), 32 );
	memset( m_ConnectionDesc.m_AppName, 0, 32 );

	m_ParentConnectionBin	= NULL;
	m_Owner					= NULL;
	m_bIsReady				= false;
	m_dwDesc				= NULL;
	m_Type					= TYPE_UNKNOWN;
	m_AvgLag				= 0;
	m_LagHistory[0]			= 0;
	m_LagHistory[1]			= 0;
	m_LagHistory[2]			= 0;
	m_LagHistory[3]			= 0;
}

deConnection::~deConnection(void)
{
	if( m_bIsReady )
		this->Disconnect( deConnection::FLAG_FORCE_DISCONNECT );
}

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

	m_Owner = pOwner;
	m_ConnectionDesc.m_hLocalUser		= pOwner->GetNetHandle();
	m_ConnectionDesc.m_LocalAddress		= *pOwner->GetAddress();
	m_ConnectionDesc.m_iLocalUserType	= pOwner->GetNetType();
}

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

	return m_bIsReady;
}

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

	return m_Owner;
}

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

	return m_dwDesc;
}

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

	return m_Type;
}

_inline deConnectionBin * deConnection::GetConnectionBin( void )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	// return bin
	return m_ParentConnectionBin;
}

_inline void deConnection::SetConnectionBin( deConnectionBin * pBin )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	// set bin
	m_ParentConnectionBin = pBin;
}

#ifdef DN_SESSION_TIME_INT64
_inline s64 deConnection::GetAvgLag( void )
#else
_inline float deConnection::GetAvgLag( void )
#endif
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	return m_AvgLag;
}

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

	// attempt to reset the connections
	return this->OnAttemptReset( dwFlags );
}

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

	eNetError r = this->OnBindAsServer( dwFlags );

	if( r == CODE_OK )
		m_bIsReady = true;
	else
		m_bIsReady = false;

	return r;
}

eNetError deConnection::ConnectTo( deNetAddress * pAddress )
{
	if( !pAddress )
		return CODE_INVALID_PARAM;
	else
	{
		// ensure this is thread-safe and locked
		ENSURE_LOCK( this, this->GetSynchObject() );

		eNetError r = this->OnConnectTo( pAddress );

		if( r == CODE_OK )
		{
			// ok, connection is ready
			m_bIsReady	= true;
		}

		return r;
	}
}

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

	eNetError r = this->OnDisconnect( dwFlags );

	if( r == CODE_OK )
	{
		// remove from bin
		if( m_ParentConnectionBin )
			m_ParentConnectionBin->RemoveConnection( this );

		m_ParentConnectionBin = NULL;

		// cleanup		
		m_aPacketLists.clear();
		m_bIsReady		= false;
		m_dwDesc		= 0;
	}

	return r;
}

eNetError deConnection::RecievePacket( dePacket * pPacket, DWORD dwFlags )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	eNetError r = CODE_OK;

	char d[255];
	memset( d, 0, 255 );
	std::string strMsg = "-- Recieved Packet. Size [";
	strMsg += itoa( pPacket->GetHeader().Size, d, 10 );
	strMsg += "] ID [";
	strMsg += itoa( pPacket->GetHeader().ID, d, 10 );
	strMsg += "] SubID [";
	strMsg += itoa( pPacket->GetHeader().SubID, d, 10 );
	strMsg += "] --\n";
	NET_LOG( strMsg.c_str() );


	// if the packet is fragmented, put it in a list
	if( pPacket->IsFragmented() )
	{
		// create a dummy packet to search with
		dePacketList tmpList;
		tmpList.AddPacket( pPacket );

		// is the list already there?
		aPacketLists::iterator itr = m_aPacketLists.find( tmpList );

		if( itr != NULL && itr != m_aPacketLists.end() )
		{
			// it is already there, insert the new packet
			itr->AddPacket( pPacket );

			// is it complete?
			if( itr->IsCompleteList() )
			{
				// send it on its way
				r = this->RecievePacket( itr->ConsolidatePackets(), NULL );
			}
		}
		else
		{
			// we have to add a new packet list
			dePacketList list;
			list.AddPacket( pPacket );

			// is it complete? (shouldn't be, but check anyway)
			if( list.IsCompleteList() )
				r = this->RecievePacket( list.ConsolidatePackets(), NULL );
			else
				// insert he list
				m_aPacketLists.insert( list );
		}
	}
	else
	{
		deASSERT( m_Owner );

		// the packet isn't fragmented, send it on its way
		deNetMsg msg = deNetMsg( pPacket );
		
		if( msg.GetMsgID() > deNetMsg::MSG_HIGH_LEVEL )
		{
			// high level msg, send to deNetUser
			r = m_Owner->HandleNetMsg( &msg );
		}
		else
		{
			dePacket packet;
			deNetMsg newMsg;

			deConnectionDisconnect *	disconnect	= NULL;
			deConnectionPing *			ping		= NULL;
			deConnectionHandshake *		hand		= NULL;
			
			char d[255];
			WORD msgID = msg.GetMsgID();

			switch( msgID )
			{
			case deNetMsg::MSG_DISCONNECT:

				NET_LOG( "-- Recieved Disconnect Message --\n" );

				disconnect = reinterpret_cast<deConnectionDisconnect*>( msg.GetData() );

				// build deConnectionLost msg
				deConnectionLost lost;
				lost.Reason = deConnectionLost::REASON_CONN_CLOSED;
				lost.pConn	= this;

				// build packet
				dePacket::PacketHeader header;
				header.Flags		= 0;
				header.ID			= 0;
				header.NetMsgID		= deNetMsg::MSG_CONNECTION_LOST;
				header.Recipient	= msg.GetRecip();
				header.Sender		= msg.GetSender();
				header.Size			= sizeof(deConnectionLost);
				header.SubID		= 0;
				header.TotalSize	= sizeof(deConnectionLost);
				header.Type			= 0;

				packet = dePacket( header, &lost, NULL );

				// build msg
				newMsg = deNetMsg( &packet );

				// set lost msg
				r = m_Owner->HandleNetMsg( &newMsg );
				break;

			case deNetMsg::MSG_PING:

				ping = reinterpret_cast<deConnectionPing*>( msg.GetData() );

				if( ping-> dwFlags & deConnectionPing::FLAG_RETURN_PING )
				{
					if( ping->dwFlags & deConnectionPing::FLAG_CONFIRM_NET_ID )
					{
						// set net id
						ping->hNetID = this->GetConnDesc()->m_hLocalUser;
					}

					// clear flags
					ping->dwFlags = NULL;

					// send back ping
					r = this->Send( deNetMsg::MSG_PING, ping, sizeof(deConnectionPing), NULL );

					// we recieved a ping
					NET_LOG( "-- Ping! --\n" );
				}
				else
				{
					// a ping was returned
					NET_LOG( "-- Ping Returned <<\n" );

					// send back ping
					ping->fTimeRecieved	= GetOwner()->GetApproxSessionTime();

					// update lag
					m_LagHistory[4]	= m_LagHistory[3];
					m_LagHistory[3]	= m_LagHistory[2];
					m_LagHistory[1]	= m_LagHistory[0];

					#ifdef DN_SESSION_TIME_INT64
					 s64 avg	= 0;
					 s64 iNum = 0;
					 m_LagHistory[0] = (ping->fTimeRecieved - ping->fTimeSent)/((s64)2);
					#else
					 float avg	= 0;
					 float iNum	= 0;
					 m_LagHistory[0] = (ping->fTimeRecieved - ping->fTimeSent)/((float)2);
					#endif

					// get avg lag
					if( m_LagHistory[3] > 0 )
					{
						avg	+= m_LagHistory[3];
						iNum++;
					}

					if( m_LagHistory[2] > 0 )
					{
						avg	+= m_LagHistory[2];
						iNum++;
					}

					if( m_LagHistory[1] > 0 )
					{
						avg	+= m_LagHistory[1];
						iNum++;
					}

					if( m_LagHistory[0] > 0 )
					{
						avg	+= m_LagHistory[0];
						iNum++;
					}

					if( iNum > 0 )
					{
						avg /= iNum;
						m_AvgLag = avg;
					}

					r = CODE_OK;
				}
				break;

			case deNetMsg::MSG_HANDSHAKE:

				r = CODE_NOT_SUPPORTED_BY_OBJECT;
				break;

			default:

				std::string strMsg = "";
				memset( d, 0, 255 );
				strMsg += "** Invalid Low-Level Message ID [";
				strMsg += itoa( msgID, d, 10 );
				strMsg += "] **\n";
				NET_LOG( strMsg.c_str() );
				r = CODE_INVALID_PARAM;
				break;
			};
		}
	}

	return r;
}

eNetError deConnection::Send( WORD pNetMsgID, const void * pBuff, DWORD dwSize, DWORD  dwFlags )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	// static id counter
	static unsigned int iID = 0;
	
	unsigned int	iSize  = 0;
	unsigned int	iTotalSent = 0;
	unsigned int	iSubID = 0;
	BYTE *			ptr = NULL;
	eNetError		r = CODE_OK;
	bool			bStop = false;
	
	char d[255];

	// ensure buffer size is large enough
	if( gSendBufferSize < deConnection::GetMaxPacketSize() )
	{
		SAFE_DELETE_ARRAY( gSendBuffer );

		gSendBufferSize	= deConnection::GetMaxPacketSize();
		deASSERT( gSendBufferSize > 0 );
		gSendBuffer = new BYTE[ gSendBufferSize ];
		deASSERT( gSendBuffer );		
	}

	// set initial size
	if( dwSize > deConnection::GetMaxPacketSize() )
		iSize = deConnection::GetMaxPacketSize();
	else
		iSize = dwSize;

	// set initial ptr
	ptr = reinterpret_cast<BYTE*>( const_cast<void*>(pBuff) );

	while( iSize > 0 && r == CODE_OK )
	{
		// icrement packet counter
		iID++;
		if( iID == 0 ) iID = 1;

		// create header
		dePacket::PacketHeader header;
		header.Flags		= dwFlags;
		header.LocalID		= iID;
		header.NetMsgID		= pNetMsgID;
		header.Recipient	= this->GetConnDesc()->m_hRemoteUser;
		header.Sender		= this->GetConnDesc()->m_hLocalUser;
		header.Size			= iSize;
		header.SubID		= iSubID;
		header.TotalSize	= dwSize;
		header.Type			= 0;

		// copy mem
		memcpy( gSendBuffer, &header, sizeof( dePacket::PacketHeader ) );
		memcpy( gSendBuffer + sizeof( dePacket::PacketHeader ), ptr, iSize );
		
		std::string strMsg = "";
		memset( d, 0, 255 );
		strMsg += "-- Sending packet ID [";
		strMsg += itoa( header.ID, d, 10 );
		strMsg += "] SubID [";
		strMsg += itoa( header.SubID, d, 10 );
		strMsg += "] --\n";
		NET_LOG( strMsg.c_str() );

		// send data
		r = this->OnSend( pNetMsgID, gSendBuffer, iSize + sizeof( dePacket::PacketHeader ), dwFlags );

		// increment sub id
		iSubID++;

		// get new size
		iTotalSent += iSize;
		iSize		= dwSize - iSize;
		
		// increment ptr
		ptr += iSize;
	}

	if( r == CODE_OK )
	{
		std::string strMsg = "";
		memset( d, 0, 255 );
		strMsg += "-- Sent Message [";
		strMsg += itoa( iTotalSent, d, 10 );
		strMsg += "] Bytes --\n";
		NET_LOG( strMsg.c_str() );
	}
	else if( r == CODE_INVALID_CONNECTION )
	{
		if( this->IsReady() )
		{
			// try to reset the connection
			if( this->AttemptReset( NULL ) != CODE_OK )
			{
				// we lost the connection, or never had it
				this->Disconnect( NULL );

				// setup lost structure
				deConnectionLost lost;
				lost.pConn		= this;
				lost.Reason		= deConnectionLost::REASON_CONN_LOCAL_ERROR;

				// create packet
				dePacket::PacketHeader header;
				header.Flags		= 0;
				header.ID			= 0;
				header.LocalID		= 0;
				header.NetMsgID		= deNetMsg::MSG_CONNECTION_LOST;
				header.Recipient	= GetOwner()->GetNetHandle();
				header.Sender		= GetOwner()->GetNetHandle();
				header.Size			= sizeof( deConnectionLost );
				header.SubID		= 0;
				header.TotalSize	= sizeof( deConnectionLost );
				header.Type			= 0;
				dePacket packet = dePacket( header, &lost, NULL );

				// setup net msg
				deNetMsg msg = deNetMsg( &packet );

				// send msg
				GetOwner()->HandleNetMsg( &msg );					

				r = CODE_INVALID_CONNECTION;
			}
			else
			{
				r = CODE_OK;
			}
		}
	}

	return r;
}