/**
 * @file SESAME_facteur.c
 * @brief SESAME_facteur is a program used for communicate with a SESAME protocol server
 *
 *  @author Zoltan Hubert <zoltan.hubert@obspm.fr>
 *  @author Denis Perret <denis.perret@obspm.fr>
 *  @author Eric Gendron <eric.gendron@obspm.fr>
 *  @author Arnaud Sevin <arnaud.sevin@obspm.fr>
 *
 *  Compilation :
 *  gcc -Wall SESAME_facteur.c -o SESAME_facteur
 *
 *  for Windows, please use cygwin with :<br>
 *  gcc -mno-cygwin -Wall SESAME_facteur.c -o SESAME_facteur -lwsock32
 *  <br>
 *  History :<br>
 *  
 *  05 jun 2008
 *  - Modify commentaries for Doxygen compatibility
 *  - replacing stderr by stdout for windows compatibility
 *  
 *  24 avr 2008
 *  - compatible with cygwin et UNIX-like
 *  - set the same TIMEOUT for cygwin et UNIX-like
 *  - define TIMEOUT to 15 seconds
 *  - flush all buffer for Emacs compatibility
 *
 *  18 avr 2008
 *  - update with define VERSION
 *
 *  15 jan 2008:
 *  - use 4 chars to tell message size
 *
 *  06 nov 2007:
 *  - client updated to cope with modifications of server, that
 *  allows hosts trying to connect to SESAME_server to get the
 *  IP adress of hosts already connected.
 *  - log has become an option (--log)
 *  - bug fixed (required to include <sys/time.h> for Mac 10.3.9, instead of <time.h>)
 *
 *  22 jun 2007:
 *  added log in SESAME_facteur.log
 *  added timeout on receive
 *
 *  07 feb 2007:
 *  added SESAME_INFO
 *
 *  XX nov 2006:
 *  loop on handshake
 *
 *  31 oct 2006:
 *  added if "ERROR : " as beginning of answer,
 *  answer always printed (even if "--silent")
 *<br>
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/time.h>

/*  +-----------------------------------------------+
    |               UNIX or Windows ?               |
    +-----------------------------------------------+   */
#ifdef WIN32                    /* compilation for Windows */
  #include <winsock2.h>
  WSADATA        WSAData;

  void Ciao( int socket ) { if (socket>=0) closesocket(socket); WSACleanup(); }
#else                             /* compilation for UNIX */
  #include <unistd.h>
  #include <netdb.h>
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
/**
 * Close the socket
 * @param socket Socket to close
 */
  void Ciao( int socket ) { if (socket>=0) close(socket); }
#endif

/**
 * When we have modify the file
 */
#define VERSION "5 june 2008"

/**
 * Define the time out
 */
#define TIMEOUT 15

/**
 * Port used for communicate
 */
#define SESAME_PORT 1501

/**
 * Maximum message size we can send
 */
#define MAX_MESSAGE 9999

/*  +-----------------------------------------------+
    |                   Functions                   |
    +-----------------------------------------------+   */

/**
 * Write the message if verbous!=1
 * It can be used like printf
 * Exemple : ecrire(verbous, "toto %d", i);
 */
void ecrire(int verbous, char *args, ...) {
  if (verbous) {
    va_list args_list;

    va_start(args_list, args);
    vprintf(args, args_list);
    va_end(args_list);
    fflush(stdout);
  }
}

/**
 * Write the message into log_file
 * It can be used like printf
 * Exemple : fprintf_now(log_file, "toto %d", i);
 */
void fprintf_now(FILE *log_file, char *args, ...) {
  if(log_file!=NULL) {
    va_list args_list;

    va_start(args_list, args);
    vfprintf(log_file, args, args_list);
    va_end(args_list);
    fflush(log_file);
  }
}

/**
 * Quit the server and print the meesage
 * @param message Message to write
 * @param socket Socket to close
 * @param log_file Log file to close
 */
