#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 <string.h>
#include "helper.c"
#define USAGE "Usage: trace -a <routetrace port> -b <source hostname> -c <source port> -d <destination hostname> -e <destination port> -f <debug option>\n"
#define OPSTRING "a:b:c:d:e:f:"

struct routetrace_options {
	int routetrace_port;
	char debug;
	struct sockaddr_in source_addr;
	struct sockaddr_in destination_addr;
	/* NON CLI OPTIONS */
	struct sockaddr_in my_addr;
} options;

void exit_usage(){
	fprintf(stderr, USAGE);
	exit(1);
}

void parse_options(int argc, char** argv){
	int opt;
	if(argc != 13) exit_usage();
	while((opt = getopt(argc, argv, OPSTRING)) != -1){
		switch(opt){
			case 'a':
				options.routetrace_port = options_parse_port();
				break;
			case 'b':
				/* get the source addr */
				str_to_sockaddr_in(&options.source_addr, optarg);
				break;
			case 'c':
				options.source_addr.sin_port = htons(options_parse_port());
				break;
			case 'd':
				/* get the destination addr */
				str_to_sockaddr_in(&options.destination_addr, optarg);
				break;
			case 'e':
				options.destination_addr.sin_port = htons(options_parse_port());
				break;
			case 'f':
				options.debug = atoi(optarg);
				if(options.debug < 0 || options.debug > 1)
					error("Priority must be one of (0,1).\n");
				break;
			case '?':
				exit_usage();
		}
	}
	// populate the source struct
	get_my_addr(&options.my_addr);
	options.my_addr.sin_port = htons(options.routetrace_port);

}

void probe(int sockfd, int ttl){
	struct packet packet;
	char *dest_str;
	char *source_str;
	packet.type = 'T';
	packet.ttl = ttl;
	packet.source = options.my_addr;
	packet.destination = options.destination_addr;
	packet_send_to_addr(sockfd, &packet, &options.source_addr);
	if(options.debug) {
		sockaddr_in_to_str(&source_str, &(packet.source));
		sockaddr_in_to_str(&dest_str, &(packet.destination));
		printf("Sent:     %c | %i | %s | %i | %s | %i | \n", 
				packet.type, ttl, source_str, 
				ntohs(packet.source.sin_port), 
				dest_str, ntohs(packet.destination.sin_port));
	}
}

struct packet* listen_for_reply(int listen_sockfd){
	struct packet *packet;
	char *dest_str;
	char *source_str;
	if(options.debug){
		printf("Waiting for probe response\n");
	}
	while((packet = packet_receive(listen_sockfd)) 
			&& packet->type != 'T') ;
	if(options.debug){
		sockaddr_in_to_str(&source_str, &packet->source);
		sockaddr_in_to_str(&dest_str, &packet->destination);
		printf("Received: %c | %i | %s | %i | %s | %i | \n", 
				packet->type, packet->ttl, source_str, 
				ntohs(packet->source.sin_port), 
				dest_str, ntohs(packet->destination.sin_port));
	}
	return packet;
}

void probe_and_listen(){
	int ttl = 0;
	char *dot_dec;
	struct packet *reply_packet;
	int listen_sockfd;
	int send_sockfd; 
	// socket for receiving packets
	listen_sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
	Bind(listen_sockfd, (struct sockaddr *) 
			&options.my_addr, sizeof(struct sockaddr));

	// socket for sending packets
	send_sockfd  = Socket(AF_INET, SOCK_DGRAM, 0);
	printf("Hop#  IP, Port\n");
	while(1){
		probe(send_sockfd, ttl++);
		reply_packet = listen_for_reply(listen_sockfd);
		if(reply_packet->ttl==1337) {
			printf("Host unreachable.\n");
			break;	
		}
		sockaddr_in_to_str(&dot_dec, &reply_packet->source);
		printf("%i    %s, %i\n", ttl, dot_dec, 
			ntohs(reply_packet->source.sin_port));
		if(host_cmp(&reply_packet->source, &options.destination_addr))
			break;
	}
}

int main(int argc, char* argv[]) {
	parse_options(argc, argv);
	probe_and_listen();
	return 0;
}
