///////////////////////////////////////////////////////////////////////////////
/// @file deTCPConnectionProvider.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 "deTCPConnectionBin.hpp"
#include "deTCPConnection.hpp"
#include "deNetWinsock.hpp"
#include "deThreadPool.hpp"

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

// static declartions
//-----------------------------------------------
DENET_API deTCPConnectionProvider * deTCPConnectionProvider::m_Singleton	= NULL;

// function declaration
//-----------------------------------------------
void CALLBACK	OnRecvComplete( DWORD	dwError,
								DWORD	dwBytesTransferred,
								LPWSAOVERLAPPED lpOverlapped,
								DWORD	dwFlags );

//-----------------------------------------------
deTCPConnectionProvider::deTCPConnectionProvider(void)
{
	deASSERT( !m_Singleton );
	m_Singleton = this;

	// init connections
	if( m_aConnections )
		SAFE_DELETE_ARRAY( m_aConnections );

	m_aConnections = new deTCPConnection[ deNet_GetMaxConnections() ];
	deASSERT( m_aConnections );

	m_aConnectionBins = new deTCPConnectionBin[ deNet_GetMaxConnectionBins() ];
	deASSERT( m_aConnections );

	// set provider info iNumProtocols
	m_ProviderInfo.dwVersion		= 0;
	m_ProviderInfo.iNumProtocols	= 0;
	m_ProviderInfo.wsProtocolInfo	= NULL;
}

deTCPConnectionProvider::~deTCPConnectionProvider(void)
{
	if( IsReady() )
		Shutdown();

	m_Singleton = NULL;

	// release connections
	SAFE_DELETE_ARRAY( m_aConnections );

	// release connection bins
	SAFE_DELETE_ARRAY( m_aConnectionBins );
}

deConnection * deTCPConnectionProvider::GetConnectionAt( DWORD pIndex )
{
	// ensure this is thread-safe and locked
	ENSURE_LOCK( this, this->GetSynchObject() );

	deASSERT( pIndex < deNet_GetMaxConnections() );
	deTCPConnection * connections = static_cast<deTCPConnection*>(m_aConnections);
	deASSERT( connections );

	return connections + pIndex;
}

deConnectionBin * deTCPConnectionProvider::GetAvailableBin(void)
{
	deTCPConnectionBin * bin = NULL;
	deTCPConnectionBin * bins = static_cast<deTCPConnectionBin*>(m_aConnectionBins);

	for( int i=0; i <= GetNumProvidedConnectionBins(); i++ )
	{
		if( bins[i].GetNumConnections() < bins[i].GetNumMaxConnections() )
		{
			bin = bins + i;
			break;
		}
	}

	return bin;
}

deConnection * deTCPConnectionProvider::GetUnusedConnection(void)
{
	deTCPConnection * conn = NULL;
	deTCPConnection * connections = static_cast<deTCPConnection*>(m_aConnections);
	deASSERT( connections );

	for( int i=0; i <= this->GetNumProvidedConnections(); i++ )
	{
		if( !connections[i].IsReady() )
		{
			conn = connections + i;
			break;
		}
	}

	return conn;
}

