#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:"
#define DEBUG 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 */
}options;



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

	if(argc != 9){
		fprintf(stderr, "Usage: requester -p <port> -s <hostname> -g <sender port> -o <file option>\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 '?':
				fprintf(stderr, "Usage: requester -p <port> -s <hostname> -g <sender port> -o <file option>\n");
				exit(1);
		}
	}
}

void make_request_packet(struct packet* req_packet){

	req_packet->type = 'R';
	req_packet->sequence = options.file_option;
	req_packet->length = 0;
	req_packet->payload = NULL;
}


void send_packet(int sockfd,struct packet* req_packet, const SA *pservaddr, socklen_t servlen){
	unsigned int buffer_size;
//	Sendto(sockfd, packet_to_buffer(req_packet, &buffer_size), buffer_size, 0, pservaddr, servlen);

	uint8_t *buffer;
	buffer = packet_to_buffer(req_packet, &buffer_size);
		
	if(sendto(sockfd, buffer, buffer_size, 0, pservaddr, servlen) == -1){
		error("Error in the sendto function!\n");
	}

	printf("\nSent request for file: %i.txt\n",options.file_option);
}

void receive_packets(double start_time){

	uint8_t buffer[MAXLEN];
	struct packet* recv_packet;
	struct sockaddr_in my_addr;
	int sockfd;
	int total_packets = 0;
	int total_bytes = 0;
	double rec_time;

	sockfd = Socket(AF_INET, SOCK_DGRAM, 0);	

	memset((char *) &my_addr, 0, sizeof(my_addr));
	my_addr.sin_family = AF_INET;
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	my_addr.sin_port = htons(options.wait_port);

 	Bind(sockfd,(SA *) &my_addr, sizeof(my_addr));

	char file_name[8];
	int fp;

	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++;

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

		total_bytes += recv_packet->length;
//		print_packet_stats(recv_packet);
		alog_print_packet(recv_packet, 0);

		Write(fp, recv_packet->payload, recv_packet->length);
	}
}

void print_packet_stats(struct packet *packet){

	int i = 0;
	// add in the timing stuff
	
	printf("Time: %s ", get_current_time());
	printf("From: %s  ", options.dot_dec_addr);
	printf("Seq #: %i  ", packet->sequence);
	printf("Length: %i  ", packet->length);
	printf("Data: ");
	for(; i < 4; i++){
		printf("%c", *(packet->payload + i));
	}
	printf("\n");

}

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[]){
	int sockfd;
	struct sockaddr_in serv_addr;
	struct packet request_packet;
	double start_time;

	parse_options(argc, argv);

	// set up the socket to send and reveive packets on
	sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
	
	// clear the struct
	memset((char *) &serv_addr, 0, sizeof(serv_addr));

	// populate the struct
	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");
	}

	make_request_packet(&request_packet);
	start_time = get_time();
	send_packet(sockfd, &request_packet, (struct sockaddr*) &serv_addr, sizeof(serv_addr));

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

	return 0;
}
