#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 SA struct sockaddr
#define OPSTRING "p:s:g:o:w:f:h:"
#define DEBUG 0 
#define DEBUG2 0

void print_packet_stats(struct packet *packet);
void print_overall_stats(int total_bytes, int total_packets, double total_time);

struct req_options{
	int wait_port;		/* my port, port that this machine is waiting on */
	char* hostname;		/* hostname passed in through command line that is used to fetch the host info */
	int sender_port;	/* their port, the port that the other machine is listening on */
	int file_option;
	struct hostent *host;
	char dot_dec_addr[16]; 	/* dot decimal address of where to send the packets to */
	int window;
	struct sockaddr_in next_hop;
}options;



void parse_options(int argc, char* const argv[]){
	int opt;
	int port_num;

	if(argc != 15){
		fprintf(stderr, "Usage: requester -p <port> -s <hostname> -g <sender port> -f <f_hostname> -h <f_port> -o <file option> -w <window>\n");
		exit(1);
	}

	while((opt = getopt(argc, argv, OPSTRING)) != -1){
		switch(opt){
			case 'p':
				options.wait_port = atoi(optarg);
				if(DEBUG)printf("The requester's wait port is %i\n",
						options.wait_port);
				if(options.wait_port < 1024 || 
						options.wait_port > 65536){
					error("Invalid entry for port. Must be in range 1024 - 65536\n");
				}
				break;

			case 's':
				options.hostname = optarg;
				if(DEBUG)printf("The hostname is %s\n", 
						options.hostname);

				if((options.host = gethostbyname(options.hostname)) == NULL){
					error("Could not get the address of the host\n");
				}
				
				if((inet_ntop(options.host->h_addrtype, *(options.host->h_addr_list), options.dot_dec_addr, 16)) == 0){
					error("Error with inet_ntop\n");
				}
				
				if(DEBUG){
					printf("The address for this host is %s\n", options.dot_dec_addr);
				}

				break;
			
			case 'g':
				options.sender_port = atoi(optarg);
				if(DEBUG)printf("The port that the sender is waiting on is %i\n", options.sender_port);
				if(options.sender_port < 1024 || 
						options.sender_port > 65536){
					error("Invalid entry for sender port. Must be in range 1024 - 65536\n");
				}
				break;

			case 'o':
				options.file_option = atoi(optarg);
				if(DEBUG)printf("The file option is set to %i\n", options.file_option);
				if(options.file_option != 1 && options.file_option != 2 && options.file_option != 3){
					error("The file option needs to be set to 1, 2, or 3\n");
				}
				break;
			case 'w':
				options.window = atoi(optarg);
				if(options.window < 1){
					error("The window must be greater than 0\n");
				}
				break;
			case 'f':
				str_to_sockaddr_in(&options.next_hop, optarg);
				break;
			case 'h':
				port_num = atoi(optarg);
				if(port_num < 1024 || port_num > 65536){
					error("Invalid entry for f_port. Must be in range 1024 - 65536\n");
				}
				options.next_hop.sin_port = htons(port_num);
				break;
			case '?':
				fprintf(stderr, "Usage: requester -p <port> -s <hostname> -g <sender port> -f <f_hostname> -h <f_port> -o <file option> -w <window>\n");
				exit(1);
		}
	}
}

void send_request_packet(struct sockaddr_in *my_addr, struct sockaddr_in *serv_addr, int sockfd){
	struct packet req_packet;

	req_packet.type = 'R';
	req_packet.sequence = options.file_option;
	req_packet.length = sizeof(uint32_t);
	req_packet.payload = malloc(sizeof(uint32_t));
	*((uint32_t*) req_packet.payload) = htonl( (uint32_t) options.window);
//	printf("Payload: %i\n", *((uint32_t*)req_packet.payload));
	req_packet.source = *(my_addr);
	req_packet.destination = *(serv_addr);
	req_packet.priority = 1;

	packet_send_to_addr(sockfd, &req_packet, &options.next_hop);

}

void send_ack_packet(int sequence, struct sockaddr_in *my_addr, struct sockaddr_in *serv_addr, int sockfd){

	struct packet ack_packet;

	ack_packet.type = 'A';
	ack_packet.sequence = sequence;
	ack_packet.source = *(my_addr);
	ack_packet.destination = *(serv_addr);
	//ack_packet.payload = NULL;
	ack_packet.length = sizeof(uint32_t);
	ack_packet.payload = malloc(sizeof(uint32_t));
	*((uint32_t*) ack_packet.payload) = htonl( (uint32_t) options.window);
	//ack_packet.length = 0;
	ack_packet.priority = 1;

	if(DEBUG2) printf("Sending ack packet...");
	if(DEBUG2) print_packet(&ack_packet);
	packet_send_to_addr(sockfd, &ack_packet, &options.next_hop);
	printf("Sent ACK: %i\n", sequence);
}

