#include <sys/types.h>
#include <stdio.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/fcntl.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <poll.h>
#include <stdarg.h>
#include <time.h>
#include <errno.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>

#define MAX_PROCS 1024

struct addrinfo *res;
struct addrinfo hints;
char server[1024];
int  port;
int  total_count;
int endless;

unsigned char addr_data[1024];
struct sockaddr *sock;

void printer(char *prefix, const char *fmt, ...){
        char p[102400];
        FILE *log;
        va_list ap;
        struct timeval tv;
        struct tm _tm;
        struct tm *tm;
        char datestamp[512];
        gettimeofday(&tv,NULL);
        tm = localtime_r(&tv.tv_sec,&_tm);



        sprintf(datestamp,"[%04d-%02d-%02d %02d:%02d:%02d.%06d]{%d} (%s)",
                tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
                tm->tm_hour, tm->tm_min, tm->tm_sec, tv.tv_usec,
                getpid(), prefix == NULL ? "NULL" : prefix );

        va_start(ap,fmt);
        vsnprintf(p,102400,fmt,ap);
        va_end(ap);


	printf("%s %s",datestamp,p);
}


void process_slave(int id){
	int i;
	int slen;
	char request[10240];
	char prefix[128];
	sprintf(prefix,"[%05d]",id);
	srandom(time(NULL) * getpid());
	sprintf(request,"GET / HTTP/1.1\r\nHostname: %s\r\nConnection: keep-alive\r\n\r\n",server);
	slen = strlen(request);
	for ( i = 0; i <  endless; i++ ){
		int newsock;
		int save_fd;
		int retval;
		int sval;
		int sent;
		int chunks;
		char buf[10240];
		struct pollfd polly;
		newsock = socket(sock->sa_family,SOCK_STREAM,0);
		if ( newsock < 0 ){
			printf("[%05d] Failed to createsocket\n",id);
			exit(3);
		}
		if ( connect(newsock, sock,res->ai_addrlen) < 0 ){
			printf("[%05d] Failed to connect\n",id);
			exit(2);
		}
		sval = (random() % 1000);
		printer(prefix,"[%d/%d] Connected to remote [Sleep-Value: %d ms]\n",i, endless,sval);
		usleep(sval * 1000);
		printer(prefix,"[%d/%d] Sending %d Bytes\n",i,endless,slen);
		chunks = 1;
		while ( (retval = send(newsock,&request[sent],slen - sent,0)) > 0 ){
			chunks++;
			sent += retval;
		}
		if ( sent != slen ){
			printer(prefix,"[%d/%d] Sending failed! (%d/%d)\n",i,endless,sent,slen);
			exit(4);
		}
		printer(prefix,"[%d/%d] %d Bytes sent in %d chunks\n",i,endless,slen,chunks);	
		// Make me non-blocking
		/*
		save_fd = fcntl(newsock,F_GETFL);
		save_fd |= O_NONBLOCK;
		if ( fcntl(newsock,F_SETFL,save_fd) < 0 ){
			printer(prefix,"[%d/%d] Sending NON-Blocking failed!\n",i,endless);
			exit(4);
		}
		*/
		bzero(&polly,sizeof(polly));
		polly.fd = newsock;
#ifdef POLLRDHUP
		polly.events = POLLIN | POLLRDHUP | POLLHUP;
#else
		polly.events = POLLIN | POLLHUP;
#endif
		retval = poll(&polly,1,5000);
		if ( retval < 0 ){
			printer(prefix,"[%d/%d] Receiving NON-Blocking failed! (Error at poll)[%s]\n",i,endless,strerror(errno));
			close(newsock);
			exit(1);
		}
		if ( retval < 1 ){
			printer(prefix,"[%d/%d] Receiving NON-Blocking failed! (Timeout at poll)\n",i,endless);
			close(newsock);
			exit(1);
		}
		if ( polly.revents & POLLERR ){
			printer(prefix,"[%d/%d] Receiving NON-Blocking failed! (POLLERR at poll)\n",i,endless);
			close(newsock);
			exit(1);
		}
		if ( polly.revents & POLLHUP ){
			printer(prefix,"[%d/%d] Receiving NON-Blocking failed! (POLLHUP at poll)\n",i,endless);
			close(newsock);
			exit(1);
		}
#ifdef POLLRDHUP
		if ( polly.revents & POLLRDHUP ){
			printer(prefix,"[%d/%d] Receiving NON-Blocking failed! (POLLRDHUP at poll)\n",i,endless);
			close(newsock);
			exit(1);
		}
#endif
		if ( polly.revents & POLLIN ){
			printer(prefix,"[%d/%d] Receiving NON-Blocking ready! (POLLIN at poll)\n",i,endless);
		}
		retval = recv(newsock,buf,10240,0);
		if ( retval < 0 ){
			printer(prefix,"[%d/%d] Receiving NON-Blocking failed! (Error at receive)[%s]\n",i,endless,strerror(errno));
			close(newsock);
			exit(1);
		}	
		printer(prefix,"[%d/%d] Disconnecting from remote, after receving %d Bytes [%.5s]\n",i, endless,retval,buf);
		close(newsock);
		usleep(sval * 1000);
	}
	exit(0);
}

