/*
 * prxu.c
 *
 *  Created on: May 5, 2014
 *      Author: kab8c8
 *      prxu.c means project Receiving Userspace Program
 *      and launches the prxk.c kernel module.
 */

/* 	Name       : 	client_udp.c
 Author     : 	Luis A. Rivera
 Description: 	ECE 4220 lab, Spring 2012
 Lab 5. Simple client (UDP) for lecture purposes			*/
#define MSG_SIZE 40			// message size. The messages will always be character arrays of size 40.
#include <rtai.h>
#include <rtai_lxrt.h>
#include <rtai_sched.h>
#include <rtai_fifos.h>
//#include <linux/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/mman.h> //for mmap
#include <semaphore.h>
#include <math.h>
#include <fcntl.h>  //for mmap
#include <string.h>
#include <netdb.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <time.h>//no doups
//#define DEBUG
#ifdef DEBUG
#define TRACE(x) (x);  fflush(stdout);
#define PRINT(x) printf(x); printf("\n"); fflush(stdout);
#define KALEN(x) {(printf(x));  printf("\n"); fflush(stdout);}
#define MATH(x)  {(x);  printf("\n"); fflush(stdout);}
#define DISPLAY_RECEIVED(x) TRACE(x)
#else
#define TRACE(x) ;
#define PRINT(x) ;
#define KALEN(x) ;
#define MATH(x)  ;
#define DISPLAY_RECEIVED(x) ;
#endif

int length, n, sock, receivingSock, boolval = 1, shutdownFlag = 0, numDroppedPackets = 0, numResets = 0, pktIndex = 0, serverFound = 0;
struct timeval packetReceivedOn[10];
#define MSG_SIZE 40			// message size
int unifiedPort = 0;
void error(const char *);
void* thread_read0(void *arg);
//kbhit is at the bottom of the code with the reference from where it came from.
int kbhit();
//slow kb hit is a blocking function for a minimum amount of time.
int slowkbhit();
int main(int argc, char *argv[]) {

	system("rmmod prxk >& /dev/null");
	if (argc != 2) {
		printf("usage %s port \n", argv[0]);
		exit(1);
	}

	int TransmitSock, n;
	unsigned int length;
	struct sockaddr_in server;
	struct hostent *hp;
	char buffer[MSG_SIZE];
	bzero(buffer, MSG_SIZE);

	//set the initialize and attribute for threads
	pthread_t thread[2];
	pthread_attr_t thread_attr;
	pthread_attr_init(&thread_attr);
	pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_JOINABLE);

	TransmitSock = socket(AF_INET, SOCK_DGRAM, 0); // Creates socket. Connectionless.
	if (TransmitSock < 0)
		error("socket");
	unifiedPort = atoi(argv[1]);
	server.sin_port = htons(unifiedPort); // port field
	server.sin_family = AF_INET; // symbol constant for Internet domain
	length = sizeof(struct sockaddr_in); // size of structure

	// change socket permissions to allow broadcast
	if (argc == 3) {
		hp = gethostbyname(argv[2]); // converts host name input (e.g. 10.3.52.12)
		if (hp == 0)
			error("Unknown host");
		bcopy((char *) hp->h_addr, (char *) &server.sin_addr, hp->h_length);
	} else {
		int boolval = 1;
		if (setsockopt(TransmitSock, SOL_SOCKET, SO_BROADCAST, &boolval, sizeof(boolval)) < 0) {
			printf("error setting socket options\n");
			exit(-1);
		}
		server.sin_addr.s_addr = inet_addr("10.3.52.255");
	}

	//Set to broadcast initially to find the server.
	do {

		//try to receive the acknowledgment. if thread hasn't found server then it will have shutdown. restart.
		if (serverFound == 0) {
			bzero(buffer, MSG_SIZE); // sets all values to zero. memset() could be used
			sprintf(buffer, "WHOIS");
			PRINT("Sending WHOIS to the server")
			// send message to server
			n = sendto(TransmitSock, buffer, strlen(buffer), 0, (const struct sockaddr *) &server, length);
			if (n < 0)
				error("Sendto");

			pthread_create(&thread[0], &thread_attr, thread_read0, (void *) &server);
			PRINT("Sleeping to allow the binding to expire in order to rebroadcast.");
			TRACE(printf("shutdownflag == %d, serverFound == %d\n",shutdownFlag,serverFound));
			sleep(20);
		}

		//the user may want to exit.
		if (kbhit() != 0) {
			break;
		}
		//use slow keyboard hit function call to keep from polling too fast unnecessarily.
	} while (shutdownFlag == 0 && serverFound <= 0); //TODO changed kbhit from && to ||

	if (shutdownFlag == 0 && kbhit() == 0) {
		PRINT("INIT DONE");
		printf("Please enter a command (Q to quit):     \n"
			"Dropped packets and Errors will apear on the screen and in the log\n"
			"Receiving Data.\n");
	}

	printf(".");
	while (buffer[0] != 'q' && buffer[0] != 'Q' && shutdownFlag == 0) {
		//check for user input with a timeout to come back and check for shutdown from thread.
		if (slowkbhit() != 0) {
			PRINT("Keyboard was hit");
			bzero(buffer, MSG_SIZE); // sets all values to zero. memset() could be used
			fgets(buffer, MSG_SIZE - 1, stdin); // MSG_SIZE-1 because a null character is added
			//server.sin_addr.s_addr = inet_addr("10.3.52.255"); // broadcast address because default is to reply to the sender of the received message
			if (buffer[0] != 'q' && buffer[0] != 'Q') {
				// send message to server.
				n = sendto(TransmitSock, buffer, strlen(buffer), 0, (const struct sockaddr *) &server, length);
				if (n < 0)
					error("Sendto");
				//enter period in seconds.microseconds.
				printf("Please enter a command (Q to quit):   \n");
				fflush(stdout);
			} else {
				shutdownFlag = 1;
				sprintf(buffer, "%s", "shutdown\n");
				n = sendto(TransmitSock, buffer, strlen(buffer), 0, (const struct sockaddr *) &server, length);
				if (n < 0)
					error("Sendto");
			}
		}
		PRINT("LOOPING main");
		//show the user that the program is not just stuck.
		printf(".");
		fflush(stdout);

	}
	printf("\n\nTransmit portion of the program shutting down\n");
	shutdownFlag = 1;
	pthread_join(thread[0], NULL);
	close(TransmitSock);
	close(receivingSock);
	printf("program shutdown properly\n\n\n");
	return 0;
}

