#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 BUFLEN 512
#define NPACK 10
#define OPSTRING "p:f:"
#define DEBUG 1 
#define ALOG 1i
#define TIMELEN 30
#define WAIT 100
#define HB_TO 500
#define LSP_TO 2000

void parse_options(int argc, char** argv);
void print_static_info(struct host* host);
void send_lsp(int sockfd, struct host_q* q, struct host* host, int alive);


struct emulator_options{
	FILE *topology;
	int emulator_port;
	struct sockaddr_in my_addr;
	struct host* self;
}options;



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

	if(argc != 5){
		fprintf(stderr, "Usage: emulator -p <port> -f <filename>\n");
		exit(1);
	}

	while((opt = getopt(argc, argv, OPSTRING)) != -1){
		switch(opt){
			case 'p':
				options.emulator_port = options_parse_port();
				break;
			case 'f':
				options.topology = Fopen(optarg, "r");
				break;
			case '?':
				fprintf(stderr, "Usage: emulator -p <port> -f <filename>\n");
				exit(1);
		}
	}

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

}

void parse_on_comma(char* line, char* address, int* port){

	char str_port[5];

	int i = 0;
	int k = 0;

	for( ; i < strlen(line); i++){
		if(line[i] == ','){
			i++;
			break;
		}
		else{
			address[i] = line[i];
		}
	}

	for(; i < strlen(line); i++){
		str_port[k] = line[i];
		k++;
	}

	str_port[k] = '\0';

	*(port) = atoi(str_port);
}

void readtopology(struct host_q* q){

	char *line;
	char host_line[BUFLEN];
	char neighbor_line[BUFLEN];
	char host_address[BUFLEN];
	int host_port;
	char neighbor_address[BUFLEN];
	int neighbor_port;
	struct sockaddr_in* cur_sock;
	struct host *cur_host;

	line = malloc(sizeof(char) * BUFLEN);
	cur_sock = malloc(sizeof(struct sockaddr_in));

	while(fgets(line, BUFLEN, options.topology) != NULL){
		sscanf(line, "%s ", host_line);
		parse_on_comma(host_line, host_address, &host_port);
		line = line + (strlen(host_line) + 1);
		str_to_sockaddr_in(cur_sock, host_address);
		cur_sock->sin_port = htons(host_port);
		cur_host = host_q_fetch_or_make(q, cur_sock);
		if(host_cmp(&options.my_addr, cur_sock)){
			options.self = cur_host;
		}
		cur_host->is_enabled = 1;

		while(sscanf(line, "%s ", neighbor_line) != EOF){
			parse_on_comma(neighbor_line, neighbor_address, &neighbor_port);
			line = line + (strlen(neighbor_line) + 1);
			str_to_sockaddr_in(cur_sock, neighbor_address);
			cur_sock->sin_port = htons(neighbor_port);
			cur_host->edges[cur_host->edge_count] = host_q_fetch_or_make(q, cur_sock);
			cur_host->edge_count++;
		}
		cur_host->edges[cur_host->edge_count] = NULL;
	}
	Fclose(options.topology);
}

void print_static_info(struct host* host){
	char self[BUFLEN];
	char edge[BUFLEN];
	int index = 0;
	
	getnameinfo(((struct sockaddr*) &(host->address)), 
				sizeof(struct sockaddr), self, BUFLEN, NULL, 0, NI_NAMEREQD);

	printf("-----------------------------------------\n");
	printf("self: %s\n", self);


	while(host->edges[index] != NULL){

		getnameinfo(((struct sockaddr*) &(host->edges[index]->address)), 
				sizeof(struct sockaddr), edge, BUFLEN, NULL, 0, NI_NAMEREQD);

		printf("edge: %s\n ", edge);
		index++;
	}
	printf("-----------------------------------------\n");
}

void send_to_routetrace(int sockfd, struct packet* packet){

	packet->destination = packet->source;
	packet->source = options.my_addr;

	packet_send_to_addr(sockfd, packet, &packet->destination);

}

void forward_packet_to_next_hop(int sockfd, struct host_q *q, 
		struct packet* packet){
	struct host* host;	
	host_q_reset_iter(q);
	while((host = host_q_next(q))){
		if(host_cmp(&host->address, &packet->destination)){
			if(host->next_hop){
				packet_send_to_addr(sockfd, packet, &host->next_hop->address);	
			}
			else{
				packet->ttl = 1337;
				send_to_routetrace(sockfd, packet);
			}
		}
	}
}

