#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <math.h>
#include <string.h>
#include <signal.h>

#define MAXLINE 4096
#define LISTENQ 2
#define SA struct sockaddr
#define PACKET_SIZE 500 //1024

// MISC
#define ESCAPE 27
#define RED "[31m"
#define GREEN "[32m"
#define DEFAULT "[0m"
#define COMPILE_TIME __TIME__
#define COMPILE_DATE __DATE__

// Server Defines
#define SERVER_PRINT_DELAY 5 // in seconds
#define SERVER 2;

// Client Defines
#define CLIENT_PRINT_DELAY 30 // in seconds
#define CLIENT_DEF_KBPS 200 // in kbps
#define CLIENT_DEF_PORT 2000
#define CLIENT 1;

// Log Defines
#define LOGADDR "129.115.28.27"
#define LOGPORT 1500
#define LOG_PACKET_SIZE 1460

#define printsocket(s) printf("%s, %d\n", inet_ntoa(s.sin_addr), \
				   (int) ntohs(s.sin_port));

unsigned long long calcmicro();
void writeclientheader();
void writeserverheader();
void endprog();
void usage();
void sendstate();
void sendlog( char *logstr );
char time_string[100];
time_t rawtime;

struct stats {
	// Time section
	unsigned long long ll_begin_time;
	unsigned long long ll_start_time;
	unsigned long long ll_end_time;
	unsigned long long ll_local_time;
	unsigned long long ll_elapsed_time;
	unsigned long long ll_delay_time;
	unsigned long long ll_display_time;
	unsigned long long ll_catchup_bits;
	
	// Transfer section 
	unsigned long long ll_bits;
	unsigned long long ll_bits_total;

	unsigned long long ll_packets;
	unsigned long long ll_packets_total;
	
	int i_packet_size;
	int i_kbps;
	int i_myid;

	// Information
	char c_ipaddr[100];
	int i_port;
	int i_type;
	int i_id;
};

struct options {
	char *c_ipaddr; // ipaddress
	char *c_logaddr; // log address
	char *c_stateaddr; // state address
	int i_port; // port
	int i_print; // print delay
	int i_kbps; // kbps to send
};

struct options o;
struct stats s;	
void writelog( int sig );
int get_net( char *ipaddr );

int
main (int argc, char **argv)
{
	pid_t pid;
	int c;

#ifdef MIPSBUILD
	FILE *fp;
	char line[130];
#endif
	char ipaddr[130];

	printf( "Compiled %s %s\n", COMPILE_DATE, COMPILE_TIME );
	s.i_type = 0;
	o.i_print = 0;
	o.c_ipaddr = NULL;
	o.i_kbps = CLIENT_DEF_KBPS;
	o.i_port = 0;
	o.c_logaddr = LOGADDR;
	o.c_stateaddr = LOGADDR;

	signal( SIGINT, endprog );
	signal( SIGTERM, endprog );

	s.ll_display_time = 0;
	s.ll_delay_time = 0;
	s.ll_begin_time = 0;
	s.ll_start_time = 0;
	s.ll_end_time = 0;
	s.ll_local_time = 0;
	s.ll_elapsed_time = 0;
	s.ll_bits = 0;
	s.ll_bits_total = 0;
	s.ll_packets = 0;
	s.ll_packets_total = 0;
	s.ll_catchup_bits = 0;
	s.i_packet_size = PACKET_SIZE;
	s.i_kbps = CLIENT_DEF_KBPS;

	time( &rawtime );
	strcpy( time_string, (char*) ctime( &rawtime ) );
	time_string[strlen(time_string) -1] = '\0';	

#ifdef LINUXBUILD
	strcpy( ipaddr, LOGADDR );
#endif

#ifdef MIPSBUILD
	fp = popen( "/usr/sbin/nvram get wifi_ipaddr", "r" );
        while ( fgets( line, sizeof line, fp))
        {
                strcpy( ipaddr, line );
        }
	ipaddr[strlen(ipaddr) -1] = '\0';
#endif
	s.i_id = get_net( ipaddr );

#ifndef DEBUG	
	if( (pid = fork() ) < 0 ) {
		printf( "Error forking to the background.\n" );
		exit(1);
	}

	if( pid > 0 ) {
		exit(0);
	}

	setsid(); // create a new process group
#endif

	while( ( c = getopt( argc, argv, "L:d:i:k:p:S:cs" ) ) != -1 ) {
		switch( c ) {
			case 'c': // client mode
				s.i_type = CLIENT;
			break;

			case 'd': // print delay
				o.i_print = atoi( optarg );
			break;

			case 'i': // ipaddress to talk to
				o.c_ipaddr = optarg;
			break;

			case 'k': // kbps to send
				o.i_kbps = atoi( optarg );
			break;
	
			case 'p': // port to use
				o.i_port = atoi( optarg );
			break;

			case 's': // server mode
				s.i_type = SERVER;
			break;

			case 'L': // Log server address
				o.c_logaddr = optarg;		
			break;

			case 'S':
				o.c_stateaddr = optarg;
			break;

			case '?':
				if( isprint( optopt ) ) {
					fprintf( stderr, "Unknown option '-%c'.\n", optopt );
				} else {
					fprintf( stderr, "Unknown option character '\\x%x'.\n", optopt );
				}
				usage();
			break;

			default:
				usage();
			break;
		}
	}
	
	if( s.i_type == 0 ) {
		usage();
	} else if( s.i_type == 1 ) { // client
		if( o.i_port == 0 || o.c_ipaddr == NULL ) {
			usage();
		} else {
			return ucli( &o, &s );
		}
	} else if( s.i_type == 2 ) { // server
		if( o.i_port == 0 ) {
			usage();
		} else {
			return userv( &o, &s );
		}
	} else {
		usage();
	}
}

