#include <stdio.h>
#include <unistd.h>
#include <string.h> //per le funzioni di parsing sulle stringhe
#include <netdb.h>// per la gethostbyname
#include <stdlib.h> //per la malloc
#include <signal.h>
#include <sys/wait.h>
#include <math.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/select.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/stat.h>  // per stat

#include "define.h"


//#define DEBUG 1
#ifdef DEBUG
#undef DEBUG
#endif


double probabilita_errore=0.0002;
int msec_ritardomax=10;

void sig_child (int signo);
/* This function slays dead children, otherwise they become zombies.
 * Unix signals do not queue correctly, so we check in a nonblocking
 * way (WNOHANG) for all our dead children */

void sig_child (int signo)
{
  pid_t pid;

  while ((pid = waitpid (-1, NULL, WNOHANG)) > 0) ;
  if ((signal (SIGCHLD, sig_child)) == SIG_ERR)
    {
		fprintf(stderr,"signal(SIGCHLD,..): Cannot attach signal handler\n");
		fflush(stderr);
	}
  return;
}

int readn(int fd, char *ptr, int nbytes)
{
	int nleft,nread;
#ifdef DEBUG
	int j;
#endif

	nleft=nbytes;
	while(nleft>0)
	{
		do 
		{
			nread=read(fd,ptr,nleft);
		}	while( (nread<0) && (errno==EINTR) );
			
		if(nread<0)
		{	// errore
			char msg[2000];
			sprintf(msg,"readn: errore in lettura [result %d] :",nread);
			perror(msg);
			return(-1);
		}
		else
		{
			if(nread==0) {
				return(0);
			}
		}

#ifdef DEBUG
		printf("R: ");
		for(j=0;j<nread;j++)
			printf("%c",*(ptr+j));
		printf("\n");
#endif

		nleft -= nread;
		ptr   += nread;
	}
return(nbytes);           /* termino */
}

ssize_t writen (int fd, const void *buf, size_t n) 
{
	size_t nleft;
	ssize_t nwritten;
	char *ptr;
#ifdef DEBUG
	int j;
#endif

	ptr = (char*)buf;
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
			if (errno == EINTR)
				nwritten = 0; /* and call write() again*/
			else {
			    fprintf (stderr, "write() failed, Err: %d \"%s\"\n",errno,strerror(errno));
				return(-1); /* error */
			}
		}

#ifdef DEBUG
		printf("W: ");
		for(j=0;j<nwritten;j++)
			printf("%c",*(ptr+j));
		printf("\n");
#endif

		nleft -= nwritten;
		ptr += nwritten;
	}
	return(n);
}


long int attesa(long int msec_ritardo)
{
	struct timeval tv;
	int ris;

	tv.tv_sec=0;
	tv.tv_usec=1000*msec_ritardo;
	do {
		ris=select(0,NULL,NULL,NULL,&tv);
	} while( (ris<0) && (errno==EINTR) );
	return(msec_ritardo);
}

double random_0_1(void)
{
	return( ((double)random())/((double)RAND_MAX)   );
}

// ssize_t writen_con_errore_e_attesa(int fd, char *buf, size_t n) 
ssize_t writen_con_errore(int fd, char *buf, size_t n) 
{
	int ris, i;
	double prob;
	long int msec_ritardo;
	
	for(i=0;i<n;i++)
	{
		prob=random_0_1();
		// printf("prob=%f  probabilita_errore=%f\n", prob, probabilita_errore);
		if(prob<probabilita_errore) 
		{
			fprintf(stderr, "generato errore simulato in scrittura:\n\tprob %f   probabilita_errore %f\n",
					prob, probabilita_errore);
			return(i);  // errore simulato
		}
		else
		{
			
			msec_ritardo = 1L+ (long int)(  (prob=random_0_1())*msec_ritardomax);
			// printf("msec_ritardomax %d prob=%f  msec_ritardo %ld\n", msec_ritardomax, prob, msec_ritardo);
			attesa(msec_ritardo);
			ris=writen(fd,buf+i,1);
			if(ris!=1)
				return(i);	// errore vero
		}
	}
	return(n);
}


void send_error_msg(int clientfd)
{
	ErrorReplyHeader erh;

	memset(&erh, 0, sizeof(ErrorReplyHeader));
	erh.error_code ='E';
	writen_con_errore(clientfd, (char*) &erh, sizeof(ErrorReplyHeader));
}

