//******************************************************************************************
//
//
//		september 13, 2004 :  Source file for the communication in socket mode
//
//								with a Lecroy scope (avaiable for all models)
//		
//		author : X.Elattaoui
//
//		SocketLecroy.cpp: implementation of the SocketLecroy class.
//
//******************************************************************************************

static int	hSocket;
static int	sTimeout = 15;
//static int	sWinsockInitFlag = FALSE;
static char	sCurrentAddress[256];
static int 	sConnectedFlag = false;
const int	CMD_BUF_LEN	= 8192;
static char sCommandBuffer[CMD_BUF_LEN];

//- INCLUDE
#include "SocketLecroy.h"

//- init of the static instance
SocketLecroy* SocketLecroy::SL_instance = 0;	//- ptr on the SocketLecroy instance
 
SocketLecroy* SocketLecroy::get_instance()
{
	if( !SL_instance )
		SL_instance = new SocketLecroy();

	return SL_instance;
	
}

void SocketLecroy::delete_instance(SocketLecroy* SL_instance)
{
	if(SL_instance)
	{
		delete SL_instance ;
		SL_instance = 0;
	}

}

//- CTOR
SocketLecroy::SocketLecroy()
{
	sConnectedFlag=false;

}
//- DTOR
SocketLecroy::~SocketLecroy()
{
   TCP_Disconnect();

}