eNetError deTCPConnectionProvider::OnInit( DWORD dwFlags )
{
	deASSERT( m_SynchObject.IsLocked() );

	int iRet = 0;

	m_ProviderInfo.dwVersion = WINSOCK_VERSION;

	// init Winsock.dll
	iRet = WSAStartup( m_ProviderInfo.dwVersion, &m_ProviderInfo.wsDeviceData );
	if( iRet )
	{
		// there was an error!
		NET_LOG_ERROR( CODE_WINSOCK_ERROR );
		WSACleanup();
		return CODE_WINSOCK_ERROR;
	}
	
	// check version
	if( m_ProviderInfo.dwVersion != m_ProviderInfo.wsDeviceData.wVersion )
	{
		NET_LOG( "Unsupported Winsock version! Shutting down..." );
		WSACleanup();
		return CODE_UNSUPPORTED_VERSION;
	}

	// enum protocols
	WSAPROTOCOL_INFO *	info = NULL;
	int					iNum = 0;
	unsigned long		iLen = 0; // we have a static array of twelve
	iRet = WSAEnumProtocols( NULL, info, &iLen );
	if( iRet != SOCKET_ERROR )
	{
		// there wasn't an error, this shouldn't happend
		WSACleanup();
		return CODE_WINSOCK_ERROR;
	}
	else if( WSAENOBUFS != (iRet = WSAGetLastError()) )
	{
		// an error occured that wasn't what we expected
		NET_LOG_ERROR( CODE_WINSOCK_ERROR );
		return CODE_WINSOCK_ERROR;
	}
	else
	{
		// get number
		m_ProviderInfo.iNumProtocols = iLen / sizeof(WSAPROTOCOL_INFO);
		deASSERT( iLen > 0 );

		// allocate mem
		m_ProviderInfo.wsProtocolInfo = new WSAPROTOCOL_INFO[m_ProviderInfo.iNumProtocols];
		
		iRet = WSAEnumProtocols( NULL, m_ProviderInfo.wsProtocolInfo, &iLen );

		if( iRet == SOCKET_ERROR )
		{
			// an error occured that wasn't what we expected
			NET_LOG_ERROR( CODE_WINSOCK_ERROR );
			return CODE_WINSOCK_ERROR;
		}
	}

	// output the protocols we know about
	std::string strInfo = "";
	char d[255];
	memset( d, 0, 255 );
	iNum = iLen / sizeof(WSAPROTOCOL_INFO);
	info = m_ProviderInfo.wsProtocolInfo;

	//for( int i=0; i<iNum; i++ )
	if( false == true )
	{	 
		if( (info->dwProviderFlags & PFL_HIDDEN) == 0 )
		{
			strInfo =	"Protocol: "; 
			strInfo +=	info->szProtocol;
			strInfo +=	" ID [";
			strInfo +=	itoa( info->iProtocol,d,10 );
			strInfo +=	"] Version [";
			strInfo +=	itoa( info->iVersion,d,10 );
			strInfo +=	"] Socket Type [";
			strInfo +=	itoa( info->iSocketType,d,10 );
			strInfo +=	"] Family Type [";
			strInfo +=	itoa( info->iAddressFamily,d,10 );
			strInfo +=	"]\n"; 

			if( info->dwProviderFlags & PFL_RECOMMENDED_PROTO_ENTRY  )
				strInfo += "** Recommended Protocol Entry **\n";
		
			if( info->dwServiceFlags1 & XP1_CONNECTIONLESS )
				strInfo += "Type = Connectionless [UDP]\n";
			else
				strInfo += "Type = Connection Oriented [TCP]\n";

			if( info->dwServiceFlags1 & XP1_GUARANTEED_DELIVERY )
				strInfo += "Guaranteed Delivery = true\n";
			else
				strInfo += "Guaranteed Delivery = false\n";

			if( info->dwServiceFlags1 & XP1_GUARANTEED_ORDER  )
				strInfo += "Guaranteed Order = true\n";
			else
				strInfo += "Guaranteed Order = false\n";

			if( info->dwServiceFlags1 & XP1_GRACEFUL_CLOSE   )
				strInfo += "Graceful Close [Two-Phase] = true\n";
			else
				strInfo += "Graceful Close [Two-Phase] = false\n";

			if( info->dwServiceFlags1 & XP1_CONNECT_DATA    )
				strInfo += "Supports Connect Data = true\n";
			else
				strInfo += "Supports Connect Data = false\n";

			if( info->dwServiceFlags1 & XP1_DISCONNECT_DATA     )
				strInfo += "Supports Disconnect Data = true\n";
			else
				strInfo += "Supports Disconnect Data = false\n";

			if( info->dwServiceFlags1 & XP1_SUPPORT_BROADCAST      )
				strInfo += "Supports Broadcasting = true\n";
			else
				strInfo += "Supports Broadcasting = false\n";

			if( info->dwServiceFlags1 & XP1_MESSAGE_ORIENTED      )
				strInfo += "Honors Message Boundaries = true\n";
			else
				strInfo += "Honors Message Boundaries = false\n";  

			if( info->dwServiceFlags1 & XP1_PSEUDO_STREAM      )
				strInfo += "Pseudo Stream = true\n";
			else
				strInfo += "Pseudo Stream = false\n";  

			NET_LOG( "--------------------------------------------------------------------\n" );
			NET_LOG( strInfo.c_str() );
			NET_LOG( "--------------------------------------------------------------------\n" );
			strInfo = "";
			info++;
		}
	}

	return CODE_OK;
};

eNetError deTCPConnectionProvider::OnShutdown( DWORD dwFlags )
{
	// clean up winsock 2
	int iRet = WSACleanup();

	// release provider info
	m_ProviderInfo.dwVersion		= 0;
	m_ProviderInfo.iNumProtocols	= 0;
	SAFE_DELETE_ARRAY( m_ProviderInfo.wsProtocolInfo );

	if( iRet )
	{
		NET_LOG_ERROR( CODE_WINSOCK_ERROR );
		return CODE_WINSOCK_ERROR;
	}
	else
		return CODE_OK;
};

