///////////////////////////////////////////////////////////////////////////////
/// @file deNet.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 "deTCPConnectionProvider.hpp"
#include <STRING>
#include <TIME.H>
#include "deTimer.hpp"
#include "deNet.hpp"
#include "deNetUser.hpp"
#include "deThreadPool.hpp"
#include "deMasterServer.hpp"
#include "deSlaveServer.hpp"
#include "deClient.hpp"
#include "dePeer.hpp"

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

// global variables
//-----------------------------------------------
char			strTemp[256];
char			strAppName[32];
DWORD			dwPort					= DN_DEFAULT_PORT;
DWORD			dwVersion				= 0;
FILE	*		hLogFile				= NULL;
deThreadPool	threadPool;
DWORD			iMaxNumConnections		= 0;
DWORD			iMaxNumConnectionBins	= 0;
BYTE *			gSendBuffer				= NULL;
DWORD			gSendBufferSize			= 0;
deTimer			gTimer;

// process executed on logging
DWORD (* gLogProcess)( const char * )	= NULL;

//-----------------------------------------------
// deNetAdderess 
//-----------------------------------------------
_inline deNetAddress::deNetAddress(void)		
{ 
	m_dwAddress		= 0;
	m_dwPort		= 0;
	strcpy( m_strAddress, "" );
	strcpy( m_strName, "" );
}

_inline deNetAddress::deNetAddress( DWORD pIP, DWORD pPort, const char * pIPStr, const char * pName )
{
	m_dwAddress		= pIP;
	m_dwPort		= pPort;
	
	if( pIPStr )	
		strcpy( m_strAddress, pIPStr );
	else
		strcpy( m_strAddress, "" );

	if( pName )		
		strcpy( m_strName, pName );
	else
		strcpy( m_strName, "" );
}

_inline deNetAddress::~deNetAddress(void)		
{ 
}

eNetError deNet_CreateConnection( deConnection ** pOut, int pType, DWORD pFlags )
{
	deConnectionProvider *	provider	= NULL;
	eNetError				r			= CODE_INVALID_PARAM;

	// now choose the provider
	switch( pType )
	{
		case deConnection::TYPE_TCP:

			// is the provider turned on?
			if( !deTCPConnectionProvider::GetMgr() )
			{
				provider = new deTCPConnectionProvider();
				deASSERT( provider );

				provider->Init( NULL );
				deASSERT( provider->IsReady() );
			}
			else
			{
				provider = deTCPConnectionProvider::GetMgr();
			}
			break;

		case deConnection::TYPE_UDP:

			/*
			// is the provider turned on?
			if( !deTCPConnectionProvider::GetMgr() )
			{
				provider = new deTCPConnectionProvider();
				deASSERT( provider );
			}
			*/
			break;

		case deConnection::TYPE_DPLAY:

			/*
			// is the provider turned on?
			if( !deTCPConnectionProvider::GetMgr() )
			{
				provider = new deTCPConnectionProvider();
				deASSERT( provider );
			}
			*/
			break;

		case deConnection::TYPE_BSD:

			/*
			// is the provider turned on?
			if( !deTCPConnectionProvider::GetMgr() )
			{
				provider = new deTCPConnectionProvider();
				deASSERT( provider );
			}
			*/
			break;

		default:

			provider	= NULL;
			*pOut		= NULL;
			break;
	};

	if( !provider )
	{
		*pOut = NULL;
	}
	else
	{
		// create the connection		
		r = provider->CreateConnection( pOut, NULL, pFlags );
	}

	return r;
}