//- Build the connection
void SocketLecroy::TCP_Connect(char *ip_address) throw (lecroy::SocketException)
{

SOCKADDR_IN	serverAddr;
int sockAddrSize = sizeof (SOCKADDR), result;
const int resp = 1;
fd_set wr_set = {1, {0}};
TIMEVAL tval;
unsigned long argp;
char tmpStr[256];
	
	
	//- connection test
	if (sConnectedFlag)
		return;

	strcpy(sCurrentAddress, ip_address);
	
	tval.tv_sec = sTimeout;
	tval.tv_usec = 0;	

    //- build server socket address
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons (SERVER_PORT);
			
	if ((serverAddr.sin_addr.s_addr = inet_addr(ip_address)) == -1) 
	{
		throw lecroy::SocketException("COMMUNICATION_BROKEN ", 
										"Bad server address.",
										"SocketLecroy::TCP_Connect( ).");
	}

    //- create client's socket
  	if ((hSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
	{
		throw lecroy::SocketException("COMMUNICATION_BROKEN ", 
										"Unable to create client's socket.",
										"SocketLecroy::TCP_Connect( ).");
	}

    if (setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&resp, sizeof(resp)) != 0)
	{
		throw lecroy::SocketException("COMMUNICATION_BROKEN ", 
										"Unable to set socket option to TCP_NODELAY.",
										"SocketLecroy::TCP_Connect( ).");
	}

	wr_set.fd_array[0] = hSocket;
	
	argp = 1;//-non blocking mode
	ioctlsocket(hSocket, FIONBIO, &argp);

	int status = connect(hSocket, (SOCKADDR FAR *) &serverAddr, sockAddrSize);
	int error_code = WSAGetLastError();

	//- already connected !
	if(status && error_code == WSAEISCONN)
		return;
	if(status != 0)  // We are not connected : so retry
	{
      		if(error_code == WSAEINPROGRESS || error_code == WSAEWOULDBLOCK) // But the connection is in progress
			{
				int nb = 0;
				
				while(nb++ < 5) // We will attempt to connect every 100 ms for 5 times max.
				{
					status = ::connect (hSocket, ( sockaddr *)&serverAddr, sizeof(serverAddr));
					error_code = WSAGetLastError();
					if(status != 0) // Still not connected
					{
						if(errno == WSAEISCONN) // This is the right error !
						{
							Sleep(150);  // Sleep for 150 ms
						}
					}// Connection is OK.
					else
						break;
				}//TODO : throw ; // Too much attempts, so failure !
			}// TODO : throw ; // Not the right error, so failure !
	}// Connected at first attempt !
	
	result = select(hSocket, NULL, &wr_set, NULL, &tval);
	
	argp = 0;//-blocking mode
	ioctlsocket(hSocket, FIONBIO, &argp);
	
	//- connect to server (scope)
	if (result == SOCKET_ERROR)
	{
  	    sprintf(tmpStr, "Unable to make connection to IP:%s", ip_address);
		throw lecroy::SocketException("COMMUNICATION_BROKEN ", 
										tmpStr,
										"SocketLecroy::TCP_Connect( ).");
	}

	sConnectedFlag = TRUE;
}


//- DisconnectFromScope: disconnect from a network device
void SocketLecroy::TCP_Disconnect(void) 
{
	
	if (sConnectedFlag)
	{
		closesocket(hSocket);
		sConnectedFlag = FALSE;
	}

}

//- Clear a connection
void SocketLecroy::TCP_ClearDevice(void) throw (lecroy::SocketException)
{

	if ( !sConnectedFlag )
		throw lecroy::SocketException("COMMUNICATION_BROKEN", 
										"Disconnection already done.",
										"SocketLecroy::TCP_ClearDevice( ).");
	
	TCP_Disconnect();
	TCP_Connect(sCurrentAddress);	

}

//- Send commands to the remote device
void SocketLecroy::TCP_WriteDevice(char *buf, int len, bool eoi_flag) throw (lecroy::SocketException)
{

TCP_HEADER header;
int result, bytes_more, bytes_xferd;
char *idxPtr;
	
	//- test connection
	if ( !sConnectedFlag )
		throw lecroy::SocketException("COMMUNICATION_BROKEN ", 
										"Device not connected.",
										"SocketLecroy::TCP_WriteDevice( ).");
		
	if (len < CMD_BUF_LEN)
		strcpy(sCommandBuffer, buf);
	
	//- set the header info
	header.bEOI_Flag = DATA_FLAG;
	header.bEOI_Flag |= (eoi_flag)? EOI_FLAG:0;
	header.reserved[0] = 1;
	header.reserved[1] = 0;
	header.reserved[2] = 0;
	header.iLength = htonl(len);
	
	
	//- write the header first
	if (send(hSocket, (char *) &header, sizeof(TCP_HEADER), 0) != sizeof(TCP_HEADER))
	{
		throw lecroy::SocketException("COMMUNICATION_BROKEN ", 
										"Unable to send header info to the server.",
										"SocketLecroy::TCP_WriteDevice( ).");
	}
	
	bytes_more = len;
	idxPtr = buf;
	bytes_xferd = 0;
	while (1)
	{
		//- then write the rest of the block
		idxPtr = buf + bytes_xferd;	
		
		if ((result = send(hSocket, (char *) idxPtr, bytes_more, 0)) < 0)
		{
			throw lecroy::SocketException("COMMUNICATION_BROKEN ", 
											"Unable to send data to the server.",
											"SocketLecroy::TCP_WriteDevice( ).");
		}
		
		bytes_xferd += result;
		bytes_more -= result;
		if (bytes_more <= 0)
			break;
	}

}

//- Read the device answer
void SocketLecroy::TCP_ReadDevice(char *buf, int len, int *recv_count) throw (lecroy::SocketException)
{

TCP_HEADER header;
int result, accum, space_left, bytes_more, buf_count;
char tmpStr[256];
char *idxPtr;
fd_set rd_set = {1, {0}};
TIMEVAL tval;
	
	//- test connection
	if ( !sConnectedFlag )
		throw lecroy::SocketException("COMMUNICATION_BROKEN ", 
										"Device not connected.",
										"SocketLecroy::TCP_ReadDevice( ).");
	
	*recv_count = 0;
	
	if (!buf)
		throw lecroy::SocketException("COMMUNICATION_BROKEN ", 
										"Buffer memory not allocated.",
										"SocketLecroy::TCP_ReadDevice( ).");
	
	rd_set.fd_array[0] = hSocket;
	tval.tv_sec = sTimeout;
	tval.tv_usec = 0;	
	
	memset(buf, 0, len);
	buf_count = 0;
	space_left = len;
		
	while (1)
	{
		result = select(hSocket, &rd_set, NULL, NULL, &tval);
		if ( !result || result == SOCKET_ERROR)
		{
			TCP_ClearDevice();
			throw lecroy::SocketException("COMMUNICATION_BROKEN ", 
											"Read Timeout.",
											"SocketLecroy::TCP_ReadDevice( ).");
		}
		//- get the header info first
		accum = 0;
		while (1)
		{
			memset(&header, 0, sizeof(TCP_HEADER));
			
			if ((result = recv(hSocket, (char *) &header + accum, sizeof(header) - accum, 0)) < 0)
			{
				TCP_ClearDevice();
				throw lecroy::SocketException("COMMUNICATION_BROKEN ", 
												"Unable to receive header info from the server.",
												"SocketLecroy::TCP_ReadDevice( ).");
			}
			
			accum += result;
			if (accum>=sizeof(header))
				break;
		}
	
		header.iLength = ntohl(header.iLength);
//		if (header.iLength < 1)
//			return 0;
	
		//- only read to len amount
		if (header.iLength > space_left)
		{
			header.iLength = space_left;
			throw lecroy::SocketException("COMMUNICATION_BROKEN ", 
											"Read buffer size is too small.",
											"SocketLecroy::TCP_ReadDevice( ).");
		}
	
		//- read the rest of the block
		accum = 0;
		while (1)
		{
			idxPtr = buf + (buf_count + accum);
			bytes_more = header.iLength - accum;
			if ((space_left-accum) < TCP_MINIMUM_PACKET_SIZE)
			{
				TCP_ClearDevice();
				sprintf(tmpStr, "Read buffer needs to be adjusted, must be minimum of %d bytes", TCP_MINIMUM_PACKET_SIZE);
				throw lecroy::SocketException("COMMUNICATION_BROKEN ", 
												tmpStr,
												"SocketLecroy::TCP_ReadDevice( ).");
			}
			
			if ((result = recv(hSocket, (char *) idxPtr, (bytes_more>2048)?2048:bytes_more, 0)) < 0)
			{
				TCP_ClearDevice();
				//-MessageBox(0, "Unable to receive data from the server.", "ERROR", MB_OK);
				throw lecroy::SocketException("COMMUNICATION_BROKEN ", 
												"Unable to receive data from the server",
												"SocketLecroy::TCP_ReadDevice( ).");
			}

			accum += result;
			if (accum >= header.iLength)
				break;
			if ((accum + buf_count) >= len)
				break;
		}
		buf_count += accum;
		space_left -= accum;
		
		if (header.bEOI_Flag & EOI_FLAG)
			break;
		if (space_left <= 0)
			break;
	}
	
	*recv_count = buf_count;
}