eNetError deTCPConnectionProvider::OnCreateConnection( deConnection ** pOut, void * pProviderInfo, DWORD dwFlags )
{
	int		iRet	= 0;
	bool	ok		= true;
	
	deTCPConnection * conn = static_cast<deTCPConnection*>(*pOut);
	deASSERT(conn);

	// lock connection
	conn->GetSynchObject()->Lock( this );
	
	if( !pProviderInfo )
	{
		// create socket
		conn->m_ConnectionInfo.hSocket = WSASocket( AF_INET, 
													SOCK_STREAM, 
													IPPROTO_TCP, /*IPPROTO_IP*/
													NULL,
													conn->m_ConnectionInfo.gGroup,
													WSA_FLAG_OVERLAPPED );

		// set socket options
		if( conn->m_ConnectionInfo.hSocket != INVALID_SOCKET )
		{
			DWORD	dwArg	= 0;
			int		iArg	= 0;

			// set max recv size
			dwArg	= deConnection::GetMaxPacketSize();
			iRet	= ioctlsocket( conn->m_ConnectionInfo.hSocket, FIONREAD, &dwArg );

			if( iRet == SOCKET_ERROR )
			{
				// something went wrong
				NET_LOG( "Unable to set max recieve buffer size for socket!\n" );
				NET_LOG_ERROR( CODE_WINSOCK_ERROR );
			}

			/*
			// allow conditional accept
			iArg = 1;
			iRet = setsockopt(	conn->m_ConnectionInfo.hSocket,
								SOL_SOCKET, SO_CONDITIONAL_ACCEPT, &iArg, sizeof(int) );

			if( iRet == SOCKET_ERROR )
			{
				// something went wrong
				NET_LOG( "Unable to allow conditional accept for given socket!\n" );
				NET_LOG_ERROR( CODE_WINSOCK_ERROR );
			}
			*/

#if _DEBUG
			// allow debug info 
			iArg = 1;
			iRet = setsockopt(	conn->m_ConnectionInfo.hSocket,
								SOL_SOCKET, SO_DEBUG, (char*)&iArg, sizeof(int) );

			if( iRet == SOCKET_ERROR )
			{
				// something went wrong
				NET_LOG( "Unable to allow debug info for given socket!\n" );
				NET_LOG_ERROR( CODE_WINSOCK_ERROR );
			}
#endif
			// don't linger on close 
			iArg = 1;
			iRet = setsockopt(	conn->m_ConnectionInfo.hSocket,
								SOL_SOCKET, SO_DONTLINGER, (char*)&iArg, sizeof(int) );

			if( iRet == SOCKET_ERROR )
			{
				// something went wrong
				NET_LOG( "Unable to disable OnClose lingering for given socket!\n" );
				NET_LOG_ERROR( CODE_WINSOCK_ERROR );
			}

			// don't route messages 
			iArg = 1;
			iRet = setsockopt(	conn->m_ConnectionInfo.hSocket,
								SOL_SOCKET, SO_DONTROUTE, (char*)&iArg, sizeof(int) );

			if( iRet == SOCKET_ERROR )
			{
				// something went wrong
				NET_LOG( "Unable to disable routing of messages for given socket!\n" );
				NET_LOG_ERROR( CODE_WINSOCK_ERROR );
			}

			// don't send keep-alives, i'll do it myself 
			iArg = 0;
			iRet = setsockopt(	conn->m_ConnectionInfo.hSocket,
								SOL_SOCKET, SO_KEEPALIVE, (char*)&iArg, sizeof(int) );

			if( iRet == SOCKET_ERROR )
			{
				// something went wrong
				NET_LOG( "Unable to disable keep-alive messages for given socket!\n" );
				NET_LOG_ERROR( CODE_WINSOCK_ERROR );
			}

			// set total per-socket buffer space for recv 
			iArg = deConnection::GetMaxPacketSize() + 4; // pad it a little
			iRet = setsockopt(	conn->m_ConnectionInfo.hSocket,
								SOL_SOCKET, SO_RCVBUF, (char*)&iArg, sizeof(int) );

			if( iRet == SOCKET_ERROR )
			{
				// something went wrong
				NET_LOG( "Unable to set buffer size [recv] for given socket!\n" );
				NET_LOG_ERROR( CODE_WINSOCK_ERROR );
			}

			// set total per-socket buffer space for send 
			iArg = deConnection::GetMaxPacketSize() + 4; // pad it a little
			iRet = setsockopt(	conn->m_ConnectionInfo.hSocket,
								SOL_SOCKET, SO_SNDBUF, (char*)&iArg, sizeof(int) );

			if( iRet == SOCKET_ERROR )
			{
				// something went wrong
				NET_LOG( "Unable to set buffer size [send] for given socket!\n" );
				NET_LOG_ERROR( CODE_WINSOCK_ERROR );
			}

			// disables nagle algo 
			iArg = 1;
			iRet = setsockopt(	conn->m_ConnectionInfo.hSocket,
								IPPROTO_TCP, TCP_NODELAY, (char*)&iArg, sizeof(int) );

			if( iRet == SOCKET_ERROR )
			{
				// something went wrong
				NET_LOG( "Unable to disable Nagle algo for given socket!\n" );
				NET_LOG_ERROR( CODE_WINSOCK_ERROR );
			}
		}
	}
	else
	{
		// a socket was supplied for us
		conn->m_ConnectionInfo.hSocket = *((SOCKET*)pProviderInfo);
	}

	if( conn->m_ConnectionInfo.hSocket == INVALID_SOCKET )
	{
		NET_LOG_ERROR( CODE_WINSOCK_ERROR );

		// unlock connection
		conn->GetSynchObject()->Unlock();

		return CODE_WINSOCK_ERROR;
	}

	memset( &conn->m_ConnectionInfo.address, 0, sizeof(SOCKADDR_IN) );

	// set up local address
	char strIP[16];
	char strName[16];
	in_addr tmpAddr;
	tmpAddr.s_addr = GetHostAddress();
	GetHostName( strName );
	strcpy( strIP, inet_ntoa( tmpAddr ) );
	
	// save connection desc
	conn->m_ConnectionDesc.m_LocalAddress = deNetAddress(	GetHostAddress(),
															htons(DN_DEFAULT_PORT),
															strIP,	
															strName );

	// spit out the new pointer
	*pOut = conn;

	// unlock connection
	conn->GetSynchObject()->Unlock();

	return CODE_OK;
}