//-------------------------------------------------------------------------
// deNetErrorStr( eNetError, const char *, int )
//-------------------------------------------------------------------------
const char * deNetErrorStr( eNetError err, const char * pFile, int iLine )
{
	char		d[10];
	int			iRet	= 0;	
	std::string msg		= "!! ";

	switch( err )
	{
	case CODE_OK:
		msg  += "No Error.";
		break;

	case CODE_INVALID_CONNECTION:
		msg  += "Invalid Connection.";
		break;

	case CODE_INVALID_PARAM:
		msg	 += "Invalid Paramater.";
		break;

	case CODE_UNSUPPORTED_VERSION:
		msg	 += "Unsupported deNet Version.";
		break;

	case CODE_TOO_MANY_CONNECTIONS:
		msg	 += "Too Many Connections Open.";
		break;

	case CODE_CONNECTION_REFUSED:
		msg	 += "Connection Refused By Remote Server/User.";
		break;

	case CODE_UNABLE_TO_CREATE_CONNECTION:
		msg	 += "Unable to Create Connection.";
		break;

	case CODE_RESOURCE_ERROR:
		msg	 += "Resources Exhausted/Unavailable.";
		break;

	case CODE_NOT_SUPPORTED_BY_OBJECT:
		msg	 += "Method/Property Not Supported By Object.";
		break;

	case CODE_INVALID_NET_ID:
		msg	 += "Invalid Net ID.";
		break;

	case CODE_WINSOCK_ERROR:
		msg	 += "WS2 Error: ";
		iRet = WSAGetLastError();

		// get str value of number
		msg += itoa( iRet, d, 10 );
		msg += " ";
		
		if	   ( iRet == 0 )					msg +=  "No error";
		else if( iRet == WSAEINTR )				msg +=  "Interrupted system call";
		else if( iRet == WSAEBADF )				msg +=  "Bad file number";
		else if( iRet == WSAEACCES )			msg +=  "Permission denied";
		else if( iRet == WSAEFAULT )			msg +=  "Bad address";
		else if( iRet == WSAEINVAL )			msg +=  "Invalid argument";
		else if( iRet == WSAEMFILE )			msg +=  "Too many open sockets";
		else if( iRet == WSAEWOULDBLOCK )		msg +=  "Operation would block";
		else if( iRet == WSAEINPROGRESS )		msg +=  "Operation now in progress";
		else if( iRet == WSAEALREADY )			msg +=  "Operation already in progress";
		else if( iRet == WSAENOTSOCK )			msg +=  "Socket operation on non-socket";
		else if( iRet == WSAEDESTADDRREQ )		msg +=  "Destination address required";
		else if( iRet == WSAEMSGSIZE )			msg +=  "Message too long";
		else if( iRet == WSAEPROTOTYPE )		msg +=  "Protocol wrong type for socket";
		else if( iRet == WSAENOPROTOOPT )		msg +=  "Bad protocol option";
		else if( iRet == WSAEPROTONOSUPPORT )	msg +=  "Protocol not supported";
		else if( iRet == WSAESOCKTNOSUPPORT )	msg +=  "Socket type not supported";
		else if( iRet == WSAEOPNOTSUPP )		msg +=  "Operation not supported on socket";
		else if( iRet == WSAEPFNOSUPPORT )		msg +=  "Protocol family not supported";
		else if( iRet == WSAEAFNOSUPPORT )		msg +=  "Address family not supported";
		else if( iRet == WSAEADDRINUSE )		msg +=  "Address already in use";
		else if( iRet == WSAEADDRNOTAVAIL )		msg +=  "Can't assign requested address";
		else if( iRet == WSAENETDOWN )			msg +=  "Network is down";
		else if( iRet == WSAENETUNREACH )		msg +=  "Network is unreachable";
		else if( iRet == WSAENETRESET )			msg +=  "Net connection reset";
		else if( iRet == WSAECONNABORTED )		msg +=  "Software caused connection abort";
		else if( iRet == WSAECONNRESET )		msg +=  "Connection reset by peer";
		else if( iRet == WSAENOBUFS )			msg +=  "No buffer space available";
		else if( iRet == WSAEISCONN )			msg +=  "Socket is already connected";
		else if( iRet == WSAENOTCONN )			msg +=  "Socket is not connected";
		else if( iRet == WSAESHUTDOWN )			msg +=  "Can't send after socket shutdown";
		else if( iRet == WSAETOOMANYREFS )		msg +=  "Too many references, can't splice";
		else if( iRet == WSAETIMEDOUT )			msg +=  "Connection timed out";
		else if( iRet == WSAECONNREFUSED )		msg +=  "Connection refused";
		else if( iRet == WSAELOOP )				msg +=  "Too many levels of symbolic links";
		else if( iRet == WSAENAMETOOLONG )		msg +=  "File name too long";
		else if( iRet == WSAEHOSTDOWN )			msg +=  "Host is down";
		else if( iRet == WSAEHOSTUNREACH )		msg +=  "No route to host";
		else if( iRet == WSAENOTEMPTY )			msg +=  "Directory not empty";
		else if( iRet == WSAEPROCLIM )			msg +=  "Too many processes";
		else if( iRet == WSAEUSERS )			msg +=  "Too many users";
		else if( iRet == WSAEDQUOT )			msg +=  "Disc quota exceeded";
		else if( iRet == WSAESTALE )			msg +=  "Stale NFS file handle";
		else if( iRet == WSAEREMOTE )			msg +=  "Too many levels of remote in path";
		else if( iRet == WSASYSNOTREADY )		msg +=  "Network system is unavailable";
		else if( iRet == WSAVERNOTSUPPORTED )	msg +=  "Winsock version out of range";
		else if( iRet == WSANOTINITIALISED )	msg +=  "WSAStartup not yet called";
		else if( iRet == WSAEDISCON )			msg +=  "Graceful shutdown in progress";
		else if( iRet == WSAHOST_NOT_FOUND )	msg +=	"Host not found";
		else if( iRet == WSANO_DATA )			msg +=	"No host data of that type was found";
		else if( iRet == WSAEINVAL )			msg +=	"Invalid Argument";
		else msg += "Unknown";		
		break;

	default:
		msg  += "Generic/Unknown deNet error!";
		break;
	}

	// build msg
	if( pFile )	
		msg += "  FILE: "; msg += pFile;

	if( iLine >= 0 )
		msg += "  LINE: "; msg += itoa( iLine, d, 10 );

	msg += " !!\n";
	return strcpy( strTemp, msg.c_str() );
}