void die(char *message, int socket, FILE *log_file) {
  if(log_file!=NULL) {
    fprintf_now(log_file, message );
    fflush(log_file);
    fclose( log_file );
  }
  printf(message);
  fflush(stdout);
  Ciao(socket);
}

/**
 * Create an socket for the server
 * @param servername DNS name or IP address
 * @param serverport Port number
 * @param log_file Log file
 * @return The socket
 */
int  CreateSocket( char *servername, int serverport, FILE *log_file ) {
  /*  "black box" (=blind) copy from some forgotten source
      to get tcp_socket (see later) */
  int                  rc, tcp_socket;
  struct  hostent*     host;
  struct  sockaddr_in  TCPserver ;

  fprintf_now(log_file, "Recherche de l'adresse IP : " );
  /*  first, check if the specified host exists
      if you supply servername as IP adress,
      no lookup is performed and should be faster */
  host = gethostbyname( servername );
  if( host==NULL ){
    die("ERROR : Unknown host. Check network\n", -1, log_file);
    return -1;
  }

  fprintf_now(log_file, "OK\nCréation du socket : " );
  /*  create the TCP socket ... */
  //AF_INET >> tjs pour le net, SOCK_STREAM >> pour utiliser le protocole TCP
  tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if( tcp_socket < 0 ) {
    die("ERROR : Cannot open tcp socket.\n", -1, log_file);
    return -1;
  }

  /*  define the TCP socket timeout ... */
#ifdef WIN32                    /* on compile pour Windows */
  int rcvtimeo = TIMEOUT*1000 ;
  if (setsockopt( tcp_socket , SOL_SOCKET , SO_RCVTIMEO , (const char *)&rcvtimeo , sizeof(rcvtimeo)) < 0){
    die("ERROR : can't define the TCP socket timeout\n", tcp_socket, log_file);
    return -1;
  }
#else
  struct timeval      timeout;
  timeout.tv_sec  = TIMEOUT;
  timeout.tv_usec  = 0;
  if (setsockopt(tcp_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
    die("ERROR : can't define the TCP socket timeout\n", tcp_socket, log_file);
    return -1;
  }
#endif
  TCPserver.sin_family = host->h_addrtype;
  memcpy((char *) &TCPserver.sin_addr.s_addr, host->h_addr_list[0], host->h_length);
  TCPserver.sin_port = htons( serverport );

  fprintf_now(log_file, "OK\nLiaison du socket : " );
  //Linking socket with the address
  bind(tcp_socket,(struct sockaddr *)&TCPserver,sizeof(TCPserver));

  fprintf_now(log_file, "OK\nConnection au socket : " );
  /*  connect to TCP server */
  rc = connect( tcp_socket, (struct sockaddr *) &TCPserver, sizeof(TCPserver) );
  if( rc < 0 ) {
    die("ERROR : Remote server not connecting. Is it running ?\n", tcp_socket, log_file);
    return -1;
  }
  fprintf_now(log_file, "OK\n" );

  /*  OK, got tcp_socket that is a file like in
      "in UNIX everything is a file" to read and write to */

  return tcp_socket;
}





/*  +-----------------------------------------------+
    |                  Main program                 |
    +-----------------------------------------------+   */
/**
 * Main program
 * - Create socket
 * - Send message
 * - Listen server answer
 */
int main ( int argc, char* argv[] ) {
  int              rc, tcp_socket=-1, msg_length, answ_length;
  int              verbous = 1;
  int              debug = 0;
  int              pret = 0;
  int              handshake = 0;
  char*            remote_server;
  char*            message;
  char*            option;
  char             tcp_buffer[16] = "";
  char             answer[MAX_MESSAGE] = "";

  // Fichier debug
  FILE *log_file=NULL;

#ifdef WIN32
  WSAStartup( MAKEWORD(2,0), &WSAData ); // obligatoire pour le TCP/IP
#endif

  /*  +-----------------------------------------------+
      |              Help and some checks             |
      +-----------------------------------------------+   */
  if( argc<3 || argc>4 ) {
    printf("\n   SESAME_facteur Version : %s\n\n", VERSION);
    printf("usage: %s <SERVER> <MESSAGE> <OPTION> \n", argv[0] );
    printf("<SERVER>  is the IP address of \"SESAME_server\" \n");
    printf("<MESSAGE> is an alphanumerical text without spaces\n");
    printf("<OPTION>  is an optional argument:\n");
    printf("   --silent : to print only error messages \n");
    printf("   --debug  : to print all exchanges with SESAME_server \n");
    printf("   --log    : to log in SESAME_facteur.log all exchanges with SESAME_server \n\n");
    fflush(stdout);
    return 0;
  }

  remote_server  = argv[1];
  message        = argv[2];
  option         = argv[3];

  /*  the command comming from Yorick and destined to SESAME_travailleur */
  msg_length = strlen( message );
  if( msg_length > MAX_MESSAGE ) {
    die("ERROR : Message too large\n", -1, log_file);
    return 0;
  }

  /*  silent, verbous ou debug mode : */
  if (argc == 4) {
    if      ( strcmp(option,"--silent") == 0 ) verbous = 0;
    else if ( strcmp(option,"--debug" ) == 0 ) debug = 1;
    else if ( strcmp(option,"--log" ) == 0 ) log_file=fopen("SESAME_facteur.log","w");
    else printf("WARNING : Unknown option %s\n", option );
    fflush(stdout);
  }

  fprintf_now(log_file, "---- Debut ----\n\n" );

  /*  +-----------------------------------------------+
      |         Trying to connect to the server       |
      +-----------------------------------------------+   */
  while ( !pret && handshake<3 ){

    handshake++;
    fprintf_now(log_file,  "Connection attempt #%i\n", handshake );
    ecrire( debug, "DEBUG : Connection attempt #%i\n", handshake );

    /*      open a TCP socket to the server */
    tcp_socket = CreateSocket( remote_server, SESAME_PORT, log_file );
    if(tcp_socket>0){
      fprintf_now(log_file,  "TCP link to %s\n", remote_server );
      ecrire( debug, "DEBUG : TCP link to %s\n", remote_server);

      /*      send "SESAME_OPEN" plus the length of the following
	      message to SESAME_server */
      sprintf( tcp_buffer, "SESAME_OPEN%4d", msg_length );
      fprintf_now(log_file, "\nsending %s\n", tcp_buffer );
      ecrire( debug,  "DEBUG : sending %s\n", tcp_buffer );
      send( tcp_socket, tcp_buffer, 15, 0 );

      /*      Read if SESAME_server is ready */
      fprintf_now(log_file, "\nwaiting for SESAME_PRET\n" );
      rc = recv( tcp_socket, tcp_buffer, 15, 0 );
      tcp_buffer[rc] = '\0';
      fprintf_now(log_file,  "received %s\n", tcp_buffer );
      ecrire( debug, "DEBUG : received %s\n", tcp_buffer );
      if (rc < 0) printf("WARNING : SESAME_server doesn't answer.\n");
      else if ( strncmp(tcp_buffer,"SESAME_CLOS",11)==0 ) {
	answ_length = strtol( tcp_buffer+11, NULL, 0 );
	if ( answ_length>0 ) {
	  rc = recv( tcp_socket, answer, answ_length, 0 );
	  answer[rc] = '\0';
	  if( rc > 0 ) ecrire( verbous, answer );
	}
	else
	  printf("ERROR : SESAME_server is running but used by somebody else.\n");
	fflush(stdout);
	Ciao(tcp_socket);
	handshake=3;
	return 0;
      }
      else if ( strcmp(tcp_buffer,"SESAME_PRET")==0 ) pret = 1; // c'est bon
      else {
	fprintf_now(log_file,  "bad answer, re-sending SESAME_OPEN\n" );
	ecrire( debug, "DEBUG : bad answer, re-sending SESAME_OPEN\n" );
	Ciao(tcp_socket);
      }
    }else
      return 0;

  }

  if ( !pret ) {
    printf("ERROR : Unable to connect to %s after %1d attempts.\n", remote_server, handshake);
    Ciao(tcp_socket);
    return 0;
  }

  fprintf_now(log_file, "\nConnected to %s\n", remote_server );
  ecrire( debug,  "DEBUG : Connected to %s\n", remote_server );


  /*  +-----------------------------------------------+
      |               Hey-ho, let's go                |
      +-----------------------------------------------+   */

  /*  Send the real message to server */
  fprintf_now(log_file, "\nsending %s\n", message );
  ecrire( debug,  "DEBUG : sending %s\n", message );
  send( tcp_socket, message, msg_length, 0 );


  /*  Read "SESAME_FINI" plus the length of the following answer.
      If no answer, ... tough. works most of the times though */
  fprintf_now(log_file, "\nwaiting for SESAME_INFO or SESAME_FINI\n" );
  rc = recv( tcp_socket, tcp_buffer, 15, 0 );
  tcp_buffer[rc] = '\0';
  fprintf_now(log_file,  "received %s\n", tcp_buffer );
  ecrire( debug, "DEBUG : received %s\n", tcp_buffer );

  while (  (rc > 0)  && (strncmp(tcp_buffer,"SESAME_INFO",11)==0) ) {
    answ_length = strtol( tcp_buffer+11, NULL, 0 );
    if ( answ_length>0 ) {
      fprintf_now(log_file, "\nwaiting buffer_info...\n" );
      rc = recv( tcp_socket, answer, answ_length, 0 );
      fprintf_now(log_file, "received buffer_info\n" );
      answer[rc] = '\0';
      if( rc > 0 ) ecrire( verbous, answer );
    }
    else ecrire( verbous, "WARNING : No answer !\n" );
    fprintf_now(log_file, "\nwaiting for SESAME_INFO or SESAME_FINI\n" );
    rc = recv( tcp_socket, tcp_buffer, 15, 0 );
    fprintf_now(log_file,  "received %s\n", tcp_buffer );
    ecrire( debug, "DEBUG : received %s\n", tcp_buffer );
  }

  if (  (rc < 0)  || (strncmp(tcp_buffer,"SESAME_FINI",11)) ) {
    die("WARNING : Message sent. SESAME_server didn't confirm !\n", tcp_socket, log_file);
    return 0;
  }

  answ_length = strtol( tcp_buffer+11, NULL, 0 );
  fprintf_now(log_file,  "answer length = %d\n", answ_length );
  ecrire( debug, "DEBUG : answer length = %d\n", answ_length );

  /*  Read the final answer from SESAME_travailleur on TCP */
  if ( answ_length>0 ) {
    fprintf_now(log_file, "\nwaiting for buffer_fin\n" );
    rc = recv( tcp_socket, answer, answ_length, 0 );
    fprintf_now(log_file, "received buffer_fin : %s\n" , answer );
    answer[rc] = '\0';
    if( rc > 0 ) {
      if ( strncmp(answer,"ERROR",5) == 0 )
	printf(answer);
      else
	ecrire( verbous, answer );
    }
  }
  else ecrire( verbous, "WARNING : No answer !\n" );


  /*  +-----------------------------------------------+
      |                    Bye-bye                    |
      +-----------------------------------------------+   */
  fflush(stdout);
  Ciao( tcp_socket);

  fprintf_now(log_file, "\n---- Sortie normale ----\n" );
  if( log_file )
    fclose(log_file);
  return 1;
}

int SESAME_facteur ( int argc, char* argv[] ) {
  return main(argc, argv);
}
