#include "StdAfx.h"
#include <winsock2.h>
#include "TCPClient.h"

/************************************************************************************
METHOD      :  TCPClient ctor
ARGUMENTS   :  char* pchIpAddress            -- IP Address
               UINT32 nPort                  -- port number
*************************************************************************************/
TCPClient::TCPClient( char* pchIpAddress, UINT32 nPort, Status *pStatus, char *pClientName )  
:  m_bIsConnected(false),
   m_nPort( nPort ),
	m_pStatus( pStatus ),
	m_nSocketID( 0 )
{  
	int nWinsockVersion = (2 << 8) + 2;
	WSADATA			wsaData;
	
	if( WSAStartup(nWinsockVersion, &wsaData) == 0 )
	{
		m_bWsaStarted = true;
	}
	else
	{
		AfxMessageBox( "TCPClient::CRITICAL ERROR WinSocket startup error", MB_OK );
		m_pStatus->PrintStatus( "TCPClient::CRITICAL ERROR WinSocket startup error, exiting\n" );
		m_bWsaStarted = false;
	}


	strcpy_s( m_pchIpAddress, pchIpAddress );
	strcpy_s( m_pchClientName, pchIpAddress );
	if( pClientName ) 
		strcpy_s( m_pchClientName, pClientName );

}



/************************************************************************************
METHOD      :  TCPClient dtor
*************************************************************************************/
TCPClient::~TCPClient( void )  
{
	Kill();
	Sleep(500);
	WSACleanup();
}



/************************************************************************************
METHOD      :  Initialize
DESCRIPTION :  Create a client socket and connect to the server defined in the
            :  constructor (passed in port number)
ARGUMENTS   :  INT32 nReceiveTimeout_ms      -- passed in receive timeout
RETURNS     :  true if the socket was successfully created.  false otherwise
*************************************************************************************/
bool TCPClient::Initialize(INT32 nReceiveTimeout_ms)
{
	m_bIsConnected = false;

	if (!m_bWsaStarted)
	{
		m_pStatus->PrintStatus( "Ethernet ERROR, WinSock not initialized", MB_OK );
		return false;
	}

	// try to connect
	int nPort = htons( m_nPort );

	// create client socket
	if( (m_nSocketID = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP )) == INVALID_SOCKET )
	{
		m_pStatus->PrintStatus( "Ethernet ERROR: Failed socket creation", MB_OK );
		return false;
	}

	sockaddr_in socketAdx;
	socketAdx.sin_family = AF_INET;      
	socketAdx.sin_port = nPort;
	socketAdx.sin_addr.s_addr = inet_addr( m_pchIpAddress );

	//m_pStatus->PrintStatus( "Ethernet: Trying to connect to SL Sensor...", MB_OK );

	int nConnectValue;
	int nErrorValue;

	while (true)
	{	
		nConnectValue = connect( m_nSocketID, (SOCKADDR*) &socketAdx, sizeof(socketAdx));

		if (nConnectValue != SOCKET_ERROR)
			break;

		nErrorValue = WSAGetLastError();

		if ((nErrorValue != WSAEWOULDBLOCK) && (nErrorValue != WSAECONNREFUSED))
		{
			//m_pStatus->PrintStatus( "TCPClient: ERROR, in connect: %d", (int)nErrorValue );
			///return false;
			Sleep(500);
		}
		else
		{
			//m_pStatus->PrintStatus( "TCPClient: ERROR, in connect: %d, retrying...", (int)nErrorValue );
			Sleep(500);
		}
	}

	m_bIsConnected = true;

	int milliseconds = nReceiveTimeout_ms;
	setsockopt( m_nSocketID, SOL_SOCKET, SO_RCVTIMEO, (char *)&milliseconds, sizeof( milliseconds ) ); 

	// disable Nagle algo - send packets as fast as you get them, regardless of size
	UINT8 flag = 1; 
	setsockopt( m_nSocketID, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof( flag ) ); 

	// do not block on close waiting for unsent data
   int dummyval = 1;
   setsockopt( m_nSocketID, SOL_SOCKET, SO_DONTLINGER, (char *)&dummyval, sizeof( dummyval ) ); 

	return true;
}

void TCPClient::Reinitialize(INT32 nReceiveTimeout_ms)
{
	//m_pStatus->PrintStatus( "Ethernet Error: Lost Connection, Re-Connecting..." );
	Kill();
	while (!Initialize( nReceiveTimeout_ms /*TCP_WAITFOREVER*/ ) );
	//m_pStatus->PrintStatus( "Ethernet: Re-Connection Successful" );
}

