#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:g:r:l:f:h:i:t:"
#define DEBUG 1 
#define ALOG 1
#define PACKET_ERR "ERROR: had to try 10 times to send a packet. Giving up on this packet."


struct sender_req_options{
	unsigned int port;
	unsigned int requester_port;
	int rate;
	int sequence_number;
	int current_sequence_number;
	int length;
	double over_all_sent;
	unsigned int emulator_port;
	struct sockaddr_in emulator_addr;
	char priority;
	double timeout;
	uint32_t window;
	int in_sockfd;
	int out_sockfd;
	double num_resent;
} options;

struct packet * wait_for_request_packet();


void parse_options(int argc, char* const argv[]){
	int opt;
	if(argc != 17){
		printf("Usage: sender -p <port> -g <requester port> -r <rate> -l <length> -f <f_hostname> -h <f_port> -i <priority> -t <timeout>\n");
		exit(1);
	}
	while((opt = getopt(argc, argv, OPSTRING)) != -1){
		switch(opt){
			case 'p':
				options.port = options_parse_port();
				break;
			
			case 'g':
				options.requester_port = options_parse_port(); 
				break;
			
			case 'r':
				options.rate = atoi(optarg);
				if(options.rate < 1 || options.rate > 500)
					error("Non-positive rate. Must be 1-500.\n");
				break;
			/*
			**  Removed the 'q' cli option. 
			case 'q':
				options.sequence_number = atoi(optarg);
				if(options.sequence_number < 1){
					error("Sequence can't be negative\n");
				}
				break;
			*/
			case 'l':
				options.length = atoi(optarg);
				if(options.length < 1){
					error("Length must be greater than 0\n");
				}
				break;
			case 'f':
				/* get the emulator addr */
				str_to_sockaddr_in(&options.emulator_addr, optarg);
				break;
			
			case 'h':
				options.emulator_addr.sin_port = htons(options_parse_port());
				break;
			
			case 'i':
				options.priority = atoi(optarg);
				if(options.priority < 1 || options.priority > 3)
					error("Priority must be one of (1,2,3).\n");
				break;
			
			case 't':
				options.timeout = atoi(optarg);
				if(options.timeout < 1){
					error("Timeout must be greater than 0\n");
				}
				break;
			
			case '?':
				printf("Usage: sender -p <port> -g <requester port> -r <rate> -q <seq_no> -l <length>\n");
				exit(1);
		}
	}
}
struct packet* read_in_packet(struct packet* template_packet, FILE *f){
	struct packet* packet = malloc(sizeof(struct packet));
	packet->payload = malloc(options.length * sizeof(uint8_t));
	/* read in data */
	packet->length = fread(packet->payload, sizeof(uint8_t), options.length, f);
	/* update sequence number */
	packet->sequence = options.current_sequence_number++;
	packet->destination = template_packet->destination;
	packet->source = template_packet->source;
	packet->type = template_packet->type;
	packet->dot_addr = template_packet->dot_addr;
	packet->priority = options.priority;
	return packet;
}



void send_queue(struct packet_q *q){
	struct packet *packet;
	packet_q_reset_iter(q);
	struct timespec sleep_amount;
	sleep_amount.tv_sec = 0;
	sleep_amount.tv_nsec = (1.0/((double) (options.rate == 1 ? 1.001 : options.rate) ))*1e9;
	while((packet = packet_q_next(q))) {
		packet_send_to_addr(options.out_sockfd, packet, &options.emulator_addr);
		options.over_all_sent ++;
		/* delay to achieve desired sending rate*/
		if(ALOG) alog_print_packet(packet, 1);
		nanosleep(&sleep_amount, NULL);
	}
}

void remove_sequence_number(struct packet_q *q, int sequence){
	struct packet *packet;
	packet_q_reset_iter(q);
	while((packet = packet_q_next(q))) {
		if(packet->sequence == sequence){
			packet_q_remove_current(q);
			return;
		}
	}
	/* hm, no sequence like that found! */
	/* TODO Examine why this happens for HUGE ack packet delays and make sure this 
	** is reasonable */
	/*printf("Warning: packet received for unknown sequence: '%i'\n", sequence);*/
}