int check_destination(struct packet *packet, struct sockaddr_in *my_addr){
	/*print_packet(packet);
	print_sockaddr(my_addr);*/
	if(packet->destination.sin_addr.s_addr != my_addr->sin_addr.s_addr){
		printf("Packet was received that was destined for another machine\n");
		return 1;
	}

	return 0;

}

int write_to_file(int fp, struct packet_q *q){


	struct packet* packet;
	int first_sequence_num;

	if(q->length > 0){

		packet_q_reset_iter(q);

		packet = packet_q_next(q);
		first_sequence_num = packet->sequence;
		Write(fp, packet->payload, packet->length);
		packet_q_remove_current(q);
		free(packet);

		while((packet = packet_q_next(q))){
			Write(fp, packet->payload, packet->length);
			packet_q_remove_current(q);
			free(packet);
		}
		return first_sequence_num;
	}
	return -1;
}

void receive_packets(double start_time, struct sockaddr_in *my_addr, struct sockaddr_in *serv_addr, int sock_sending){

	uint8_t buffer[MAXLEN];
	struct packet* recv_packet;
	int sockfd;
	int total_packets = 0;
	int total_bytes = 0;
	double rec_time;
	struct packet_q q;
	int min_window = 0;
	int max_window = INT_MAX;
	int fp;
	char file_name[8];

	sockfd = Socket(AF_INET, SOCK_DGRAM, 0);	
	Bind(sockfd,(SA *) my_addr, sizeof(*(my_addr)));

	sprintf(file_name, "log%i.txt", options.file_option);
	fp = open(file_name ,O_RDWR | O_CREAT | O_TRUNC,  S_IRWXU);

	printf("Output File: %s\n\n", file_name);

	while(1){
		Recvfrom(sockfd, buffer, MAXLEN, 0, NULL, NULL);
		rec_time = get_time() - start_time;

		recv_packet = buffer_to_packet(buffer);
		recv_packet->dot_addr = options.dot_dec_addr;
		total_packets++;


		// make sure the packet received was meant for this requester

		if(check_destination(recv_packet, my_addr) == 0){
			if(DEBUG2) print_packet(recv_packet); 

			send_ack_packet(recv_packet->sequence, my_addr, serv_addr, sock_sending);

			if(recv_packet->type == 'E'){
				print_overall_stats(total_bytes, total_packets, rec_time);
				write_to_file(fp, &q);
				break;
			}

			total_bytes += recv_packet->length;
		//	alog_print_packet(recv_packet, 0);	// don't print individual packet data

			if(recv_packet->sequence >= min_window && recv_packet->sequence <= max_window){
				packet_q_add_by_sequence(&q, recv_packet);
			}
			if(q.length >= options.window){
				min_window = write_to_file(fp, &q);
				min_window += options.window;
				max_window = min_window + options.window;
			}
		}
	}
}


void print_overall_stats(int total_bytes, int total_packets, double total_time){
	printf("\n");
	printf("Total Packets: %i\n", total_packets);
	printf("Total Bytes: %i\n", total_bytes);
	printf("Avg Packets/Second: %.3f\n", total_packets/total_time);
	printf("Total Time: %.3f seconds\n", total_time);

}

int main(int argc, char *argv[]){
	struct sockaddr_in serv_addr;
	double start_time;
	struct sockaddr_in my_addr;
	int sock_sending;

	sock_sending = Socket(AF_INET, SOCK_DGRAM, 0);

	parse_options(argc, argv);

	// this is to populate the source struct 
	get_my_addr(&my_addr);
	my_addr.sin_port = htons(options.wait_port);
	
	// set up the destination struct
	memset((char *) &serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = options.host->h_addrtype;
	serv_addr.sin_port = htons(options.sender_port);
	if (inet_pton(options.host->h_addrtype, options.dot_dec_addr, &serv_addr.sin_addr) == 0) {
		error("inet_pton() failed\n");
	}

	start_time = get_time();
	send_request_packet(&my_addr, &serv_addr, sock_sending);

	// then need a function that will listen for the response and print out the logistics.
	receive_packets(start_time, &my_addr, &serv_addr, sock_sending);

	return 0;
}