//-------------------------------------------------------------------------
// deNetDlg( const char * )
//-------------------------------------------------------------------------
void	deNetDlg( const char * pStr )
{
	// output to debug window
	OutputDebugString( pStr );

	deNetLog( "MESSAGE DIALOG: " );
	deNetLog( pStr );
	deNetLog( "\n" );

	::MessageBox(	NULL, pStr, 
					"PlastiX Engine Problems",
					MB_ICONEXCLAMATION|MB_OK|MB_DEFBUTTON1 ); 
}

//-------------------------------------------------------------------------
// deNetLogStop()
//-------------------------------------------------------------------------
void	deNetLogStop(void)
{
	std::string str = ">> >> deNet log stopped on ";
	char d[32];
	str += _strdate(d); str += "  "; str += _strtime(d);
	str += " << <<\n";
	fwrite( str.c_str(), str.length(), 1, hLogFile );
	fclose( hLogFile );
	hLogFile = NULL;
	OutputDebugString( str.c_str() );
}

//-------------------------------------------------------------------------
// deNetLogStart( FILE )
//-------------------------------------------------------------------------
void	deNetLogStart( FILE * pFile )
{
	if( pFile )
		hLogFile = pFile;
	else
		hLogFile = fopen( "deNet_Log.dat", "w" );

	if( !hLogFile )
		deNetDlg( "Unable to create log file!" );
	else
	{
		char d[32];
		std::string str = ">> >> deNet log started on ";
		str += _strdate(d); str += "  "; str += _strtime(d);
		str += " << << \n";
		fwrite( str.c_str(), str.length(), 1, hLogFile );
		OutputDebugString( str.c_str() );
	}
}

//-------------------------------------------------------------------------
// deNetLog( const char * )
//-------------------------------------------------------------------------
void	deNetLog( const char * pStr )
{
	if( hLogFile )
	{
		fwrite( pStr, strlen(pStr), 1, hLogFile );
		OutputDebugString( pStr );
	}

	if( gLogProcess )
		gLogProcess( pStr );

}

//-------------------------------------------------------------------------
// <function> deNet_SetOnLogHandler( DWORD (* pProcess)( const char * ) )
//-------------------------------------------------------------------------
_inline DENET_API void  deNet_SetOnLogHandler( DWORD (* pProcess)( const char * ) )
{
	gLogProcess = pProcess;
}

