#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#define NUMSERVER 4		/* numero di server partecipanti */
#define TIMEOUT 1800		/* time out per la fase di aggiornamento */
#define ATTESA_MAX 180		/* time out per select */
#define max_tent 2		/* cicli di messaggi datagram nella fase finale */

char server_name[NUMSERVER][80];	/*nomi dei server partecipanti */
int agg = 0;			/* agg=1 fase di aggiornamento attiva */
int rich_guasti[NUMSERVER];	/* stato dei server */
int sped_guasti[NUMSERVER];
char final_vet[NUMSERVER + 1];
int pid[NUMSERVER], pid2[NUMSERVER];	/* int o pid_t */
int cont_sped = 0, cont_ric = 0;
int my_number;			/* Informazioni sul server locale */
char *my_name;
char nome_file_tgz[80];

/* sock_gets, sock_write, sock_puts, sock_read, e' codice public domain scritto da 
   Vic Metcalfe (vic@brutus.tlug.org); Ho apportato alcune modifiche */

/* This function reads from a socket, until it recieves a linefeed
   character.  It fills the buffer "str" up to the maximum size "count".
   This function will return -1 if the socket is closed during the read
   operation.
   Note that if a single line exceeds the length of count, the extra data
   will be read and discarded!  You have been warned. */

int
sock_gets (sockfd, str, count)
     int sockfd;
     char *str;
     size_t count;
{
  int bytes_read;
  int total_count = 0;
  char *current_position;
  char last_read = 0;

  current_position = str;
  while (last_read != 10)
    {
      bytes_read = read (sockfd, &last_read, 1);
      if (bytes_read <= 0)
	{
/* The other side may have closed unexpectedly */
/* Is this effective on other platforms than linux? */
	  return -1;
	}
      if ((total_count < count) && (last_read != 10) && (last_read != 13))
	{
	  current_position[0] = last_read;
	  current_position++;
	  total_count++;
	}
    }
  if (count > 0)
    current_position[0] = 0;
  /* Riconoscimento riga vuota con \n */
  if (current_position == str)
    return 1;
  else
    return total_count;
}


/* This is just like the write() system call, accept that it will
   make sure that all data is transmitted. */

int
sock_write (sockfd, buf, count)
     int sockfd;
     char *buf;
     size_t count;
{
  size_t bytes_sent = 0;
  int this_write;
  while (bytes_sent < count)
    {
      do
	{
	  this_write = write (sockfd, buf, count - bytes_sent);
	  if ((this_write) < 0)
	    perror ("write");
	}
      while ((this_write < 0) && (errno == EINTR));
      if (this_write <= 0)
	return this_write;
      bytes_sent += this_write;
      buf += this_write;
    }
  return count;
}

/* This function writes a character string out to a socket.  It will 
   return -1 if the connection is closed while it is trying to write. */

int
sock_puts (sockfd, str)
     int sockfd;
     char *str;
{
  return sock_write (sockfd, str, strlen (str));
}

/* This is just like the read() system call, accept that it will make
   sure that all your data goes through the socket. */

int
sock_read (sockfd, buf, count)
     int sockfd;
     char *buf;
     size_t count;
{
  size_t bytes_read = 0;
  int this_read;

  while (bytes_read < count)
    {
      do
	this_read = read (sockfd, buf, count - bytes_read);
      while ((this_read < 0) && (errno == EINTR));
      if (this_read < 0)
	return this_read;
      else if (this_read == 0)
	return bytes_read;
      bytes_read += this_read;
      buf += this_read;
    }
  return count;
}

/* Legge da un file i nomi dei server partecipanti.I nomi dei server sono scritti 
   nel file server.txt */

int
read_name (char *file_name)
{
  char *buf;
  int i = 0, j = 0, fd;
  if ((fd = open (file_name, O_RDONLY)) < 0)
    {
      printf ("Errore apertura file %s \n", file_name);
      exit (1);
    }
  while ((j < NUMSERVER) && (read (fd, buf, 1) > 0))
    {
      switch (*buf)
	{
	case '\n':
	  server_name[j][i] = '\0';
	  /* posizione del server locale nel file server.txt */
	  if (strcmp (my_name, server_name[j]) == 0)
	    my_number = j;
	  j++;
	  i = 0;
	default:
	  if ((*buf) != ('\n'))
	    server_name[j][i++] = *buf;
	}
    }
  close (fd);
}

/* Instaura una connessione TCP con il server "nomeremoto" 
   per richiedere il file tgz dei nuovi messaggi */