void 
usage() {
	fprintf( stderr, "Modes:\n" );
	fprintf( stderr, "-c\tClient mode\n" );
	fprintf( stderr, "-s\tServer mode\n\n" );
	fprintf( stderr, "Options:\n" );
	fprintf( stderr, "-p\tPort (Required)\n" );
	fprintf( stderr, "-i\tIP Address (Required, Client Only)\n" );
	fprintf( stderr, "-k\tKbps to sent (Optional, Client Only)\n" );
	fprintf( stderr, "-d\tPrint Delay (Optional, Client Only)\n" );
	exit(0);
}

int 
get_net( char *ipaddr ) {
	char str[20];
	char *net1;
	char *net2;
	char *net3;
	char *netaddress;
	int mynetaddress;

	if( ipaddr == NULL ) {
		return 0;
	}

	if( ( strlen( ipaddr ) < 15 ) && ( strlen( ipaddr ) > 7 ) ) {
#ifdef DEBUG 
		printf( "%s\n", ipaddr );
#endif
		sprintf( str, "%s", ipaddr );
		net1 = strchr( str, '.' ); //find the first dot
		if( net1 == NULL ) return 0;
		net2 = strchr( net1 + 1, '.' ); //finds the second
		if( net2 == NULL ) return 0;
		netaddress = net2 + 1;
	   	net3 = strchr( net2 + 1, '.' ); //finds the last
		if( net3 == NULL ) return 0;
	   	net3[0] = '\0';
	   	sscanf( netaddress, "%d", &mynetaddress );
	   	return mynetaddress;
	} else {
		return 0;
	}
}

void
endprog() {
	writelog( 1 );
	exit(1);
}

void
writelog( int sig ) {
	FILE *fp = NULL;
	char logstr[MAXLINE];
	char logtype[10];

	if( !fp ) {
		if( s.i_type == 1 ) {
	//		fp = fopen( "/root/client.log", "a" );
			sprintf( logtype, "\tC\n" );
		} else if( s.i_type == 2 ) {
			s.ll_local_time = calcmicro();
	//		fp = fopen( "/root/server.log", "a" );
			sprintf( logtype, "\tS\n" );
		} else {
			printf( "Error. Undefined log type." );
			exit( 0 );
		}
	}
	sprintf( logstr, "[%s]\t%s\t", time_string, s.c_ipaddr );
	sprintf( logstr, "%s%llu\t%llu\t", logstr, ( s.ll_start_time - s.ll_begin_time )/1000, ( s.ll_local_time - s.ll_begin_time )/1000 );
	sprintf( logstr, "%s%llu\t%llu\t", logstr, s.ll_packets, s.ll_bits );
	sprintf( logstr, "%s%d\t%llu\t", logstr, 0, s.ll_packets_total );
	sprintf( logstr, "%s%llu\t%d", logstr, s.ll_bits_total, s.i_packet_size );
	
	//fprintf( fp, "%s\n", logstr );
	//fclose( fp );

	sprintf( logstr, "%s%s", logstr, logtype );
	sendlog( logstr );
	sendstate();

	if( s.i_type == 2 ) {
        	s.ll_packets = 0;
        	s.ll_bits = 0;
		s.ll_start_time = s.ll_end_time;
        	s.ll_end_time = s.ll_start_time + ( SERVER_PRINT_DELAY * 1000 );
	}
}

