#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "projcommon.c"
#define BUFLEN 512
#define NPACK 10
#define OPSTRING "p:q:f:l:"
#define DEBUG 1 
#define ALOG 1i
#define TIMELEN 30
#define FD struct forward_destination

void get_forwarding_info();
void parse_options(int argc, char** argv);

struct emulator_options{
	FILE *forwarding;
	int emulator_port;
	int queue_size;
	FILE *log;
	struct sockaddr_in my_addr;
	struct forward_destination *head;
}options;

struct forward_destination{
	struct sockaddr_in destination;
	struct sockaddr_in next_hop;
	double delay;
	int loss;
	struct forward_destination *next;
};



void parse_options(int argc, char** argv){
	int opt;

	if(argc != 9){
		fprintf(stderr, "Usage: emulator -p <port> -q <queue size> -f <filename> -l <log>\n");
		exit(1);
	}

	while((opt = getopt(argc, argv, OPSTRING)) != -1){
		switch(opt){
			case 'p':
				options.emulator_port = options_parse_port();
				break;
			case 'q':
				options.queue_size = atoi(optarg);
				if(options.queue_size < 1){
					error("Queue must be greater than 0\n");
				}
				break;
			case 'f':
				options.forwarding = Fopen(optarg, "r");
				break;
			case 'l':
				options.log = Fopen(optarg, "w");
				break;
			case '?':
				fprintf(stderr, "Usage: emulator -p <port> -q <queue size> -f <filename> -l <log>\n");
				exit(1);
		}
	}

	// populate the source struct
	get_my_addr(&options.my_addr);
	options.my_addr.sin_port = htons(options.emulator_port);

}

void get_forwarding_info(){

	char emul_addr[BUFLEN];
	int emul_port;
	char destination_addr[BUFLEN];
	int destination_port;
	char next_hop_addr[BUFLEN];
	int next_hop_port;
	int delay;
	int loss;
	struct sockaddr_in static_emul_addr; 
	struct forward_destination *tmp;

	options.head = NULL;

	while(fscanf(options.forwarding,"%s %i %s %i %s %i %i %i", emul_addr, 
				&emul_port, destination_addr, &destination_port,
				next_hop_addr, &next_hop_port, 
				&delay, &loss) != EOF){

		str_to_sockaddr_in(&static_emul_addr, emul_addr);

		if(options.my_addr.sin_addr.s_addr == static_emul_addr.sin_addr.s_addr &&
				options.emulator_port == emul_port){
		
			tmp = malloc(sizeof(struct forward_destination));

			str_to_sockaddr_in(&tmp->destination, destination_addr);
			tmp->destination.sin_port = htons(destination_port);

			str_to_sockaddr_in(&tmp->next_hop, next_hop_addr);
			tmp->next_hop.sin_port = htons(next_hop_port);

			tmp->delay = delay;
			tmp->loss = loss;

			tmp->next = options.head;
			options.head = tmp;
		}
	}
	Fclose(options.forwarding);
}

void log_info(struct packet* packet, char *error_message){

	// NEED: source IP address and port, the intended destination IP address
	// and port, the time of loss (to millisecond resolution), the priority 
	// level of the packet, and the size of the payload

	char source[BUFLEN];
	char destination[BUFLEN];
	int source_port = ntohs(packet->source.sin_port);
	int destination_port = ntohs(packet->destination.sin_port);
	char time_str[TIMELEN];

	getnameinfo(((struct sockaddr *) &packet->source), 
			sizeof(struct sockaddr), source, BUFLEN, NULL, 0, NI_NAMEREQD);
	getnameinfo(((struct sockaddr *) &packet->destination), 
			sizeof(struct sockaddr), destination, BUFLEN, NULL, 0, NI_NAMEREQD);

	char buffer[BUFLEN];
	get_current_time(time_str);

	sprintf(buffer, "%s <Source IP: %s  Source Port: %i  Destination IP: %s  Destination Port: %i  Time: %s  Priority: %i  Size: %i>\n",
			error_message, source, source_port, destination, destination_port, time_str, packet->priority, packet->length);

	Fwrite(buffer, sizeof(char), strlen(buffer), options.log);
	fflush(options.log);
}

/* This is for testing the get_forwarding_info function */

