#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 "helper.c"
#define OKAY_TEST_COUNT (16)
#define HOST_NUMBER (32)


char *current_test = NULL;
void assert(int test, const char* message){
	if(!test){
		fflush(stderr);
		fprintf(stderr, "%-16s [ FAILED ]  %-64s \n", 
				current_test, message);
		exit(1);
	}
}

char num_to_buffer_utest(){
	uint8_t *b;
	b = malloc(8);
	uint32_t original = 1235457890;
	uint32_t result;
	uint16_t original16 = 12354;
	uint16_t result16;
	char success = 1;
	num_to_buffer32(b, original);
	result = buffer_to_num32(b);
	if(result != original){
		printf("Correct: %i, Result: %i", original, result);
		printf("num_to_buffer & buffer_to_num (32): FAIL\n");
		success = 0;
	} else {
	}
	b = malloc(8);
	num_to_buffer16(b, original16);
	result16 = buffer_to_num16(b);
	if(result16 != original16){
		printf("Correct: %i, Result: %i", original16, result16);
		printf("num_to_buffer & buffer_to_num (16): FAIL\n");
		success = 0;
	} else {
	}
	return success;
}

char packet_to_buffer_utest(){
	uint8_t *buffer;
	struct packet *original_packet = malloc(sizeof(struct packet));
	struct packet *result_packet;
	int i;
	unsigned int buffer_size;
	original_packet->type = 'R';
	original_packet->source.sin_addr.s_addr = 0xFEE1900D;
	original_packet->source.sin_port = 1337;
	original_packet->destination.sin_addr.s_addr = 0x900DFEE1;
	original_packet->destination.sin_port = 6969;
	buffer = packet_to_buffer(original_packet, &buffer_size);

	result_packet = buffer_to_packet(buffer);
	/* SOURCE */
	if(result_packet->source.sin_addr.s_addr != 
			original_packet->source.sin_addr.s_addr) {
		printf("source.");
		printf("sin_addr: Correct: %016x, Result: %016x\n", 
				original_packet->source.sin_addr.s_addr,
				result_packet->source.sin_addr.s_addr);
		printf("packet_to_buffer & buffer_to_packet: FAIL");
		return 0;
	}

	if(result_packet->source.sin_port != 
			original_packet->source.sin_port) {
		printf("source.");
		printf("sin_port: Correct: %016x, Result: %016x\n", 
				original_packet->source.sin_port,
				result_packet->source.sin_port);
		printf("packet_to_buffer & buffer_to_packet: FAIL");
		return 0;
	}
	/* DESTINATION */
	if(result_packet->destination.sin_addr.s_addr != 
			original_packet->destination.sin_addr.s_addr) {
		printf("destination.");
		printf("sin_addr: Correct: %016x, Result: %016x\n", 
				original_packet->destination.sin_addr.s_addr,
				result_packet->destination.sin_addr.s_addr);
		printf("packet_to_buffer & buffer_to_packet: FAIL");
		return 0;
	}

	if(result_packet->destination.sin_port != 
			original_packet->destination.sin_port) {
		printf("destination.");
		printf("sin_port: Correct: %016x, Result: %016x\n", 
				original_packet->destination.sin_port,
				result_packet->destination.sin_port);
		printf("packet_to_buffer & buffer_to_packet: FAIL");
		return 0;
	}



	if(result_packet->type != original_packet->type) {
		printf("type: Correct: %c, Result: %c\n", original_packet->type,
				result_packet->type);
		printf("packet_to_buffer & buffer_to_packet: FAIL");
		return 0;
	}
	i=0 ;
	return 1;	
}

