/* serv_4_FSM.c */

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>

#define PORT_NUMBER 60000
#define REQUETES 1 /* Empêche plus d'une connexion à la fois */
#define TAMPON 100

enum liste_des_etats {
  ETAT0_initialisation,
  ETAT1_attente_connexion,
  ETAT2_attente_donnee_entreante
} etat_serveur=ETAT0_initialisation;


/* sortie avec un message d'erreur contextualisé */
void erreur(char *msg) {
  perror(msg);
  exit(1);
}

/* variable persistentes du serveur */
int sock_serveur, sock_client, port_number=PORT_NUMBER;
const int one = 1, zero=0;
struct sockaddr_in sockaddr_serveur, sockaddr_client;
unsigned int taille_client = sizeof(sockaddr_client);
char buffer[TAMPON];
char *ack="Bonjour !\n";
char *eot="Au revoir !\n";

void serveur() {
  /* variables locales du serveur */
  int len, flags;
  char *env_port;

  switch(etat_serveur) {
/********************************************************/
    case ETAT0_initialisation :
      /* gestion du numéro du port */
      env_port = getenv("GHDL_TCPPORT");
      if (env_port != NULL) {
        port_number = atol(env_port);
        if ((port_number < 1024) || (port_number > 65535))
          erreur("Mauvais numéro de port");
      }

      /* Création d'une socket TCP */
      if ((sock_serveur = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
        erreur("Echec lors de la création de la socket");

      /* Fabrication de la structure sockaddr_in du serveur */
      memset(&sockaddr_serveur, 0, sizeof(sockaddr_serveur));
      sockaddr_serveur.sin_family = AF_INET;
      sockaddr_serveur.sin_addr.s_addr = htonl(INADDR_ANY);
      sockaddr_serveur.sin_port = htons(port_number);

#ifdef SO_REUSEADDR
      if (setsockopt(sock_serveur, SOL_SOCKET,
                SO_REUSEADDR, &one, sizeof(one)) < 0)
        erreur("setsockopt(SO_REUSEADDR)");
#endif
#ifdef SO_REUSEPORT
      if (setsockopt(sock_serveur, SOL_SOCKET,
                 SO_REUSEPORT, &one, sizeof(one)) < 0)
        erreur("setsockopt(SO_REUSEPORT)");
#endif

      /* Branche la socket */
      if (bind(sock_serveur, (struct sockaddr *) &sockaddr_serveur,
                                sizeof(sockaddr_serveur)) < 0)
        erreur("Echec de bind() du serveur");

      /* Ecoute la socket */
      if (listen(sock_serveur, REQUETES) < 0)
        erreur("Echec de listen() du serveur");

      /* Débloque la socket */
      flags = fcntl(sock_serveur, F_GETFL);
      fcntl(sock_serveur, F_SETFL, flags | O_NONBLOCK);

      printf("Attente sur le port %d\n", port_number);

      etat_serveur=ETAT1_attente_connexion;

/********************************************************/
    case ETAT1_attente_connexion:
      /* Attente d'une connexion */
      if ((sock_client = accept(sock_serveur,
         (struct sockaddr *) &sockaddr_client, &taille_client)) < 0) {
        if ((errno == EWOULDBLOCK) || (errno == EAGAIN))
          return;
        else
          erreur("accept() : Echec d'attente de connexion");
      }

      fprintf(stdout, "Client connecté de %s\n",
                         inet_ntoa(sockaddr_client.sin_addr));

      /* Petit message de bienvenue */  
      len = strlen(ack);
      if (send(sock_client, ack, len, 0) != len)
        erreur("Echec d'envoi du message de bienvenue");

      etat_serveur= ETAT2_attente_donnee_entreante;

/********************************************************/
    case ETAT2_attente_donnee_entreante :
      /* attend quelque chose */
      if (recv(sock_client, buffer, TAMPON, MSG_DONTWAIT) < 0) {
        if ((errno == EWOULDBLOCK) || (errno == EAGAIN))
          return;
        else
          erreur("Echec de réception du message du client");
      }

      /* Adieu, serveur cruel ! */  
      len = strlen(eot);
      if (send(sock_client, eot, len, 0) != len)
        erreur("Echec d'envoi du message d'adieu");

      close(sock_client);
      printf("Fermeture de la connexion\n");

      etat_serveur=ETAT1_attente_connexion;
      return;

/********************************************************/
    default: erreur("état interdit !");
  }
}

/* fonction principale qui "travaille" (fait tourner un tiret)
  et appelle le serveur périodiquement */
int main(int argc, char *argv[]) {
  while (1) {
    write(STDERR_FILENO, "-\x0D", 2); /* affiche la progression */
    usleep(100*1000);   /* attente de 100ms */
    serveur();

    write(STDERR_FILENO, "/\x0D", 2);  usleep(100*1000);
    serveur();

    write(STDERR_FILENO, "|\x0D", 2);  usleep(100*1000);
    serveur();

    write(STDERR_FILENO, "\\\x0D", 2);  usleep(100*1000);
    serveur();
  }
}