int
ricevi_file (char *nomeremoto)
{
  int fromlen, sd, fd, j;
  char nome_file[80];
  char buf[256];
  int count = 256;
  struct hostent *host;
  struct sockaddr_in rem1_indirizzo;
  memset ((char *) &rem1_indirizzo, 0, sizeof (struct sockaddr_in));
  rem1_indirizzo.sin_family = AF_INET;
  host = gethostbyname (nomeremoto);
  if (host == NULL)
    {
      printf ("%s not found in /etc/hosts \n", nomeremoto);
      exit (1);
    }
  rem1_indirizzo.sin_addr.s_addr = ((struct in_addr *) (host->h_addr))->s_addr;
  rem1_indirizzo.sin_port = 3001;
  sd = socket (AF_INET, SOCK_STREAM, 0);
  if (sd < 0)
    {
      perror ("Apertura socket");
      exit (1);
    }
  if (connect (sd, (struct sockaddr *) &rem1_indirizzo, sizeof (struct sockaddr)) < 0)
    {
      perror ("errore in connect");
      return -1;
    }
  sprintf (nome_file, "%s%s", nomeremoto, ".tgz");
  if ((fd = open (nome_file, O_CREAT | O_EXCL)) < 0)
    {
      printf ("File %s error", nome_file);
      exit (1);
    }
  while ((sock_read (sd, buf, count)) == count)
    {
      for (j = 0; j < count; j++)
	{
	  if (buf[j] != EOF)
	    write (fd, buf + j, 1);
	  else
	    {
	      close (fd);
	      close (sd);
	      return (1);
	    }
	}
    }
  close (fd);
  close (sd);
  return 0;
}

/* Compatta i nuovi messaggi ricevuti dal server locale 
   in un file tgz (che verra' poi spedito ai server richiedenti) */

void
crea_file (char *nome_file)
{
  int stato;

  switch (fork ())
    {
    case -1:
      perror ("fork");
      exit (1);
    case 0:
      chdir ("dir_nuovi_messaggi");
      sprintf (nome_file_tgz, "%s%s%s", "/temp/", nome_file, ".tgz");
      /* Sposta i nuovi messaggi in un file compresso */
      execl ("/bin/tar", "tar", "-c", "--compress", "--remove-files", "--file", nome_file_tgz, "*.*", (char *) 0);
    default:
      wait (&stato);
      chdir ("..");
    }
}

/* Spedisce un file sulla socket "socket" */

int
spedisci_file (char *nomefile, int socket, int id)
{
  int fd, nwrite;
  int count;
  char buf[256];
  fd = open (nomefile, O_RDONLY);	/* da sistemare */
  if (fd < 0)
    {
      printf ("File errore");
      return (-1);
    }
  /* se il file in questione e' quello dei nuovi messaggi, che deve essere  spedito agli 
     altri server, la dimensione concordata di buf e' 256 altrimenti se il file e' un  post da 
     spedire ad un client assumo la dimesione di buf uguale a 1 */
  if (id == 0)
    count = 256;
  else
    count = 1;
  while ((read (fd, buf, count)) > 0)
    {
      nwrite = sock_write (socket, buf, count);
      if (nwrite != count)
	{
	  close (fd);
	  return -1;
	}
    }
  close (fd);
  return 1;
}


/* Spedisce sulla socket "socket" il contenuto della directory "name".
   (per segnalare ad un client i messaggi disponibili) */

int
read_dir (int socket, char *name)
{
  int num_file = 0;
  int k = 1;
  char nome_post[80];

  DIR *dir;
  struct dirent *dd;
  dir = opendir (name);
  while (((dd = readdir (dir)) != NULL) && (k > 0))
    {
      if ((strcmp (".", dd->d_name) != 0) && (strcmp ("..", dd->d_name) != 0))
	{
	  sprintf (nome_post, "%s%s", dd->d_name, "\n");
	  k = sock_puts (socket, nome_post);
	  num_file++;
	}
    }
  closedir (dir);
  if (k > 0)
    return (num_file);
  else
    return (k);
}


/* per il vettore rich_guasti 1:OK 0:guasto */
/* per il vettore sped_guasti 1:OK 0:guasto 2:file in ricezione */

void
gestore_sped (int signo)	/* gestione spedizione dei dati agli altri server */
{
  int stato, pid_child, j;
  pid_child = wait (&stato);
  cont_sped++;
  j = 0;
  while (pid[j] != pid_child)
    j++;
  stato = (((stato) & 0xff00) >> 8);
  sped_guasti[j] = stato;
}