void wait_for_acks_and_prune(struct packet_q *q){
	struct packet *ack_packet = NULL;
	struct packet *packet;

	while(q->length > 0){
		ack_packet = nonblocking_receive_packet(options.in_sockfd);
		if(ack_packet != NULL){
			if(ack_packet->type == 'A'){ /* is an ack packet */
				if(ALOG) printf("Received ack for %i\n", ack_packet->sequence);
				remove_sequence_number(q, ack_packet->sequence);
			} /*else printf("Warning: packet received that's not an ACK packet; waiting more."); */
		}
		packet_q_reset_iter(q);
		while((packet = packet_q_next(q))){
			if((get_time() - q->current_time) > (options.timeout/1000)){
				/*printf("Resending data\n");*/
				/* resend this data packet */
				packet_send_to_addr(options.out_sockfd, packet, &options.emulator_addr);
				packet_q_reset_current_time(q);
				options.over_all_sent ++;
				options.num_resent ++;
			} 
			if (q->current_resets > 10){
				if(ALOG) printf("Reached Max retry %i\n", 
						packet->sequence);
				else fprintf(stderr, PACKET_ERR);
				packet_q_remove_current(q);
			}
		}
	}
}

struct packet * make_end_packet(struct packet *  template_packet){
	struct packet* packet = malloc(sizeof(struct packet));
	packet->payload = NULL;
	packet->length = 0;
	packet->sequence = options.current_sequence_number++;
	packet->destination = template_packet->destination;
	packet->source = template_packet->source;
	packet->type = 'E';
	packet->dot_addr = template_packet->dot_addr;
	packet->priority = options.priority;
	return packet;
}

void send_window(struct packet_q *q){
	while(q->length > 0){ /* while there are packets left */
		send_queue(q);
		wait_for_acks_and_prune(q);
	}
}

void send_file(struct packet* request_packet){
	char filename[18];
	struct packet template_packet;
	struct packet *packet = &template_packet;
	FILE *f;
	struct packet_q q;
	int current_window;
	/* Make the filename */
	sprintf(filename, "%i.txt", request_packet->sequence);
	if(ALOG) printf("Received request for file: %s\n\n", filename);
	/* open the file for reading */
	f = Fopen(filename, "r");
	template_packet.length = options.length;
	template_packet.type = 'D';
	/* Now open a new socket */ 
	options.out_sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
	/*memset((char *) &packet.destination, 0, sizeof(packet.destination));*/
	options.current_sequence_number = options.sequence_number;

	/* set the destination to be the source of the received packet, but 
	** override the port with the user specified port */
	template_packet.destination = request_packet->source;
	template_packet.destination.sin_port = htons(options.requester_port);
	template_packet.source = request_packet->destination;
	template_packet.source.sin_port = htons(options.port);

	/* set the window size */
	options.window = ntohl(*((uint32_t*) request_packet->payload));
	
	/* Populate the dot_addr field */
	template_packet.dot_addr = malloc(LENGTH_IPV4);
	if(inet_ntop(AF_INET, &template_packet.destination.sin_addr, 
				template_packet.dot_addr, LENGTH_IPV4) == 0) 
		error("inet_pton() failed\n");
	while(packet->length == options.length){
		/* Build a windows worth of packets */
		current_window = options.window;
		packet_q_new(&q);
		while(packet->length == options.length && current_window--){
			packet = read_in_packet(&template_packet, f); 
			packet_q_enqueue(&q, packet);
		}
		/* send this widnow */
		send_window(&q);
	}
	fclose(f);
	/* Now build and send the end packet, and put it in a single window */ 
	packet_q_new(&q);
	packet = make_end_packet(&template_packet);
	packet_q_enqueue(&q, packet);
	send_window(&q);
}

struct packet * wait_for_request_packet(){
	struct packet *request_packet;
	struct sockaddr_in my_addr;
	memset((char *) &my_addr, 0, sizeof(my_addr));
	options.in_sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
	my_addr.sin_family = AF_INET;
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	my_addr.sin_port = htons(options.port);
	Bind(options.in_sockfd, (struct sockaddr*) &my_addr, sizeof(my_addr));
	while(1) {
		if(ALOG) printf("Waiting for request packet\n");
		request_packet = packet_receive(options.in_sockfd);
		if(request_packet->type != 'R')
			fprintf(stderr, "Received a packet which is not a request packet.");
		else break;
	}
	return request_packet;
}

int main(int argc, char* const argv[]) {
	parse_options(argc, argv);
	/* Hard coded the sequence number to one.... */
	options.sequence_number = 1;
	options.over_all_sent = 0;
	options.num_resent = 0;
	send_file(wait_for_request_packet());
	printf("Over all packets sent: %.3f\n", options.over_all_sent);
	printf("Number of packets resent: %.3f\n", options.num_resent);
	printf("Percentage of packets lost: %.3f\n", (options.num_resent / options.over_all_sent) * 100);
	return 0;
}