void error(const char *msg) {
	perror(msg);
	exit(0);
}
//xxx this is the pthread that reads from the sending board.
void* thread_read0(void *arg) {
	PRINT("Thread read from socket started\n");
	serverFound = -1;
	char receivedFromIP[10];
	struct sockaddr_in rxSock;
	socklen_t fromlen = sizeof(struct sockaddr_in); // size of structure
	struct sockaddr_in addr;
	FILE *logFilePointer;
	char receivingBuffer[MSG_SIZE];
	bzero(receivingBuffer, MSG_SIZE);
	struct timeval tv;
	length = sizeof(rxSock); // length of structure
	bzero(&rxSock, length); // sets all values to zero. memset() could be used
	rxSock.sin_family = AF_INET; // symbol constant for Internet domain
	rxSock.sin_addr.s_addr = INADDR_ANY; // IP address of the machine on which the server is running
	rxSock.sin_port = htons(unifiedPort); // TODO prxu port number inflexible

	printf("Opening receive socket on port %d\n", unifiedPort);
	receivingSock = socket(AF_INET, SOCK_DGRAM, 0); // Creates socket. Connectionless.
	if (receivingSock < 0)
		error("Opening socket");

	// binds the socket to the address of the host and the port number for this process on this machine.
	if (bind(receivingSock, (struct sockaddr *) &rxSock, length) < 0) {
		printf("Error setting up the socket.\n"
			"Try a different port number.");
		error("binding");
	}
	//Open, Write, Close should be the correct procedure to reduce possibility of exiting without closing.
	if ((logFilePointer = fopen("logFile.txt", "a+")) == NULL) {
		printf("file opening error\n");
		shutdownFlag = 2;
	}
	//set time out to detect no packets on this port.
	tv.tv_sec = 5;
	tv.tv_usec = 0;
	if (setsockopt(receivingSock, SOL_SOCKET, SO_RCVTIMEO, (char *) &tv, sizeof(tv)) < 0) {
		printf("error setting socket options\n");
		fflush(stdout);
		shutdownFlag = 3;
	}
	//since we are using UDP our first message could have been lost.
	do {
		bzero(receivingBuffer, MSG_SIZE); // sets all values to zero. memset() could be used
		n = recvfrom(receivingSock, receivingBuffer, MSG_SIZE, 0, (struct sockaddr *) &addr, &fromlen);

		//check for shutdown. don't count as dropped packet if we purposefully shutdown the sending board.
		if (shutdownFlag != 0) {
			break;
		}

		TRACE(printf("packet received initially as \"%s\"\n",receivingBuffer));

		TRACE(sprintf(receivedFromIP, "%s", inet_ntoa(addr.sin_addr)));
		TRACE(printf("Message from IP %s\n", receivedFromIP));

		if (n == -1) { //A PACKET WAS NOT RECEIVED within the timeout period.!
			printf("timeout was reached, no packet came in.\n"
				"We are looking for the server on port %d.\n"
				"Are you sure you typed in the same port as the server?\n"
				"Did you start the server?\n"
				"Please wait while we try again.\n"
				"Press q to abort and quit the program\n", unifiedPort);
			//tell user space that we didn't find the server, try again.
			serverFound = 0;
			close(receivingSock);
			PRINT("Closing reading pthread.")
			pthread_exit(0);
		} else if (n == 0) {
			PRINT("empty packet");
			close(receivingSock);
			PRINT("Closing reading pthread.");
			pthread_exit(0);
		} else if (strcmp(receivingBuffer, "WHOIS") == 0) {
			PRINT("Our message was received from broadcast.");
		} else if (strcmp(receivingBuffer, "Server found, starting periodic\n") == 0) {
			sprintf(receivedFromIP, "%s", inet_ntoa(addr.sin_addr));
			fprintf(logFilePointer, "Server found on IP %s\nCollecting Data\n", receivedFromIP);
			printf("Server found on IP %s\nCollecting Data\n", receivedFromIP);
			serverFound = 1;
		} else if (receivingBuffer[0] == '#' && receivingBuffer[1] == ',') {
			sprintf(receivedFromIP, "%s", inet_ntoa(addr.sin_addr));
			printf("Server found on IP %s\nCollecting Data\n", receivedFromIP);
			fprintf(logFilePointer, "Server found on IP %s\nCollecting Data\n", receivedFromIP);
			serverFound = 1;
		} else {
			printf(".");
			printf("This message is not from the server or is data from the server. You might try a different port.\n");
		}
		//empty the socket buffer.
	} while (shutdownFlag == 0 && serverFound <= 0);

	//} while (strcmp(buffer, "Server found, starting periodic\n") != 0 && shutdownFlag != 0);
	//} while (shutdownFlag == 0);

	/************* make receiving from socket a blocking function but only for a specific time out*****/

	tv.tv_sec = 1;
	tv.tv_usec = 0;
	if (setsockopt(receivingSock, SOL_SOCKET, SO_RCVTIMEO, (char *) &tv, sizeof(tv)) < 0) {
		printf("error setting socket options\n");
		fflush(stdout);
		shutdownFlag = 3;
	}

	fprintf(logFilePointer, "starting a new log entry\n"
		"Note that the received time and transmitted time are EPOC times that may not be in sync\n"
		"# means came from server , Sequence number , Transmit time (EPOC) , Received Time (EPOC)\n");

	DISPLAY_RECEIVED(printf("Waiting for input:\n"));
	unsigned long previoussequenceNumber = 0, sequenceNumber = 1;
	while (shutdownFlag == 0 && serverFound == 1) {
		bzero(receivingBuffer, MSG_SIZE); // sets all values to zero. memset() could be used
		// receive from a client
		n = recvfrom(receivingSock, receivingBuffer, MSG_SIZE, 0, (struct sockaddr *) &addr, &fromlen);
		if (gettimeofday(&packetReceivedOn[pktIndex /*++ % 9*/], NULL) == 0) {
			TRACE(printf("Rx time = %ld.%06ld\t", packetReceivedOn[pktIndex].tv_sec, packetReceivedOn[pktIndex].tv_usec));
		} else {
			printf("gettimeofday() failed.\n");
			break;
		}
		TRACE(sprintf(receivedFromIP, "%s", inet_ntoa(addr.sin_addr)));
		TRACE(printf("Message from IP %s\n", receivedFromIP));

		//check for shutdown. don't count as dropped packet if we purposefully shutdown the sending board.
		if (shutdownFlag != 0) {
			break;
		}

		if (n == -1) { //A PACKET WAS NOT RECEIVED within the timeout period.!
			PRINT("timeout was reached, no packet came in. \n");
			fprintf(logFilePointer, " number of dropped packets this round == %d\n", numDroppedPackets + 1);
			if (numDroppedPackets++ > 5) {
				fprintf(logFilePointer, "resetting router!!! Attempt #%d\n", numResets + 1);
				printf("resetting router!!! Attempt #%d\n", numResets + 1);
				system("insmod /home/kab8c8/workspace/prxk/Debug/prxk.o >& /dev/null");
				//sleep for enough time for the user to hear the sound and for the router to reset.
				sleep(1 * ++numResets);
				system("rmmod prxk");
				printf("Letting router boot up\n");
				int i = 0;
				for (i = 0; i < numResets; i++) {
					sleep(10 * numResets);
					//let the user know that we are still working on the problem.
					printf(".");
				}
				printf("\n");
				if (numResets >= 3) {
					printf("\n\n\nProgram not properly resetting the system.\nShutting down to prevent damage.");
					fprintf(logFilePointer, "\n\n\nProgram not properly resetting the system.\n");
					fprintf(logFilePointer, "Shutting down to prevent damage.\n\n\n");
					shutdownFlag = 1;
				}
			}
		} else if (n < -1) {
			fprintf(logFilePointer, "receiving socket error, program shutting down.");
			shutdownFlag = 1;
			error("recvfrom");//some other error.... kill the program.
		} else if (receivingBuffer[0] == '#' && receivingBuffer[1] == ',') {//A PACKET WAS RECEIVED!
			numResets = numDroppedPackets = 0;
			DISPLAY_RECEIVED(printf("code returned from recvfrom was %d\n",n));
			char receivedFromIP[10];
			sprintf(receivedFromIP, "%s", inet_ntoa(addr.sin_addr));
			DISPLAY_RECEIVED(printf("Message from IP %s\n",receivedFromIP));
			DISPLAY_RECEIVED(printf("It says: %s\n", receivingBuffer));

			//store a record.
			fprintf(logFilePointer, receivingBuffer);
			fprintf(logFilePointer, ",\t%ld.%06ld\t", packetReceivedOn[pktIndex].tv_sec, packetReceivedOn[pktIndex].tv_usec);
			fprintf(logFilePointer, "\n");

			//check for dropped packet.
			strtok(receivingBuffer, " ,");
			//each successive call to strtok uses NULL as source
			sequenceNumber = atoi(strtok(NULL, " ,"));
			TRACE(printf("sequenceNumber == %ld",sequenceNumber));
			if (sequenceNumber - 1 != previoussequenceNumber) {
				printf("Dropped packet detected.\n");
				fprintf(logFilePointer, "       Dropped packet detected.\n");
			}
			previoussequenceNumber = sequenceNumber;
			//dropped packet detected
		}
		if (shutdownFlag != 0) {
			printf("\n\nreceiving portion of the program shutting down err code %d\n", shutdownFlag);
			break;
		}
	}
	shutdownFlag = 1;
	fclose(logFilePointer);
	close(receivingSock);
	PRINT("Closing reading pthread.")
	pthread_exit(0);
}
//http://cc.byexamples.com/2007/04/08/non-blocking-user-input-in-loop-without-ncurses/
//also from http://stackoverflow.com/questions/3711830/set-a-timeout-for-reading-stdin
//http://cc.byexamples.com/2007/04/08/non-blocking-user-input-in-loop-without-ncurses/
//also from http://stackoverflow.com/questions/3711830/set-a-timeout-for-reading-stdin
int kbhit() {
	struct timeval tv;
	fd_set fds;
	tv.tv_sec = 0; //check if there is data in the buffer and return quickly.
	tv.tv_usec = 0;
	FD_ZERO(&fds);
	FD_SET(STDIN_FILENO, &fds);
	//STDIN_FILENO is 0
	select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
	return FD_ISSET(STDIN_FILENO, &fds);
}
int slowkbhit() {
	struct timeval tv;
	fd_set fds;
	tv.tv_sec = 1; // Give the user a second to type in data.
	tv.tv_usec = 1;
	FD_ZERO(&fds);
	FD_SET(STDIN_FILENO, &fds);
	//STDIN_FILENO is 0
	select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
	return FD_ISSET(STDIN_FILENO, &fds);
}