void
gestore_ric (int signo)		/* gestione richiesta dei dati agli altri server */
{
  int stato, pid_child, j;
  pid_child = wait (&stato);
  cont_ric++;
  j = 0;
  while (pid[j] != pid_child)
    j++;
  stato = (((stato) & 0xff00) >> 8);
  rich_guasti[j] = stato;
  if (stato == 0)
    {
      sped_guasti[j] = 0;
      cont_sped++;
    }
  if (cont_ric == (NUMSERVER))
    fase_finale ();
}

void
add_new_mex (char *nomefile)
{
  int stato;

  switch (fork ())
    {
    case -1:
      perror ("fork");
      exit (1);
    case 0:
      chdir ("ng_x");
      sprintf (nome_file_tgz, "%s%s%s", "/temp/", nomefile, ".tgz");
      execl ("/bin/tar", "tar", "-x", nome_file_tgz, (char *) 0);
    default:
      wait (&stato);
      unlink (nome_file_tgz);
      chdir ("..");
    }
}

int
fase_finale ()
{
  struct sockaddr_in mio_indirizzo, rem_indirizzo;
  struct hostent *host;
  int fs, attendi, cont, i, j, k, tent;
  char final_vet2[NUMSERVER];
  struct timeval
    {
      unsigned long tv_sec;
      long tv_usec;
    }
  tv;
  int retval;
  fd_set rfds;

  memset ((char *) &mio_indirizzo, 0, sizeof (struct sockaddr_in));

  mio_indirizzo.sin_family = AF_INET;
  mio_indirizzo.sin_port = htons (31337);

  fs = socket (AF_INET, SOCK_DGRAM, 0);

  if (bind (fs, (struct sockaddr *) &mio_indirizzo, sizeof (struct sockaddr_in)) < 0)
    {
      perror ("bind");
      exit (1);
    }

  attendi = 0;
  do
    {
      for (i = 0; i < NUMSERVER; i++)
	if (sped_guasti[i] == 2)
	  attendi = 1;
    }
  while (attendi == 1);

/* 
   final_vet[i]=0 il server non ha ricevuto dati dalla macchina i e la macchina i non ha richiesto dati
   final_vet[i]=1 il server non ha ricevuto dati dalla macchina i ma la macchina i ha richiesto dati
   final_vet[i]=2 il server ha ricevuto dati dalla macchina i e la macchina i non ha richiesto dati
   final_vet[i]=3 il server ha ricevuto dati dalla macchina i e la macchina i ha richiesto dati
 */

  for (i = 0; i < NUMSERVER; i++)
    {
      switch (rich_guasti[i])
	{
	case 0:
	  if (sped_guasti[i] == 0)
	    final_vet[i] = '0';
	  else
	    final_vet[i] = '1';
	case 1:
	  if (sped_guasti[i] == 0)
	    final_vet[i] = '2';
	  else
	    final_vet[i] = '3';
	}
    }
  final_vet[i] = '\0';
  rem_indirizzo.sin_port = 31337;
  for (tent = 0; tent < max_tent; tent++)
    {
      for (j = 0; j < NUMSERVER; j++)
	{
	  if (j != my_number)
	    {
	      host = gethostbyname (server_name[j]);
	      rem_indirizzo.sin_addr.s_addr = ((struct in_addr *) (host->h_addr))->s_addr;
	      if (sendto (fs, final_vet, NUMSERVER + 1, 0, &rem_indirizzo, sizeof (struct sockaddr_in)) == -1)
		{
		  fprintf (stderr, "Errore nell'invio datagram");
		}
	    }
	}
      for (j = 0; j < (NUMSERVER - 1); j++)
	{
	  FD_ZERO (&rfds);
	  FD_SET (fs, &rfds);
	  tv.tv_sec = ATTESA_MAX;
	  tv.tv_usec = 0;
	  retval = select (fs + 1, &rfds, NULL, NULL, &tv);
	  if (retval)
	    {
	      if (recvfrom (fs, final_vet2, NUMSERVER + 1, 0, &rem_indirizzo, sizeof (struct sockaddr_in)) < 0)
		{
		  perror ("recvfrom");
		  exit (1);
		}
	      else
		{
		  for (k = 0; k < NUMSERVER; k++)
		    {
		      if ((final_vet[i] == '3') && (final_vet2[i] == '3'))
			final_vet[i] == '3';
		      else
			final_vet[i] = '0';
		    }
		}
	    }
	  for (i = 0; i < NUMSERVER; i++)
	    {
	      if (final_vet[i] == '3')
		add_new_mex (server_name[i]);
	    }
	}
    }
}

/* Crea NUMSERVER-1 figli incaricati di richiedere i file 
   dei nuovi messaggi dai server remoti */

