/*
 * ptxu.c
 *
 *  Created on: May 5, 2014
 *      Author: kab8c8
 *      Description:
 *      ptxu.c project transmitting user space program.
 *      Note:
 *      The realtime tasks require compiler optimization -O3
 *      built primarily off of labs 2,3 and 5
 */


#include <stdio.h>
#include <stdlib.h>
#include <rtai.h>
#include <rtai_lxrt.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>

#define MSG_SIZE 40			// message size. The messages will always be character arrays of size 40.
#define BASEPERIOD     1000000
#define TRANSMITPERIOD (unifiedAttributes->BaseP*500)

typedef struct {
	RTIME BaseP;
	int length, n, sock, boolval;
	struct sockaddr_in server;
	socklen_t fromlen; // size of structure
	struct sockaddr_in addr;
	int shutdownFlag;
	char * port;
} unified_attributes;

void error(const char *msg) {
	perror(msg);
	exit(0);
}

//This pthread will be the sending thread.
void *thread_read1(void *arg);

//http://cc.byexamples.com/2007/04/08/non-blocking-user-input-in-loop-without-ncurses/
int kbhit();

//#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)
#define GETCHAR(x) //x
#define KERNEL(x) //x
#else
#define TRACE(x) ;
#define PRINT(x) ;
#define KALEN(x) ;
#define MATH(x)  ;
#define GETCHAR(x)
#define DISPLAY_RECEIVED(x) ;
#define KERNEL(x)
#endif

int main(int argc, char *argv[]) {
	puts("!!!Hello World!!! low latency network custodian is starting up"); /* prints !!!Hello World!!! lab3 is starting */
	PRINT("IN DEBUG MODE:::");GETCHAR(PRINT("Hit Enter"));GETCHAR(getchar());

	if (argc < 2) {
		printf("Usage: %s port \n"
			"port number that is not already used by the network and higher than 1024\n", argv[0]);
		exit(0);
	}
	unified_attributes unifiedAttributes;
	unifiedAttributes.port = argv[1];
	unifiedAttributes.boolval = 1;
	unifiedAttributes.shutdownFlag = 0;
	//socket stuff
	char buffer[MSG_SIZE]; // to store received messages or messages to be sent.
	srand(time(NULL));

	unifiedAttributes.length = sizeof(unifiedAttributes.server); // length of structure
	bzero(&unifiedAttributes.server, unifiedAttributes.length); // sets all values to zero. memset() could be used
	unifiedAttributes.server.sin_family = AF_INET; // symbol constant for Internet domain
	unifiedAttributes.server.sin_addr.s_addr = INADDR_ANY; // IP address of the machine on which
	// the unifiedAttributes.server is running
	unifiedAttributes.server.sin_port = htons(atoi(argv[1])); // port number


	unifiedAttributes.sock = socket(AF_INET, SOCK_DGRAM, 0); // Creates socket. Connectionless.
	if (unifiedAttributes.sock < 0)
		error("Opening socket");

	// binds the socket to the address of the host and the port number
	if (bind(unifiedAttributes.sock, (struct sockaddr *) &unifiedAttributes.server, unifiedAttributes.length) < 0) {
		printf("Error setting up the socket.\n"
			"Try a different port number.");
		error("binding");
	}

	//***********************************
	//for testing, get local IP address
	struct ifreq ifr;
	ifr.ifr_addr.sa_family = AF_INET;
	snprintf(ifr.ifr_name, IFNAMSIZ, "eth0");
	ioctl(unifiedAttributes.sock, SIOCGIFADDR, &ifr);

	/* and more importantly */
	TRACE( printf("%s\n", inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr)));

	//Local to main. save off the local IP as a string.
	char localIP[10];
	sprintf(localIP, "%s", inet_ntoa(((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr));
	TRACE( printf("after copy %s\n",localIP));

	// for testing. http://pic.dhe.ibm.com/infocenter/tpfhelp/current/index.jsp?topic=%2Fcom.ibm.ztpf-ztpfdf.doc_put.cur%2Fgtpc2%2Fcpp_gethostbyname.html
	u_char hostname[50];
	if (gethostname(hostname, sizeof(hostname)) == 0) {
		PRINT("gethostname success");
	} else {
		PRINT("gethostname fail");
	}
	TRACE(printf("hostname = %s\n",hostname));
	//*************************************

	// We don't want to broadcast our messages so that we can save bandwidth on the network.
	// receive from a client BEFORE starting up the unifiedAttributes.server BECAUSE we don't know the IP address of the client!
	do {
		PRINT("checking for valid message from socket");
		int n = recvfrom(unifiedAttributes.sock, buffer, MSG_SIZE, 0, (struct sockaddr *) &unifiedAttributes.addr, &unifiedAttributes.fromlen);

		if (n < 0)
			error("recvfrom");
		char receivedFromIP[10];
		sprintf(receivedFromIP, "%s", inet_ntoa(unifiedAttributes.addr.sin_addr));
		printf("Message from IP %s\n", receivedFromIP);
		//The message must be encoded to differentiate it from noise.
	} while (strcmp(buffer, "WHOIS") == 0);
	//links established


	//start real time timer
	unifiedAttributes.BaseP = start_rt_timer(nano2count(BASEPERIOD));
	/************create threads after socket is verified. ***************/
	pthread_t thread[2];
	pthread_attr_t thread_attr;

	//set the initialize and attribute for threads
	pthread_attr_init(&thread_attr);
	pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_JOINABLE);
	pthread_create(&thread[1], &thread_attr, thread_read1, &unifiedAttributes);

	while (!kbhit() && !unifiedAttributes.shutdownFlag) {
		// bzero: to "clean up" the buffer. The messages aren't always the same length...
		bzero(buffer, MSG_SIZE); // sets all values to zero. memset() could be used

		PRINT("Waiting for input:");

		// receive from a client. Either receive shutdown, new period, or
		int n = recvfrom(unifiedAttributes.sock, buffer, MSG_SIZE, 0, (struct sockaddr *) &unifiedAttributes.addr, &unifiedAttributes.fromlen);

		if (n < 0)
			error("recvfrom");
		char receivedFromIP[10];
		sprintf(receivedFromIP, "%s", inet_ntoa(unifiedAttributes.addr.sin_addr));
		printf("Message from IP %s\n", receivedFromIP);

		TRACE(printf("It says: %s\n", buffer));

		//client asked whois master
		if (strcmp(receivedFromIP, localIP) == 0) {
			//do nothing.
			PRINT("received a message from myself\nbroadcasting is working!");
		} else if (buffer[0] == 'W' && strncmp(buffer, "WHOIS", 5) == 0) {
			PRINT(" WHOIS detected.");
			//if we are active let them know

			TRACE(printf("  buffer says %s",buffer));
			//add terminating character at the end, this is what makes it a string.
			bzero(buffer, MSG_SIZE); // sets all values to zero. memset() could be used
			sprintf(buffer, "Server found, starting periodic\n");
			n = sendto(unifiedAttributes.sock, buffer, MSG_SIZE, 0, (struct sockaddr *) &unifiedAttributes.addr, unifiedAttributes.fromlen);
			if (n < 0) {
				error("sendto");
			} else {
				TRACE(printf("Message ,,,,%s,,,, sent successfully\n", buffer));
			}

		} else if (strcmp(buffer, "shutdown\n") == 0) {
			printf("SHUT DOWN COMMAND RECEIVED.\n");
			unifiedAttributes.shutdownFlag = 1;
		} else {
			//The unifiedAttributes.server should also disregard any invalid message.
			PRINT("Garbage detected and dropped");

		}

	}

	/**************************cleanup resources*********************/

	//drop whatever the user typed during the run.
	fflush(stdin);

	return 1;
}//end main program.