eNetError deTCPConnectionProvider::OnCloseConnection( deConnection ** pConn, DWORD dwFlags )
{
	deASSERT( m_SynchObject.IsLocked() );

	return (*pConn)->Disconnect( dwFlags );
};

/*
DWORD deTCPConnectionProvider::HandleTCPConnections( void * pInfo )
{
	ConnectionHandleEntry * entry		= reinterpret_cast<ConnectionHandleEntry*>(pInfo);
	deASSERT( entry );

	deTCPConnectionProvider * provider	= entry->source;
	deASSERT( provider );

	WSANETWORKEVENTS	event;
	WSAEVENT			events [WS2_CONNECTIONS_PER_THREAD];
	SOCKET				sockets[WS2_CONNECTIONS_PER_THREAD];
	deTCPConnection *	conns  [WS2_CONNECTIONS_PER_THREAD];
	

	int			iNumCons	= 0;
	DWORD		dwRet		= 0;
	int			iRet		= 0;
	int			i			= 0;
	bool		bStop		= false;
	
	for( i=0; i<WS2_CONNECTIONS_PER_THREAD; i++ )
	{
		events[i]	= NULL;
		sockets[i]	= NULL;
		conns[i]	= NULL;
	}

	if( events[0] == WSA_INVALID_EVENT )
	{
		NET_LOG_ERROR( CODE_WINSOCK_ERROR );
		return -1;
	}

	iRet = WSAEventSelect(	sockets[0], events[0], 
							FD_READ | FD_CLOSE | FD_ACCEPT );

	if( iRet == SOCKET_ERROR )
	{
		NET_LOG_ERROR( CODE_WINSOCK_ERROR );
		return -1;
	}

	iNumCons = 1;

	while( !bStop )
	{
		dwRet = WSAWaitForMultipleEvents( iNumCons, events, false, 2000, true );

		// which event was triggerd?
		int index = dwRet - WSA_WAIT_EVENT_0;

		if( dwRet != WSA_WAIT_TIMEOUT && index >= 0 )
		{
			iRet = WSAEnumNetworkEvents( sockets[index], events[index], &event );
			if( iRet == SOCKET_ERROR && index < 0 )
			{
				NET_LOG_ERROR( CODE_WINSOCK_ERROR );
				NET_LOG( "!* Dropping event due to error... *!\n" );
			}
			else
			{
				// handle the event
				if( event.lNetworkEvents & FD_READ )
				{
					// read event
				}
				else if( event.lNetworkEvents & FD_ACCEPT )
				{
					// accept event
					deTCPConnection::deTCPConnectionRequest request;
					request.hListeningSocket	= sockets[index];
					if( conns[index]->OnAccept( &request ) == CODE_OK )
						printf( "connection accepted...\n" );
					else
						printf( "connection was NOT accepted...\n" );
				}
				else if( event.lNetworkEvents & FD_CONNECT )
				{
					// connect event
				}
				else if( event.lNetworkEvents & FD_CLOSE )
				{
					deConnection * c = conns[index];
					provider->CloseConnection( &c );
					conns[index] = NULL;
					WSACloseEvent( events[index] );
					events[index] = NULL;
					iNumCons--;
					printf( "connetion closed....\n" );
					// close event 
				}
				else
				{
					NET_LOG( "!* Unable to determine type of Winsock event. Droping Event... *!\n" );
				}
			}
		}

		if( iNumCons <= 0 )
		{
			bStop = true;
		}
	}

	return 0;
}
*/