void
richiedi_dati ()
{
  int i;

  signal (SIGCHLD, gestore_ric);
  for (i = 0; i < NUMSERVER; i++)
    {
      if (i != my_number)
	{
	  switch (pid[i] = fork ())
	    {
	    case -1:
	      perror ("fork");
	      exit (1);
	    case 0:
	      if ((ricevi_file (server_name[i])) > 0)
		exit (1);
	      else
		exit (0);
	    default:
/* continua con il ciclo */
	    }
	}
    }
}

/* Crea un demone che attende sulla porta 3000 le connessioni dai client */

void
demone_client ()
{
  int sd1, ns1, prior, id_client, fromlen, connected = 1;
  long timevar;
  char *hostname;
  char buffer[1024];
  char nomepost[80];
  int fd_post;

  struct sockaddr_in myaddr_in;	/* local socket address */
  struct sockaddr_in peeraddr_in;	/* remote socket address */
  struct hostent *hp;
  memset ((char *) &peeraddr_in, 0, sizeof (struct sockaddr_in));
  memset ((char *) &myaddr_in, 0, sizeof (struct sockaddr_in));
  myaddr_in.sin_family = AF_INET;
  myaddr_in.sin_port = htons (3000);	/* definizione della porta  */

  fromlen = sizeof (struct sockaddr_in);

/* Wildcard Address... */

  myaddr_in.sin_addr.s_addr = INADDR_ANY;

/* Socket d'ascolto */

  sd1 = socket (AF_INET, SOCK_STREAM, 0);
  if (sd1 < 0)
    {
      perror ("Apertura socket");
      exit (1);
    }

/* Collegamento della socket all'indirizzo */

  if (bind (sd1, (struct sockaddr *) &myaddr_in, sizeof (struct sockaddr_in)) < 0)
    {
      perror ("bind");
      exit (1);
    }

/* Coda d'ascolto */

  if (listen (sd1, 5) == -1)
    {
      perror ("listen");
      exit (1);
    }

  signal (SIGCHLD, SIG_IGN);
  switch (fork ())
    {
    case -1:
      perror ("fork");
      exit (1);
    case 0:			/* figlio - demone */
      setsid ();
      printf ("Demone client \n");
      signal (SIGCHLD, SIG_IGN);

      for (;;)
	{
	  ns1 = accept (sd1, &peeraddr_in, &fromlen);
	  if (ns1 < 0)
	    {
	      if (errno == EINTR)
		{
		  perror ("accept, continuo");
		  continue;
		}
	      else
		exit (1);
	    }

	  switch (fork ())
	    {
	    case -1:
	      perror ("fork");
	      exit (1);
	    case 0:		/* esecuzione server */
	      close (sd1);
	      if (sock_puts (ns1, "Guarapito's little news server ready - posting OK.\n") == -1)
		{
		  connected = 0;
		  exit (2);
		}
	      else
		connected = 1;
	      while (connected)
		{
/* Legge input dal client. Considero comandi scritti in minuscolo (eventualmente si 
   puo' aggiungere una funzione di conversione  */
		  if (sock_gets (ns1, buffer, 1024) < 0)
		    {
		      connected = 0;
		      exit (2);
		    }

		  else if (strcmp ("quit", buffer) == 0)
		    {
		      sock_puts (ns1, "Goodbye \n");
		      close (ns1);
		      connected = 0;
		      exit (2);
		    }

		  else if (strcmp ("list", buffer) == 0)
		    {
		      /* Da implementare. Si considera una solo NG e quindi un'unica directory */
		      exit (2);
		    }

		  else if (strcmp ("group", buffer) == 0)
		    {
		      /* Invio dei nomi dei post */
		      if ((read_dir (ns1, "/guarapito/ng_x")) < 0)
			{
			  connected = 0;
			  exit (2);
			}
		    }

		  else if (strcmp ("post", buffer) == 0)
		    {
		      chdir ("dir_nuovi_messaggi");
		      /* Ricezione nuovo articolo */
		      do
			{
			  /* Creazione nome del post */
			  time (&timevar);
			  sprintf (nomepost, "%d%s", timevar, my_name);
			  fd_post = creat (nomepost, 0644);
			}
		      while (fd_post < 0);	/* per cambiare nome */

		      sock_puts (ns1, "punto per fine file \n");
		      connected = sock_gets (ns1, buffer, 1024);
		      while ((connected) && ((strcmp (".", buffer)) != 0))
			{
			  strcat (buffer, "\n");
			  write (fd_post, buffer, strlen (buffer));
			  connected = sock_gets (ns1, buffer, 1024);
			}
		      close (fd_post);
		      chdir ("..");
		    }

		  else if (strncmp ("article", buffer, 7) == 0)
		    {
		      /* Invio body del messaggio selezionato */
		      chdir ("ng_x");
		      connected = spedisci_file (buffer + 8, ns1, 1);
		      chdir ("..");
		      if (connected <= 0)
			exit (2);
		    }
		}
	      exit (0);
	    default:
	      close (ns1);
	    }
	}
    default:			/* processo iniziale */
    }
}