//the second sends data periodically.
void *thread_read1(void *arg) {
	PRINT("pthread1 created");

	unified_attributes * unifiedAttributes = (unified_attributes *) arg;
	PRINT(" read in pthread1 arguments.");
	RT_TASK* rttask = rt_task_init(nam2num("thd2"), 0, 512, 256);
	rt_task_make_periodic(rttask, rt_get_time(), TRANSMITPERIOD);
	char perdioicSendBuffer[MSG_SIZE];
	int TransmitSock, n;
	unsigned int txLength;
	struct sockaddr_in txSockConfig;
	char receivedFromIP[10];

	TransmitSock = socket(AF_INET, SOCK_DGRAM, 0); // Creates socket. Connectionless.
	if (TransmitSock < 0)
		error("socket");

	txSockConfig.sin_port = htons(atoi(unifiedAttributes->port)); // port field
	txSockConfig.sin_family = AF_INET; // symbol constant for Internet domain
	txLength = sizeof(struct sockaddr_in); // size of structure
	txSockConfig.sin_addr.s_addr = unifiedAttributes->addr.sin_addr.s_addr;
	bzero(perdioicSendBuffer, MSG_SIZE); // sets all values to zero. memset() could be used
	sprintf(perdioicSendBuffer, "Server found, starting periodic\n");
	sprintf(receivedFromIP, "%s", inet_ntoa(txSockConfig.sin_addr));
	printf("Client IP detected as %s\n", receivedFromIP);
	unsigned long sequenceNumber = 1;
	struct timeval packetSentOn;
	while (unifiedAttributes->shutdownFlag == 0) {

		// send message to client (Rx)
		rt_task_wait_period();
		n = sendto(TransmitSock, perdioicSendBuffer, MSG_SIZE, 0, (const struct sockaddr *) &txSockConfig, txLength);
		if (n < 0)
			error("Sendto");
		KALEN("\tSending with Thread 2");

		//pre build the message to reduce inconsistencies in the period.
		bzero(perdioicSendBuffer, MSG_SIZE); // sets all values to zero. memset() could be used
		if (gettimeofday(&packetSentOn, NULL) == 0) {
			TRACE(printf("Tx time = %ld.%06ld\t", packetSentOn.tv_sec, packetSentOn.tv_usec));
		} else {
			printf("gettimeofday() failed.\n");
			break;
		}
		sprintf(perdioicSendBuffer, "#,\t%ld,\t%ld.%06ld", sequenceNumber++, packetSentOn.tv_sec, packetSentOn.tv_usec);
	}

	PRINT("pthread1 destroyed");
	pthread_exit(0);
}
//blocking check for anything in the stdin buffer, but with a timeout of 0 sec and 0 usec.
//http://cc.byexamples.com/2007/04/08/non-blocking-user-input-in-loop-without-ncurses/
int kbhit() {
	struct timeval tv;
	fd_set fds;
	tv.tv_sec = 0;
	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);
}