int get_filesize( char *filename )
{
	FILE *f;
	int pos;
	// int pos0;

	f=fopen(filename,"rb");
	if(!f) {
		char str[1024];
		sprintf(str,"impossibile aprire il file %s: ", filename);
		perror(str);
		return(-1);
	}

	if(fseek(f,0,SEEK_SET)<0) {
		perror("fseek() failed: ");
		return(-1);
	}

	/* per curiosita' 
	pos0=ftell(f);
	if( pos0<0 ) {
		perror("ftell() failed: ");
		return(-1);
	}
	else
		printf("pos0=%d ",pos0);
		*/
	
	if(fseek(f,0,SEEK_END)<0) {
		perror("fseek() failed: ");
		return(-1);
	}
	pos=ftell(f);
	if( pos<0 ) {
		perror("ftell() failed: ");
		return(-1);
	}
	/*
	else
		printf("pos=%d\n",pos);
		*/

	fclose(f);
	f=NULL;
	// errore:	return(pos+1);
	return(pos);
}


int altra_get_filesize( char *filename )
{
        int fd;
        struct stat des;

        fd = open(filename, O_RDONLY, 0);
        if(!fd) {
                char str[1024];
                sprintf(str,"impossibile aprire il file %s: ", filename);
                perror(str);
                return(-1);
        }

        fstat(fd, &des);

		printf("size=%ld \n", des.st_size);
        return(des.st_size);
}


int check_range_request( GetRangeRequestHeader *pgrrh, char *filename, int *pstart, int *pend)
{
	int size;

	if	(	(pgrrh->filename[FILENAMELEN-1]!='\0') || 
			(pgrrh->start[LEN-1]!='\0')  ||
			(pgrrh->end[LEN-1]!='\0')
		)
		return(0);
	else
	{
		strcpy(filename,pgrrh->filename);
		*pstart=atoi(pgrrh->start);
		*pend=atoi(pgrrh->end);
		if	(	(*pstart>=0)&&(*pend>=0)&&((*pend)>=(*pstart))&&
				(((*pend)-(*pstart)+1)<=100)&&
				((size=get_filesize(pgrrh->filename))>=0) &&
				((*pend)<size)
			)
			return(1);
		else
			return(0);
	}
}


char *load_range(char *filename, int start, int end)
{
	FILE *f;
	char *ptr=NULL;

	f=fopen(filename,"rb");
	if(!f) {
		char str[1024];
		sprintf(str,"impossibile aprire il file %s: ", filename);
		perror(str);
		return(NULL);
	}
	ptr=calloc(1,end-start+1);
	if(!ptr) {
		perror("calloc() failed: ");
		return(NULL);
	}
	if(fseek(f,start,SEEK_SET)<0) {
		perror("fseek() failed: ");
		free(ptr);
		ptr=NULL;
		return(NULL);
	}
	if( fread( ptr, 1, end-start+1, f) != (end-start+1) ) {
		perror("fread() failed: ");
		free(ptr);
		ptr=NULL;
		return(NULL);
	}
	fclose(f);
	return(ptr);
}

int make_range_respons( GetRangeReplyHeader *pgrh, int size)
{
	memset(pgrh, 0, sizeof(GetRangeReplyHeader));
	pgrh->respons_code = 'R';
	sprintf(pgrh->bodysize, "%d", size);
	return(1);
}

int gestore_range_request(int clientfd, GetRangeRequestHeader *pgrrh)
{
	int ris;
	char filename[FILENAMELEN];
	GetRangeReplyHeader grreplyh;
	int start,end;
	char *ptr;

	ris=readn(clientfd, ((char*)pgrrh)+1,sizeof(GetRangeRequestHeader)-1);
	if(ris!=(sizeof(GetRangeRequestHeader)-1))
	{
		send_error_msg(clientfd);
		return(1);
	}
	if( ! check_range_request( pgrrh, filename, &start, &end) ) {
		send_error_msg(clientfd);
		return(1);
	}

	printf("Richiesta: nomefile=%s start=%d end=%d\n",filename,start,end);

	if( (ptr=load_range(filename, start, end))==NULL ) {
		send_error_msg(clientfd);
		return(1);
	}
	if( !make_range_respons( &grreplyh, 1+end-start) )
	{
		free(ptr);
		ptr=NULL;
		send_error_msg(clientfd);
		return(1);
	}
	ris=writen_con_errore(clientfd, (char*)(&grreplyh) ,sizeof(GetRangeReplyHeader));
	if( ris!=sizeof(GetRangeReplyHeader) )
	{
		free(ptr);
		ptr=NULL;
		return(1);
	}
	ris=writen_con_errore(clientfd,ptr,1+end-start);
	if( ris!= (1+end-start) )
	{
		printf("Risposta: generato errore, spediti solo %d byte di nomefile=%s (start=%d end=%d)\n",
				ris, filename, start, end);
		free(ptr);
		ptr=NULL;
		return(1);
	}
	else
		printf("Risposta: nomefile=%s start=%d end=%d\n",filename,start,end);

	free(ptr);
	ptr=NULL;
	return(0);
}