void reaper(){
}


void main(int argc, char **argv){
	struct sockaddr_in *s4;
	struct sockaddr_in6 *s6;
	char portname[32];
	char ipname[1024];
	int pid[MAX_PROCS];

	int len;
	int i;

	if ( argc < 3 ){
		printf("Usage: %s <server> <port> [count] [endless/loop-count]\n",argv[0]);
		exit(0);
	}
	strcpy(server,argv[1]);
	port = atoi(argv[2]);
	if ( argc >= 4 ){
		total_count = atoi(argv[3]);
	} else {
		total_count = 1;
	}	

	if ( total_count > MAX_PROCS ) total_count = MAX_PROCS;

	if ( argc >= 5 ){
		endless = atoi(argv[4]);
	} else {
		endless = 1;
	}
	printf("Testing: [%s]:%d (%d Times), Endless: [%d]\n",server,port,total_count,endless);

	bzero(&hints,sizeof(hints));
#ifdef AI_NUMERICSERV
	hints.ai_flags = AI_NUMERICSERV;
#endif
	hints.ai_socktype = SOCK_STREAM;

	sprintf(portname,"%d",port);

	if ( getaddrinfo(server,portname,&hints,&res) != 0 ){
		printf("Cannot resolve [%s] (or invalid IPv4/IPv6 address)\n",server);
		exit(2);
	}
	sock = (struct sockaddr *)addr_data;
	memcpy((void *)sock,(void *)res->ai_addr,res->ai_addrlen);
	if ( sock->sa_family == AF_INET6 ){
		s6 = (struct sockaddr_in6 *) sock;
		if ( inet_ntop(AF_INET6,&(s6->sin6_addr),ipname,1024) == NULL ){
			printf("Cannot convert address\n");
			exit(3);
		}
	}
	if ( sock->sa_family == AF_INET ){
		s4 = (struct sockaddr_in *) sock;
		if ( inet_ntop(AF_INET, &(s4->sin_addr),ipname,1024) == NULL ){
			printf("Cannot convert address\n");
			exit(3);
		}
	}
	printf("Addrlen: %d Bytes, Family: [%s] [%s]\n",res->ai_addrlen, sock->sa_family == AF_INET6 ? "IPv6" : (sock->sa_family == AF_INET ? "IPv4" : "unknown"),ipname);
	signal(SIGCHLD,reaper);
	for ( i = 0; i < total_count; i++ ){
		if ( (pid[i] = 	fork()) == 0 ){
			process_slave(i);
		}
	}
	while ( 1 ){
		int rpid;
		int status;
		if ( (rpid = waitpid(-1,&status,0)) > 0 ){
			int i;
			printer("**** MAIN ****","Process %d exited STATUS: %d\n",rpid,WEXITSTATUS(status));
			if ( WEXITSTATUS(status) > 0 ){
				printf("Error occured!\n");
				exit(0);
			}
			for ( i = 0; i < total_count; i++ ){
				if ( rpid == pid[i] ){ pid[i] = -1; break; }
			}
		}
		for ( i = 0; i < total_count; i++ ){
				if ( pid[i] != -1 ) break;
		}
		if ( i == total_count ){
			printer("**** MAIN ****", "All processes exited\n");
			exit(0);
		}
	}	
}