//-------------------------------------------------------------------------
// <function> deNet_SetAppName 
//-------------------------------------------------------------------------
_inline DENET_API eNetError deNet_SetAppName( const char * pName )
{
	strcpy( strAppName, pName );
	return CODE_OK;
}

//-------------------------------------------------------------------------
// <function> deNet_GetAppName
//-------------------------------------------------------------------------
_inline DENET_API const char * deNet_GetAppName(void)
{
	return strAppName;
}

//-------------------------------------------------------------------------
// <function> deNet_GetPort(void)
//-------------------------------------------------------------------------
_inline DENET_API DWORD deNet_GetPort(void)
{
	return dwPort;
}

//-------------------------------------------------------------------------
// <function> deNet_GetVersion
//-------------------------------------------------------------------------
_inline DENET_API DWORD deNet_GetVersion(void)
{
	return dwVersion;
};

//-------------------------------------------------------------------------
// <function> deNet_GetThreadPool
//-------------------------------------------------------------------------
_inline DENET_API deThreadPool * deNet_GetThreadPool(void)
{
	return &threadPool;
}

//-------------------------------------------------------------------------
// <function> deNet_GetMaxConnections
//-------------------------------------------------------------------------
_inline DENET_API DWORD deNet_GetMaxConnections(void)
{
	return iMaxNumConnections;
}

//-------------------------------------------------------------------------
// <function> deNet_GetMaxConnectionBins
//-------------------------------------------------------------------------
_inline DENET_API WORD deNet_GetMaxConnectionBins(void)
{
	return iMaxNumConnectionBins;
}

//-------------------------------------------------------------------------
// <function> deNet_Startup
//-------------------------------------------------------------------------
DENET_API eNetError deNet_Startup( DWORD pMaxConnections, DWORD pMaxConnectionBins, DWORD pPort, DWORD pFlags )
{

#ifdef _DEBUG
	int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); // Get current flag
	flag |= _CRTDBG_LEAK_CHECK_DF; // Turn on leak-checking bit
	_CrtSetDbgFlag(flag); // Set flag to the new value
#endif

	if( pMaxConnections <= 0 )
	{
		NET_LOG_ERROR( CODE_INVALID_PARAM );
		return CODE_INVALID_PARAM;
	}

	// start timer
	gTimer.StartTimer();

	// set default values
	strcpy( strAppName, "" );
	dwVersion				= 1;
	dwPort					= pPort;
	iMaxNumConnections		= pMaxConnections;
	iMaxNumConnectionBins	= pMaxConnectionBins;

	// init/reset thread pool
	threadPool.DestroyAllThreads(true);

	// start logger
	NET_LOG_START();

	return CODE_OK;
};

//-------------------------------------------------------------------------
// <fucntion> deNet_Shutdown
//-------------------------------------------------------------------------
DENET_API eNetError deNet_Shutdown( DWORD dwFlags )
{
	// shutdown all service providers
	if( deTCPConnectionProvider::GetMgr() && 
		deTCPConnectionProvider::GetMgr()->IsReady() )
	{
		deTCPConnectionProvider * tcpProvider = deTCPConnectionProvider::GetMgr();
		tcpProvider->Shutdown( NULL );		
		SAFE_DELETE( tcpProvider );
	}

	// shutdown thread pool
	threadPool.DestroyAllThreads(true);

	// set default values
	strcpy( strAppName, "" );
	dwVersion				= 1;
	dwPort					= DN_DEFAULT_PORT;
	iMaxNumConnections		= 0;
	iMaxNumConnectionBins	= 0;

	// release send buffer
	SAFE_DELETE_ARRAY( gSendBuffer );
	gSendBufferSize = 0;

	// stop timer
	gTimer.StopTimer();

	// stop logger
	NET_LOG_STOP();

	return CODE_OK;
}

//-------------------------------------------------------------------------
// <function> deNet_GetTime
//-------------------------------------------------------------------------
#ifdef DN_SESSION_TIME_INT64
 _inline DENET_API s64 deNet_GetTime( void )
 {
 	return gTimer.GetTime();
 }
#else
 _inline DENET_API float deNet_GetTime( void )
 {
 	return (float)(gTimer.GetTime());
 }
#endif