/* Fa partire, allo scadere del TIMEOUT, la fase coordinata di aggiornamento dati */

void
richiedi_coord ()
{
  if (agg != 1)
    {
      agg = 1;
      richiedi_dati ();
    }
}

main (int argc, char **argv)
{

  int sd, ns, prior, id_client, i, fromlen, connected = 1;
  char *hostname;

  struct sockaddr_in myaddr_in;	/* local socket address */
  struct sockaddr_in peeraddr_in;	/* remote socket address */
  struct hostent *hp, *myhost;
  memset ((char *) &peeraddr_in, 0, sizeof (struct sockaddr_in));
  memset ((char *) &myaddr_in, 0, sizeof (struct sockaddr_in));
  myaddr_in.sin_family = AF_INET;
  myaddr_in.sin_port = htons (3001);	/* definizione della porta  */


  fromlen = sizeof (struct sockaddr_in);
/* Wildcard Address... */

  myaddr_in.sin_addr.s_addr = INADDR_ANY;

/* Socket d'ascolto */
  myhost = gethostbyname ("localhost");
  my_name = myhost->h_name;

  printf ("Il programma gira sul server: %s \n", my_name);

  rich_guasti[my_number] = 1;
  sped_guasti[my_number] = 1;
  pid[my_number] = -1;
  pid2[my_number] = -1;

  read_name ("server.txt");

  sd = socket (AF_INET, SOCK_STREAM, 0);
  if (sd < 0)
    {
      perror ("Apertura socket");
      exit (1);
    }

/* Collegamento della socket all'indirizzo */

  if (bind (sd, (struct sockaddr *) &myaddr_in, sizeof (struct sockaddr_in)) < 0)
    {
      perror ("bind");
      exit (1);
    }

/* Coda d'ascolto */

  if (listen (sd, 5) == -1)
    {
      perror ("listen");
      exit (1);
    }

  switch (fork ())
    {

    case -1:
      perror ("fork");
      exit (1);
    case 0:			/* figlio - demone */
      setsid ();

      for (;;)
	{
	  signal (SIGALRM, richiedi_coord);
	  alarm (TIMEOUT);

	  if ((ns = accept (sd, &peeraddr_in, &fromlen)) < 0)
	    {
	      if (errno == EINTR)
		{
		  perror ("accept, continuo");
		  continue;
		}
	      else
		exit (1);
	    }
	  /* Rilevazione nome host remoto */
	  hp = gethostbyaddr ((char *) &peeraddr_in.sin_addr, sizeof (peeraddr_in.sin_addr), peeraddr_in.sin_family);
	  if (hp == NULL)
	    hostname = inet_ntoa (peeraddr_in.sin_addr);
	  else
	    hostname = hp->h_name;
/* Rilevazione posizione dell host remoto nel file server.txt */
	  prior = 0;
	  id_client = 1;
	  while (((id_client = strcmp (server_name[prior], hostname)) != 0) && (prior < NUMSERVER))
	    prior++;

	  if (id_client != 1)
	    {
	      if (agg == 0)
		{
		  agg = 1;
		  crea_file (my_name);
		  for (i = 0; i < NUMSERVER; i++)
		    {
		      if (i != my_number)
			{
			  sped_guasti[i] = 0;
			  rich_guasti[i] = 0;
			}
		    }
		  cont_sped = 1;
		  cont_ric = 1;
		  signal (SIGCHLD, gestore_ric);
		  richiedi_dati ();
		}
	      signal (SIGCHLD, gestore_sped);
	      sped_guasti[prior] = 2;
	      switch (pid2[prior] = fork ())
		{
		case -1:
		  perror ("fork");
		  exit (1);
		case 0:	/* esecuzione server */
		  close (sd);
		  chdir ("\temp");
		  if ((spedisci_file (nome_file_tgz, ns, 0)) > 0)
		    {
		      chdir ("..");
		      close (ns);
		      exit (1);
		    }
		  else
		    {
		      chdir ("..");
		      close (ns);
		      exit (0);
		    }
		default:
		  close (ns);
		}
	    }
	  else			/* accesso non autorizzato */
	    close (ns);
	}
    default:			/* processo iniziale */
      demone_client ();
      printf ("Processo iniziale fine \n");
      exit (0);
    }
}