void print_static_info(){

	struct forward_destination *head = options.head;
	char destination[BUFLEN];
	char next_hop[BUFLEN];

	while(head != NULL){

		getnameinfo(((struct sockaddr *) &head->destination), 
				sizeof(struct sockaddr), destination, BUFLEN, NULL, 0, NI_NAMEREQD);
		getnameinfo(((struct sockaddr *) &head->next_hop), 
				sizeof(struct sockaddr), next_hop, BUFLEN, NULL, 0, NI_NAMEREQD);


		printf("-----------------------------------\n");
		printf("Destination Address/Port: %s/%i\n", destination, 
					head->destination.sin_port);
		printf("Next Hop Address/Port: %s/%i\n", next_hop, head->next_hop.sin_port);
		printf("Delay: %.3f\n", head->delay);
		printf("Loss: %i\n", head->loss);
		printf("-----------------------------------\n");

		head = head->next;
	}
}

struct forward_destination*  get_next_hop(struct packet* packet){
	
	struct forward_destination *head = options.head;


	while(head != NULL){
		if(head->destination.sin_addr.s_addr == packet->destination.sin_addr.s_addr
				&& head->destination.sin_port == packet->destination.sin_port){
			return head;
		}
		head = head->next;
	}
	log_info(packet, "No Forwarding Entry Found");
	return NULL;
}

/* This is for testing the log_info function */

void test_log_info(){
	struct packet* packet = malloc(sizeof(struct packet));

	packet->source = options.my_addr;
	packet->destination = options.my_addr;
	packet->length = 10;
	packet->priority = 2;

	log_info(packet, "TEST");
}

// run this function until a NULL is received, that way all packets that are 
// ready to go will get sent out
struct packet* find_packet_to_send(struct packet_q q[]){

	struct packet* packet;
	int i = 0;

	for(; i < 3; i++){
		packet_q_reset_iter(&q[i]);
		while((packet = packet_q_next(&q[i]))){
	//		printf("current delay: %.3f\n", ((FD *)packet->info)->delay / 1000);
			if((get_time() - q[i].current_time) >= (((FD *)packet->info)->delay / 1000)){
				packet_q_remove_current(&q[i]);
				return packet;
			}
			else
				return NULL;
		}
	}
	return NULL;
}

int main(int argc, char* argv[]){

	int sockfd;
	int sockfd2;
	int rand_loss;
	struct packet* recv_packet;
	struct packet* packet_to_send;
	struct sockaddr_in *next_hop;

	struct packet_q q[3];
	
	packet_q_new(&q[0]);
	packet_q_new(&q[1]);
	packet_q_new(&q[2]);

	parse_options(argc, argv);
	get_forwarding_info();
//	print_static_info();

	// socket for receiving packets
	sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
	Bind(sockfd,(struct sockaddr *) &options.my_addr, sizeof(struct sockaddr));

	// socket for sending packets
	sockfd2 = Socket(AF_INET, SOCK_DGRAM, 0);

	printf("Emulator running...\n");

	int index;

	while(1){
		recv_packet = nonblocking_receive_packet(sockfd);
		if(recv_packet != NULL){
//			printf("Packet Type %c\n", recv_packet->type);
//			printf("Packet Address: %i\n", recv_packet->destination.sin_addr.s_addr);
			if(recv_packet->priority < 1 || recv_packet->priority > 3){
//				fprintf(stderr, "Priority received: '%i'\n", 
//						recv_packet->priority);
				print_packet(recv_packet);
				//error("INVALID PRIORITY PACKET.");
				continue;
			}
				
			index = recv_packet->priority - 1;

			if(q[index].length >= options.queue_size){
//				printf("Packet was dropped\n");
				log_info(recv_packet, "Queue Full, Packet Dropped");
			}
			else{
				if((recv_packet->info = (void *)get_next_hop(recv_packet)) != NULL){
//					printf("Packet was enqueued\n");
					packet_q_enqueue(&q[index], recv_packet);
				}
				else{
//					printf("No next hop found\n");
				}
			}
		}

		/* this loop will keep running until there are no packets
		   to be sent that fulfill the delay stipulation */
		while((packet_to_send = find_packet_to_send(q)) != NULL){

			// now determine if the packets should be dropped
			// if not then send it away 
			// if yes then log it and drop it
			// free packet



			// used to determine if the packet should be dropped
			rand_loss = (rand() % 100) + 1;

			if(rand_loss > ((FD *)packet_to_send->info)->loss || packet_to_send->type == 'E'){
				// send the packet, otherwise it is dropped
				next_hop = &((FD *)packet_to_send->info)->next_hop;
//				printf("Sent to Address: %i\n", packet_to_send->destination.sin_addr.s_addr);

				packet_send_to_addr(sockfd2, packet_to_send, next_hop);
			}
			else{
				// log the fact that the packet was lost
				log_info(packet_to_send, "Packet Lost");
			}
			free(packet_to_send);
		}
	}
	Fclose(options.log);
	return 0;
}