int check_size_request( SizeRequestHeader *psrh)
{
	if(psrh->filename[FILENAMELEN-1] != '\0') {
		printf("formato richiesta size errato\n");
		return(0);
	}
	else
		return(1);
}

int make_size_respons( SizeReplyHeader *psrh, int size)
{
	memset(psrh, 0, sizeof(SizeReplyHeader));
	psrh->respons_code = 'S';
	sprintf(psrh->filesize, "%d", size);
	return(1);
}

int gestore_size_request(int clientfd, SizeRequestHeader *psreqh)
{
	int ris,size;
	SizeReplyHeader srh;

	ris=readn(clientfd, ((char*)psreqh)+sizeof(psreqh->request_code) , sizeof(SizeRequestHeader)-sizeof(psreqh->request_code) );
	if(ris!=FILENAMELEN )
	{
		send_error_msg(clientfd);
		return(1);
	}

	if( ! check_size_request( psreqh) ) {
		send_error_msg(clientfd);
		return(1);
	}

	printf("Richiesta: Size of nomefile=%s\n", psreqh->filename);
	fflush(stdout);

	if( (size=get_filesize(psreqh->filename))<0 ) {
		send_error_msg(clientfd);
		return(1);
	}

	// if( !make_size_respons((SizeReplyHeader*)buf,size) )
	if( !make_size_respons( &srh,size) )
	{
		send_error_msg(clientfd);
		return(1);
	}

	// ris=writen_con_errore(clientfd,buf,sizeof(SizeReplyHeader));
	ris=writen_con_errore(clientfd,  (char*) (&srh) ,sizeof(SizeReplyHeader));
	// ris=write(clientfd,  (char*) &srh ,sizeof(SizeReplyHeader));
	if( ris!=sizeof(SizeReplyHeader) )
	{
		printf("Risposta: errore: spediti solo %d byte\n", ris);
		return(1);
	}
	else
		printf("Risposta: Size of nomefile=%s is %d\n",psreqh->filename, size);

	return(0);
}

void stampa_tempo_gestione_richiesta( struct timeval end, struct timeval start)
{
	struct timeval diff;

	if(end.tv_sec==start.tv_sec)
	{
		diff.tv_sec = 0L;
		diff.tv_usec = end.tv_usec-start.tv_usec;
	}
	else // end.tv_sec > start.tv_sec
	{
		if(end.tv_usec>=start.tv_usec)
		{
			diff.tv_sec = end.tv_sec-start.tv_sec;
			diff.tv_usec = end.tv_usec-start.tv_usec;
		}
		else // end.tv_usec < start.tv_usec
		{
			diff.tv_sec = end.tv_sec-start.tv_sec-1L;
			diff.tv_usec = 1000000L - (start.tv_usec-end.tv_usec);
		}
	}
	printf("tempo_gestione_richiesta: %ld msec \n", (1000L*diff.tv_sec)+(diff.tv_usec/1000L) );
}


int gestore(int clientfd)
{
	int ris;
	struct timeval start,end;

	typedef union {
		GetRangeRequestHeader	grrh;
		SizeRequestHeader		srh;
	} Richieste;

	Richieste richiesta;

	gettimeofday(&start,NULL);
	// leggo il tipo di messaggio che arriva
	ris=readn(clientfd,(char*)&richiesta,1);
	if(ris!=1) {
		send_error_msg(clientfd);
		return(1);
	}
	switch(richiesta.grrh.request_code)
	{
	case 'S':	// size
		ris = gestore_size_request(clientfd, (SizeRequestHeader*)&richiesta);
		break;

	case 'R':	// range
		ris = gestore_range_request(clientfd, (GetRangeRequestHeader*)&richiesta);
		break;

	default: // altri casi
		send_error_msg(clientfd);
		ris = 1;
	}
	gettimeofday(&end,NULL);
	stampa_tempo_gestione_richiesta(end,start);
	return(ris);

}

