//*******************************************************************************
//* Copyright (c) 2008-2014 Synchrotron SOLEIL
//* All rights reserved. This program and the accompanying materials
//* are made available under the terms of the GNU Lesser Public License v3
//* which accompanies this distribution, and is available at
//* http://www.gnu.org/licenses/lgpl.html
//******************************************************************************
//******************************************************************************************
//
//
//    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.
//
//******************************************************************************************


//- INCLUDE
#include <iostream>
#include "SocketLecroy.h"
#include <sys/time.h>
#include <time.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>

#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstdio>
//- perf!!!
#include <yat/time/Timer.h>

static int  hSocket;
static int  sTimeout = 2;       		//- second(s)
static char sCurrentAddress[256];
bool SocketLecroy::sConnectedFlag = false;

const int CMD_BUF_LEN = 8192;
static char   sCommandBuffer[CMD_BUF_LEN];

//- 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()
{
// std::cout << "\t\t\tSocketLecroy::deleted_instance ... SL_instance = " << SL_instance << std::endl;
  if(SL_instance)
  {
    delete SL_instance;
    SL_instance = 0;
  }
// std::cout << "\t\t\tSocketLecroy::deleted_instance DONE -> SL_instance = " << SL_instance << std::endl;
}

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

//- DTOR
SocketLecroy::~SocketLecroy()
{
// std::cout << "\t\t\tSocketLecroy::DTOR SocketLecroy ..." << std::endl;
  TCP_Disconnect();
// std::cout << "\t\t\tSocketLecroy::DTOR DONE." << std::endl;
}

//- Build the connection
void SocketLecroy::TCP_Connect(char *ip_address) throw (lecroy::SocketException)
{
yat::Timer t;
  struct sockaddr_in serverAddr;
  int result;
  const int resp = 1;
  fd_set wr_set;
  FD_ZERO(&wr_set);

  struct timeval tval;
  unsigned long argp;
  char tmpStr[256];

  //- connection test
  if (sConnectedFlag)
    return;

  strcpy(sCurrentAddress, ip_address);
  tval.tv_sec = 1;
  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)) == INADDR_NONE)
  {
    throw lecroy::SocketException("COMMUNICATION_BROKEN ",
                                  "Bad server address.",
                                  "SocketLecroy::TCP_Connect( ).");
  }

  //- create client's socket
  if ((hSocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) //INVALID_SOCKET)
  {
    throw lecroy::SocketException("COMMUNICATION_BROKEN ",
                                  "Unable to create client's socket.",
                                  "SocketLecroy::TCP_Connect( ).");
  }
  
  //- set Timeout for read operations
  if (setsockopt(hSocket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tval, sizeof(struct timeval)) != 0)
  {
    throw lecroy::SocketException("COMMUNICATION_BROKEN ",
                                  "Unable to set socket option to TCP_NODELAY.",
                                  "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( ).");
  }

  FD_SET(hSocket, &wr_set);
  argp = 1;//-non blocking mode
  ioctl(hSocket, FIONBIO, &argp);

  int opts;
  opts = fcntl (hSocket, F_GETFL);
  if (opts >= 0)
    opts = (opts | O_NONBLOCK);
  fcntl (hSocket, F_SETFL, opts);
  //connect(hSocket, (SOCKADDR FAR *) &serverAddr, sockAddrSize);
  int status = ::connect(hSocket, ( sockaddr *)&serverAddr, sizeof(serverAddr));

  if(status < 0)  // We are not connected : so retry
  {
    if(errno == EINPROGRESS) // But the connection is in progress
    {
      int nb = 0;
      struct timespec time_to_sleep, time_remaining;

      while(nb++ < 5) // We will attempt to connect every 100 ms for 5 times max.
      {
        status = ::connect (hSocket, ( sockaddr *)&serverAddr, sizeof(serverAddr));

        if(status != 0) // Still not connected
        {
          if(errno == EALREADY) // This is the right error !
          {
            time_to_sleep.tv_sec = 0;
            time_to_sleep.tv_nsec = 150000000L;
            nanosleep(&time_to_sleep, &time_remaining);  // 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
  ioctl(hSocket, FIONBIO, &argp);
  //- connect to server (scope)
  if (result < 0)
  {
    sprintf(tmpStr, "Unable to make connection to IP:%s", ip_address);
    throw lecroy::SocketException("COMMUNICATION_BROKEN ",
                                  tmpStr,
                                  "SocketLecroy::TCP_Connect( ).");
  }

  sConnectedFlag = true;
// std::cout << "\t\t\t SocketLecroy::TCP_Connect done in " << t.elapsed_msec() << " ms" << std::endl;
}

//- DisconnectFromScope: disconnect from a network device
void SocketLecroy::TCP_Disconnect(void) throw (lecroy::SocketException)
{
// std::cout << "\t\t\tSocketLecroy::comm closing socket ENTREING ...." << std::endl;
  if (sConnectedFlag)
  {
// 	std::cout << "\t\t\tSocketLecroy::comm closing socket ...." << std::endl;
    close(hSocket);
// 	std::cout << "\t\t\tSocketLecroy::comm closed." << std::endl;
    sConnectedFlag = false;
  }
// std::cout << "\t\t\tSocketLecroy::comm closing socket DONE ...." << std::endl;
}

//- 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);
}

void SocketLecroy::write_read(char* in_buf, unsigned int in_length, char* out_buf, int* out_length, bool eoi_flag)
{
yat::Timer t;
   yat::AutoMutex<> guard(lock_);
   TCP_WriteDevice(in_buf, in_length, eoi_flag);
// std::cout << "\t\t\t SocketLecroy::write_read WRITE done in " << t.elapsed_msec() << " ms" << std::endl;
   TCP_ReadDevice(out_buf, *out_length, out_length);
// std::cout << "\t\t\t SocketLecroy::write_read done in " << t.elapsed_msec() << " ms" << std::endl;
}
	
//- 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;
// std::cout << "\t\t\t TCP_WriteDevice -> ENTERING ..." << std::endl;
  //- 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);

// std::cout << "\t\t\t TCP_WriteDevice -> send ..." << std::endl;
  //- 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( ).");
  }

// std::cout << "\t\t\t TCP_WriteDevice -> sent!" << std::endl;
  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)
    {
// std::cout << "\t\t\tFATAL TCP_WriteDevice -> send ..." << std::endl;
      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;
  }
// std::cout << "\t\t\tFATAL TCP_WriteDevice -> DONE." << std::endl;
}

//- Read the device answer
void SocketLecroy::TCP_ReadDevice(char *buf, unsigned int len, int *recv_count) throw (lecroy::SocketException)
{
  TCP_HEADER header;
  int result;
  unsigned int accum, space_left, bytes_more, buf_count;
  char tmpStr[256];
  char *idxPtr;
  fd_set rd_set;
  FD_ZERO(&rd_set);
  struct 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( ).");

  FD_SET(hSocket, &rd_set);
  tval.tv_sec = 0;
  tval.tv_usec = 500000L;

  memset(buf, 0, len);
  buf_count = 0;
  space_left = len;

  while (1)
  {
    result = select(hSocket, &rd_set, NULL, NULL, &tval);
    if (result < 0)
    {
// std::cout << "\tSocketLecroy::TCP_ReadDevice -> result < 0." << std::endl;
      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);
    unsigned int headerLength = ntohl(header.iLength);

    //- only read to len amount
    if (headerLength > space_left)
    {
      headerLength = 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 = headerLength - 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 >= headerLength)
        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;
}