void
writeclientheader() {
	FILE *fp = NULL;

	if( !fp ) {
	//	fp = fopen( "/root/client.log", "a" );
	}

	//fprintf( fp, "# Timestamp\tServer Ipaddr\t Loop Start\tLoop End\tLoop Packets\tLoop Bytes\tEnd Time\tTotal Packets\tTotal Bytes\tPacket Size\n" );
	//fclose( fp );
}

void
writeserverheader() {
	FILE *fp = NULL;

	if( !fp ) {
	//	fp = fopen( "/root/server.log", "a" );
	}

	//fprintf( fp, "# Timestamp\tClient Ipaddr\t Loop Start\tLoop End\tLoop Packets\tLoop Bytes\tEnd Time\tTotal Packets\tTotal Bytes\tPacket Size\n" );
	//fclose( fp );
}

void sendstate() {
	int sktid, b;
        struct sockaddr_in servaddr;
	char logstr[MAXLINE];
	int i_targetid;

        if ( (sktid =socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
                printf(" ** Unable to get socket id\n");
                exit(1);
        }

        bzero(&servaddr, sizeof (servaddr));
        servaddr.sin_family = AF_INET;
        if ( (servaddr.sin_addr.s_addr = inet_addr(o.c_stateaddr)) < 0) {
                printf(" ** Unable to convert IP address\n");
                exit(1);
        }

	if( s.c_ipaddr == NULL ) {
#ifdef DEBUG
		printf( "Null ip address\n" );
#endif
		i_targetid = 0;
	} else {
#ifdef DEBUG
		printf( "Got an ip address\n" );
#endif
		i_targetid = get_net( (char*)s.c_ipaddr );
	}
        servaddr.sin_port = htons (2000);
	// 11 <time> <id> <tg> <loop_end> <bits> <mode> <bits> <loop_end>
	//                  0    1 2                               3         4          5                 6          7         8                            9
	sprintf( logstr, "STATE 11 0 %d %d %d %llu %d %llu %d", s.i_id, i_targetid, SERVER_PRINT_DELAY, s.ll_bits, s.i_type, s.ll_bits_total, (int)( s.ll_local_time - s.ll_begin_time )/1000 );
        b = sendto(sktid, logstr, strlen( logstr ), 0, (SA *) &servaddr, sizeof(servaddr));
	// cleanup?
}

void sendlog( char *logstr ) {
	int sktid, b;
        struct sockaddr_in servaddr;

        if ( (sktid =socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
                printf(" ** Unable to get socket id\n");
                exit(1);
        }

        bzero(&servaddr, sizeof (servaddr));
        servaddr.sin_family = AF_INET;
        if ( (servaddr.sin_addr.s_addr = inet_addr(o.c_logaddr)) < 0) {
                printf(" ** Unable to convert IP address\n");
                exit(1);
        }
        servaddr.sin_port = htons (LOGPORT);
        b = sendto(sktid, logstr, strlen( logstr ), 0, (SA *) &servaddr, sizeof(servaddr));
	// cleanup?
}

int
ucli(struct options *o, struct stats *s)
{
	int b;
	int sktid;

	// Packet tracking...
	int loop;

	unsigned long long ll_bits_needed;
	int i_packets_needed;
	// time stuff
	double delay;

	// kbps stuff
	struct sockaddr_in servaddr;
	char sendline[MAXLINE];
	
	if( o->i_kbps != 0 ) {
		s->i_kbps = o->i_kbps;
		delay = ( ( s->i_kbps * ( (int)1000 ) ) / ( PACKET_SIZE * 8.0 ) );
	} else {
		delay = ( ( CLIENT_DEF_KBPS * ( (int)1000 ) ) / ( PACKET_SIZE * 8.0 ) );
	}
	//----------------------------------------------------------------------------

	if ( (sktid =socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		printf(" ** Unable to get socket id\n");
		exit(1);
	}

	bzero(&servaddr, sizeof (servaddr));
	servaddr.sin_family = AF_INET;
	if ( (servaddr.sin_addr.s_addr = inet_addr(o->c_ipaddr)) < 0) {
		printf(" ** Unable to convert IP address\n");
		exit(1);
	}
	servaddr.sin_port = htons (o->i_port);

	loop = 1;
	if( o->i_print != 0 ) {
		s->ll_display_time = ( o->i_print * 1000 );
	} else {
		s->ll_display_time = ( CLIENT_PRINT_DELAY * 1000 );
	}
	
	strcpy( s->c_ipaddr, (char *)o->c_ipaddr );
	s->i_port = o->i_port;

	s->ll_begin_time = calcmicro();
	s->ll_start_time = s->ll_begin_time;
	s->ll_end_time = s->ll_begin_time + s->ll_display_time;
	s->ll_delay_time = ( unsigned long long )( (1*( (int)1000000 ) ) / delay );
	
	writeclientheader();
	while( loop ) {
		s->ll_local_time = calcmicro();

		s->ll_elapsed_time = s->ll_local_time - s->ll_begin_time;
		
		if( (s->ll_start_time + s->ll_display_time ) <= s->ll_local_time ) {
/*
			if( s->ll_packets == 1 ) {
				writelog( 1 );
				exit(1);
			}
*/
			s->ll_end_time = s->ll_local_time - s->ll_begin_time;
			writelog( 1 );
			s->ll_start_time = s->ll_local_time;
			s->ll_packets = 0;
			s->ll_bits = 0;
		}
		b = sendto(sktid, sendline, s->i_packet_size, 0, (SA *) &servaddr, sizeof(servaddr));

		s->ll_packets_total++;
		s->ll_packets++;
		s->ll_bits_total = ( s->ll_packets_total * s->i_packet_size ) * 8;
		s->ll_bits = ( s->ll_packets * s->i_packet_size ) * 8;

		if( s->ll_elapsed_time > 2000) {
			if( ( ( s->ll_bits_total /*+ s->ll_catchup_bits*/ ) / ( s->ll_elapsed_time / 1000 ) ) < ( s->i_kbps * 1000 ) ) {
				/*ll_bits_needed = s->i_kbps * s->ll_elapsed_time - s->ll_bits_total;
				i_packets_needed = (ll_bits_needed)/ s->i_packet_size;
				if( i_packets_needed > 10 ) {
					s->ll_catchup_bits += ( ll_bits_needed);
				}*/		
			} else {
				usleep( s->ll_delay_time );
			}
		} else {
			usleep( s->ll_delay_time );
		}

		//usleep( s->ll_delay_time );
	}
	return(0);
}

unsigned long long 
calcmicro()
{
	struct timeval curtime;
	struct timezone curzone;
	
	gettimeofday( &curtime, &curzone );
	return (curtime.tv_sec * 1000)+(curtime.tv_usec/1000);
}

int
userv (struct options *o, struct stats *s)
{
	int n, lenc;
	int sktid;
	char buf[MAXLINE+1];
	struct sockaddr_in servaddr, cliaddr;

	// Timer stuff
	struct sigaction sa;
	struct itimerval timer;

	/* Setup our listening socket */
	if ( (sktid =socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		printf(" ** Unable to get socket id\n");
		exit(1);
	}

	bzero(&servaddr, sizeof(servaddr));
	bzero(&cliaddr, sizeof(cliaddr));
	
	/* setup the server address information */
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons (o->i_port);

	/* setup the client address information */
	cliaddr.sin_family = AF_INET;
	cliaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	cliaddr.sin_port = htons(0);

	bind(sktid, (SA *) &servaddr, sizeof(servaddr));


	/* Setup our timer information */
	memset( &sa, 0, sizeof( sa ) );
	sa.sa_handler = &writelog;
	sigaction( SIGALRM, &sa, NULL );

	/* set the timer to display every xx seconds */	
	timer.it_value.tv_sec = SERVER_PRINT_DELAY;
	timer.it_value.tv_usec = 0;
	timer.it_interval.tv_sec = SERVER_PRINT_DELAY;
	timer.it_interval.tv_usec = 0;

	/* setup the virtual timer */
	setitimer (ITIMER_REAL, &timer, NULL);

	s->ll_begin_time = calcmicro();
	s->ll_start_time = s->ll_begin_time;

	s->ll_packets_total = 0;
	s->ll_packets = 0;
	s->ll_bits = 0;
	s->ll_bits_total = 0;
	s->ll_end_time = s->ll_start_time + ( SERVER_PRINT_DELAY * 1000 );

	writeserverheader();
	for ( ; ; ) {
		lenc = sizeof(cliaddr);
		
		n = recvfrom(sktid, buf, MAXLINE, 0, (SA *) &cliaddr, &lenc);
		if( n > 0 ) {
			s->ll_packets_total++;
			s->ll_packets++;
			s->ll_bits = ( s->ll_packets * s->i_packet_size ) * 8;
			s->ll_bits_total = ( s->ll_packets_total * s->i_packet_size ) * 8;
			strcpy( s->c_ipaddr, (char*)inet_ntoa(cliaddr.sin_addr));
		}
	}
}