void host_q_utest(){
	int i;
	struct host hosts[HOST_NUMBER];
	struct host_q q;
	struct host *next;
	host_q_new(&q);
	i = 0;
	while(i < HOST_NUMBER){
		hosts[i].last_sequence = i;
		hosts[i].last_time = i * HOST_NUMBER;
		hosts[i].next_hop = &hosts[(i+1) % HOST_NUMBER]; 
		host_q_enqueue(&q, &hosts[i]);
		i++;
	}
	assert(q.length ==  HOST_NUMBER, "Length check after adding hosts.");
	host_q_reset_iter(&q);
	i = 0;
	while(i < HOST_NUMBER){
		next = host_q_next(&q);
		assert(next != NULL, "check if next is NULL prematurely.");
		assert(hosts[i].last_sequence == next->last_sequence, 
				"make sure sequence numbers match");
		i++;
	}
	next = host_q_next(&q);
	assert(next == NULL, "check if next is NULL at the right time.");
	host_q_reset_iter(&q);
	i = 0;
	while(i < 13){
		next = host_q_next(&q);
		assert(next != NULL, "check if next is NULL prematurely.");
		assert(hosts[i].last_sequence == next->last_sequence, 
				"make sure sequence numbers match");
		i++;
	}
	assert(next->last_sequence == 12, "right now is 12");
	assert(q.length ==  HOST_NUMBER, "Length check after adding hosts. (2)");
	host_q_reset_current(&q);
	assert(q.length ==  HOST_NUMBER, "Length check after adding hosts. (3)");
	host_q_reset_iter(&q);
	i = 0;
	while(i < 13){
		next = host_q_next(&q);
		assert(next != NULL, "check if next is NULL prematurely.");
		i++;
	}
	assert(next->last_sequence == 13, "is correctly 13");
	while(i < HOST_NUMBER){
		next = host_q_next(&q);
		assert(next != NULL, "check if next is NULL prematurely.");
		i++;
	}
	assert(next->last_sequence == 12, "12 got correctly put on end");
	next = host_q_next(&q);
	assert(next == NULL, "next is null which is good");
	/* now try something more rigorous */
	host_q_new(&q);
	i = 0;
	while(i < HOST_NUMBER){
		hosts[i].last_sequence = i;
		hosts[i].last_time = i * HOST_NUMBER;
		hosts[i].next_hop = &hosts[(i+1) % HOST_NUMBER]; 
		host_q_enqueue(&q, &hosts[i]);
		i++;
	}
	assert(q.length ==  HOST_NUMBER, "Length check after adding hosts.");
	host_q_reset_iter(&q);
	i = 0;
	while(i < HOST_NUMBER){
		next = host_q_next(&q);
		assert(next != NULL, "check if next is NULL prematurely.");
		assert(hosts[i].last_sequence == next->last_sequence, 
				"make sure sequence numbers match (1)");
		i++;
	}
	/* Okay, we reset it and whatever, now we loop through and reset every 
	** even one!!! */
	host_q_reset_iter(&q);
	i = 0;
	while(i < HOST_NUMBER){
		next = host_q_next(&q);
		assert(next != NULL, "check if next is NULL prematurely.");
		assert(hosts[i].last_sequence == next->last_sequence, 
				"make sure sequence numbers match (2)");
		if(i % 2 == 0)
			host_q_reset_current(&q);
		i++;
	}
	/* Check odd numbers ...*/
	host_q_reset_iter(&q);
	i = 1;
	while(i < HOST_NUMBER){
		next = host_q_next(&q);
		assert(next != NULL, "check if next is NULL prematurely.");
		assert(hosts[i].last_sequence == next->last_sequence, 
				"make sure sequence numbers match (3)");
		i = i + 2;
	}
	/* Check even  numbers ...*/
	i = 0;
	while(i < HOST_NUMBER){
		next = host_q_next(&q);
		assert(next != NULL, "check if next is NULL prematurely.");
		assert(hosts[i].last_sequence == next->last_sequence, 
				"make sure sequence numbers match (4)");
		i = i + 2;
	}


}

void hg_utest_host(struct host hosts[], int i, int add, int check){
	if((i + add) < check && (i + add) >= 0) {
		hosts[i].edges[hosts[i].edge_count++] = &hosts[i + add]; 
	}
}
void host_graph_dijkstras_utest(){
	int i;
	struct host hosts[HOST_NUMBER];
	struct host_q q;
	host_q_new(&q);
	i = 0;

	/* TOPOGRAPHY #0 */
	/*
	** TOPOGRAPHY #0
	**    1---3--- ...
	**   / \ / \
	**  0---2---4 ...  
	*/
	while(i < HOST_NUMBER){
		hosts[i].last_sequence = i;
		hosts[i].last_time = i * HOST_NUMBER;
		host_q_enqueue(&q, &hosts[i]);
		hosts[i].is_enabled = 1;
		hosts[i].edge_count = 0;
		hosts[i].next_hop = NULL;
		hg_utest_host(hosts, i,  1, HOST_NUMBER);
		hg_utest_host(hosts, i,  2, HOST_NUMBER);
		hg_utest_host(hosts, i, -1, HOST_NUMBER);
		hg_utest_host(hosts, i, -2, HOST_NUMBER);
		hosts[i].edges[hosts[i].edge_count] = NULL;
		i++;
	}
	assert(q.length ==  HOST_NUMBER, "Length check after adding hosts.");
	/**************************/
	struct host_graph g;
	struct host* next;
	host_graph_new(&g, &q);
	g.self = &hosts[0];
	/*** Uncomment to show initial state */
	host_q_reset_iter(&q);
	while((next = host_q_next(&q))){/* print_host(next);*/ }
	host_graph_dijkstra(&g);
	host_q_reset_iter(&q);
	while((next = host_q_next(&q))){
		if(next == g.self) continue;
		assert(next->next_hop!=NULL, "No next hop computed.");
		assert(!(next->number % 2) + 1 == next->next_hop->number, 
				"Next hop wrong.");
	}
	hosts[2].is_enabled = 0; // Disable host #2
	host_graph_dijkstra(&g);
	host_q_reset_iter(&q);
	while((next = host_q_next(&q))){
		if(next == g.self) continue;
		if(next == &hosts[2]){
			assert(next->is_enabled == 0, "2 did not remain disable."); 
			assert(next->next_hop==NULL, "unreachable hosts hop null");
		} else {
			assert(next->next_hop!=NULL, "No next hop computed. (2)");
			assert(next->next_hop->number == 1, "Next hop wrong. (2)");
		}
	}
	hosts[3].is_enabled = 0;
	host_graph_dijkstra(&g);
	host_q_reset_iter(&q);
	while((next = host_q_next(&q))){
		if(next == g.self) continue;
		if(next == &hosts[1]){
			assert(next->is_enabled == 1, "1 should be enabled"); 
			assert(next->next_hop==&hosts[1], "only reachable host");
		} else {
			assert(next->next_hop==NULL, "unreachable hosts hop null (3)");
		}
	}
}

void do_test(const char *name){
	if(current_test != NULL){
		fprintf(stderr, "%-16s [ SUCCESS ]\n", current_test);
	}
	current_test = (char *) name;
}


int main(){
	/*current_host();*/
	do_test("num_to_buffer");
	num_to_buffer_utest();
	do_test("packet_to_buffer");
	packet_to_buffer_utest();
	do_test("host_q");
	host_q_utest();
	do_test("graph_dijkstras");
	host_graph_dijkstras_utest();
	do_test(NULL);
	return 0;
}