void update_alive(struct host_q *q, struct packet* packet, struct host* self, int sockfd, struct host_graph* host_graph){

	struct host* host;

	host_q_reset_iter(q);
	while((host = host_q_next(q))){

		if(host_cmp(&host->address, &packet->source)){
			if(host->is_enabled == 0){
				host->is_enabled = 1;
				send_lsp(sockfd, q, host, 1);
				host_graph_dijkstra(host_graph);
				host->last_time = get_time();
				break;
			} else {
				host->last_time = get_time();
				host_q_reset_current(q);
				break;
			}
		}
	}
}

// don't ever use this function
int is_up(struct host_q* q, struct host* host){

	struct host* cur_host;

	while((cur_host = host_q_next(q))){
		if(cur_host->address.sin_addr.s_addr == host->address.sin_addr.s_addr){
			return cur_host->is_enabled;
		}
	}

	printf("Host error 000.\n");
	return -1;
}


// we need to check to see which hosts in the q

void look_for_dead(struct host_q *q, struct host* self, int sockfd, struct host_graph* host_graph){

	struct host* host;
	double time = get_time();
	int cur_edge = 0;

	host_q_reset_iter(q);
	while((host = self->edges[cur_edge]) != NULL){

		// not sure about this comparison for the time
		// if it is greater than this time than the
		// host has missed 4 heartbeats
		if(((time - host->last_time) * 1000) >= LSP_TO){
			if(host->is_enabled == 1){
				host->is_enabled = 0;

				// tell neighbors that a node is down
				send_lsp(sockfd, q, host, 0);
				host_graph_dijkstra(host_graph);
			}
		}
		cur_edge++;
	}
}

void send_lsp(int sockfd, struct host_q* q, struct host* host, int alive){
	struct packet lsp;
	struct host* cur_host;

	lsp.type = 'L';
	lsp.ttl = alive;
	// put the node that is dead in the source position
	lsp.source = host->address;

	host_q_reset_iter(q);
	while((cur_host = host_q_next(q))){
		lsp.destination = cur_host->address;
		packet_send_to_addr(sockfd, &lsp, &lsp.destination);
	}
}

void send_heartbeat(int sockfd, struct host* self){
	struct packet heartbeat_packet;
	int cur_edge = 0;

	heartbeat_packet.type = 'H';
	heartbeat_packet.source = self->address;
	heartbeat_packet.ttl = 0;

	while(self->edges[cur_edge] != NULL){
		heartbeat_packet.destination = self->edges[cur_edge]->address;
		packet_send_to_addr(sockfd, &heartbeat_packet, &heartbeat_packet.destination);
		cur_edge++;
	}
}

struct host* find_host(struct host_q* q, struct sockaddr_in* node){

	struct host* host;

	host_q_reset_iter(q);
	while((host = host_q_next(q))){
		if(host_cmp(&host->address, node)){
			return host;
		}
	}
	printf("Host error 101\n");
	host_q_reset_iter(q);
	return host_q_next(q);
}

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

	int sockfd;
	int sockfd2;
	double last_heart;
	struct host_q q;
	struct host_graph host_graph;  
	struct packet* recv_packet;
	struct host* changed_host;
	int cur_edge;

	host_q_new(&q);

	parse_options(argc, argv);
	readtopology(&q);

	host_graph_new(&host_graph, &q);
	host_graph.self = options.self;
	host_graph_dijkstra(&host_graph);

	// 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");

	last_heart = get_time();
	while(1){

//		recv_packet = packet_receive_timeout(sockfd, WAIT); 
		recv_packet = nonblocking_receive_packet(sockfd);

		if(recv_packet != NULL){
			if(recv_packet->type == 'T'){
				if(recv_packet->ttl == 0){
					send_to_routetrace(sockfd2, recv_packet);
				}
				else if(host_cmp(&recv_packet->destination, &options.my_addr) == 0){
					if(recv_packet->ttl > 0){
						recv_packet->ttl--;
						forward_packet_to_next_hop(sockfd2, &q, recv_packet);
					}
					else if(recv_packet->ttl < 0){
						printf("ttl is unusual\n");
					}
				}
			}
			else if(recv_packet->type == 'L'){
				changed_host = find_host(&q, &recv_packet->source);
				changed_host->is_enabled = recv_packet->ttl;
				host_graph_dijkstra(&host_graph);
			}
			else if(recv_packet->type == 'H'){
				update_alive(&q, recv_packet, host_graph.self, sockfd2, &host_graph);
			}
			else {
				printf("Received Unusual Packet\n");
			}
		}

		// need to check to see is there are any dead nodes
		look_for_dead(&q, host_graph.self, sockfd2, &host_graph);

		// check to see if a heartbeat needs to be sent
		if(((get_time() - last_heart) * 1000) > HB_TO){
			cur_edge = 0;
			// send heartbeats to all neighbors
			send_heartbeat(sockfd2, host_graph.self);
			last_heart = get_time();
		}
	}

	return 0;
}