/************************************************************************************
METHOD      :  Kill
DESCRIPTION :  Closes the client socket
RETURNS     :  true if the socket was successfully closed
*************************************************************************************/
bool TCPClient::Kill(void)
{
   bool bReturnValue = true;

	if( m_nSocketID != 0 ) 
	{
		WSASendDisconnect(m_nSocketID, NULL ); Sleep(100);
		if (closesocket( m_nSocketID ) != 0) { bReturnValue = false; }
	}

   return bReturnValue;
}

bool TCPClient::ReadPacketHdr( PacketHdr *pPacketHdr, UINT16 *socketError )
{
	int  nBytesRead = 0;
	bool rtn = true;

	if( (nBytesRead = recv( m_nSocketID, (char *)pPacketHdr, (int)PacketHdr::HDR_SIZE, 0 /*nFlags*/ )) == SOCKET_ERROR
		 || nBytesRead == 0 )
	{
		rtn = false;
		*socketError = (UINT16)WSAGetLastError();
	}
	return rtn;
}

bool TCPClient::ReadPacketData( INT8 *pPacketData, UINT16 length, UINT16 *socketError )
{
	int   nBytesRead      = 0;
	bool  rtn             = false;
	INT16 nTotalBytesRead = 0;
	char	*ptr            = (char *)pPacketData;
	int   nBytesToRead    = length;

	do
	{
		// read packet data
		if( (nBytesRead = recv( m_nSocketID, ptr, nBytesToRead, 0 /*nFlags*/ )) == SOCKET_ERROR )
		{
			*socketError = WSAGetLastError();
			break;
		}
		else if( nBytesRead > 0)
		{
			nTotalBytesRead += nBytesRead;
			ptr += nBytesRead;
			nBytesToRead -= nBytesRead;
			rtn = true;
		}
		else
		{
			*socketError = SOC_ERR_0_BYTES_READ;
			break;
		}
	} while( nTotalBytesRead < (int)length ); 
	return rtn;
}


		

/************************************************************************************
METHOD      :  SendPacket
DESCRIPTION :  Sends a packet out the client socket
RETURNS     :  true if the message was sent successfully
*************************************************************************************/
bool TCPClient::SendPacket(Packet* pkt)
{
   int nNumWritten; 

	nNumWritten = send(m_nSocketID, (const char*) pkt->HdrStart(), pkt->Length(), 0);

   return (nNumWritten == (pkt->Length()) );
}

/************************************************************************************
METHOD      :  SendPacket - MBARI style
DESCRIPTION :  Sends a packet out the client socket
RETURNS     :  true if the message was sent successfully
*************************************************************************************/
bool TCPClient::SendPacket(CTDPacket* pkt)
{
	bool bRtn = false;
   int socketStatus; 

	socketStatus = send( m_nSocketID, (const char*) pkt->HdrStart(), pkt->Length(), 0);
	if( socketStatus == SOCKET_ERROR )
	{
		int lastError = WSAGetLastError();
		//cout << "TCPServer::SendPacket, error " << lastError << "  pkt len " << len << endl;
		bRtn    = false;
	}
	else 
	{
		bRtn = (socketStatus == (pkt->Length()));
	}

	if( !bRtn )
	{
		int error = 1;
	}
   return bRtn;
}


/************************************************************************************
METHOD      :  ReSync
DESCRIPTION :  Step thru TCP receive buffer, looking for next Alignment tag
*************************************************************************************/
bool TCPClient::ReSync( PacketHdr *pPacketHdr )
{
	char buffer[9]; // looking for 0x3DF0
	UINT16 tag = 0x3DF0;
	bool	resync = false;
	char *pPacketPtr = (char *)pPacketHdr->DataStart();
	int  byteReadCnt = 0;
	bool bRtn = false;

	memset( buffer, 0, 9 );

	while( !resync )
	{
		if( recv( m_nSocketID, (char *)(&buffer[1]), 8, 0 /*nFlags*/ ) != SOCKET_ERROR )
		{
			byteReadCnt += 8;
			for( int i=0; i<8; i++ )
			{
				if( memcmp( &buffer[i], &tag, sizeof( tag ) ) == 0 ) // found it!
				{
					// read the rest of the hdr
					if( recv( m_nSocketID, (char *)(pPacketPtr+9-i), 5+i, 0 ) != SOCKET_ERROR )
					{
						memcpy( pPacketPtr, buffer+i, 9-i );
						resync = true;
						bRtn   = true;
						break;
					}
				}
			}
			if( !resync )
			{
				buffer[0] = buffer[8];
				if( byteReadCnt > 1024 ) // we are toast - can't find sync word...
				{
					break;
				}
			}
		}
		Sleep(5);
	}
	return bRtn;
}