int setup_listening_socket(unsigned short int numero_porta_locale)
{
	int listenfd;
	int OptVal;
	int ris;
	struct sockaddr_in Local;

	printf ("socket()\n");
	listenfd = socket(AF_INET, SOCK_STREAM, 0);
	if (listenfd < 0) {
		fprintf (stderr, "socket() failed, Err: %d \"%s\"\n", errno,strerror(errno));
		fflush(stderr);
		return(-1);
	}

	// avoid EADDRINUSE error on bind()
	OptVal = 1;
	printf ("setsockopt()\n");
	ris = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char *)&OptVal, sizeof(OptVal));
	if (ris<0)
	{	fprintf ( stderr, "setsockopt() SO_REUSEADDR failed, Err: %d \"%s\"\n", errno,strerror(errno));
		fflush(stderr);
		return(-1);
	}

	printf("porta locale %d\n", numero_porta_locale);
	/*name the socket */
	memset ( &Local, 0, sizeof(Local) );
	Local.sin_family= AF_INET;
	// specifico l'indirizzo IP attraverso cui voglio ricevere la connessione
	Local.sin_addr.s_addr = htonl(INADDR_ANY);
	Local.sin_port	      = htons(numero_porta_locale);

	printf ("bind()\n");
	ris = bind(listenfd, (struct sockaddr*) &Local, sizeof(Local));
	if (ris<0)
	{	fprintf (stderr,"bind() failed, Err: %d \"%s\"\n",errno,strerror(errno));
		fflush(stderr);
		return(-1);
	}

	printf ("listen()\n");
	ris = listen(listenfd, 10 );
	if (ris<0)
	{	fprintf ( stderr, "listen() failed, Err: %d \"%s\"\n",errno,strerror(errno));
		fflush(stderr);
		return(-1);
    }

	// tutto OK
	return(listenfd);
}


/* usage function */ 
void usage(int numero_porta_locale, unsigned int seed, double probabilitaerrore, int msec_ritardomax)
{
	printf("Specificare tre parametri\n");
	fflush(stdout);
	printf("usage: server.exe porta_locale[%d] seme[%u] prob_errore[%f] msec_ritardomax[%d msec]\n",
			numero_porta_locale, seed, probabilitaerrore, msec_ritardomax);
}

int main(int argc, char *argv[])
{
	#define LISTEN_PORT 9999	//Porta di ascolto del DownloadManager
	unsigned short int numero_porta_locale=LISTEN_PORT;
	int listenfd;
	unsigned int seed=111;
	pid_t pid;

	if ( argc !=5) {
		usage(numero_porta_locale,seed,probabilita_errore,msec_ritardomax);
		return(1);
	}
	else {
		numero_porta_locale = atoi(argv[1]);
		seed = atoi(argv[2]);
		probabilita_errore = atof(argv[3]);
		msec_ritardomax = atoi(argv[4]);

		printf("probabilita' errore %f\n", probabilita_errore);
	}  

	// Set up signal handlers 
    if ((signal (SIGCHLD, sig_child)) == SIG_ERR) {
		fprintf(stderr,"signal(SIGCHLD,..): Cannot attach signal handler\n");
		fflush(stderr);
		return 1;
	}

	if ( (listenfd=setup_listening_socket(numero_porta_locale)) < 0 ) {
		printf("quit\n");
		return(1);
	}

	for(;;) {

		int clientfd;
		unsigned int Len;
		struct sockaddr_in Cli;

		printf ("accept()\n");
		// wait for connection request
		do {
			Len=sizeof(Cli);
			memset(&Cli,0,sizeof(struct sockaddr_in) );
			clientfd = accept(listenfd, (struct sockaddr*) &Cli, &Len);
		} while ((clientfd<0)&&(errno==EINTR)) ;
		if (clientfd<0)
		{	
			printf ("accept() failed, Err: %d \"%s\"\n",errno,strerror(errno));
			close(listenfd);
			return(1);
		}

		seed++; // cambio seme di inizializzazione per ciascun server figlio
				// ogni richiesta viene servita con una diversa sequenza di
				// numeri random generati

		pid=fork();  ///////Rimettere la Fork!!!!!!!!!!!!!!!!!!
		if(pid<0) {
			close(listenfd);
			close(clientfd);
		    fprintf (stderr, "fork() failed, Err: %d \"%s\"\n", errno,strerror(errno));
			fflush(stderr);
			return(1);
		}
		if ( pid==0 )
		{	// processo figlio
			int ris;

			close(listenfd);

			signal(SIGHUP,  SIG_DFL);
			signal(SIGINT,  SIG_DFL);
			signal(SIGTERM, SIG_DFL);
		    if ((signal (SIGCHLD, sig_child)) == SIG_ERR) {
				fprintf(stderr,"signal(SIGCHLD,..): Cannot attach signal handler\n");
				fflush(stderr);
				close(clientfd);
				return 1;
			}

			srandom(seed);			// inizializzo la sequenza di numeri casuali
			ris=gestore(clientfd);	// gestisco la singola richiesta
			close(clientfd);

			return(ris);
		}
		else
		{	// processo padre
			close(clientfd);
		}
	}
	return(0);
}

