#include <stdio.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/time.h>
#include <time.h>
#include <math.h>
#include "helper.h"
#include "data.c"
#define PACKET_SIZE (17)
#define PACKET_TYPE_SIZE (1) 
#define PACKET_1_BYTE (1) 
#define PACKET_TTL_SIZE (4)
#define PACKET_ADDR_SIZE (6) 
#define PACKET_INT_SIZE (4)
#define MAXLEN 15209
#define HOSTNAME_LENGTH (512)
#define NANO_TO_MILLI (1000000)
#define DEBUG_1 (1)

void error(char *message){
	fprintf(stderr, "A fatal error has occured: %s",  message);
	fflush(stderr);
	exit(1);
}


/* compares two hosts to see if they point to the same address */
char host_cmp(struct sockaddr_in *a, struct sockaddr_in *b){
	return a->sin_addr.s_addr == b->sin_addr.s_addr &&
		a->sin_port == b->sin_port;
}


/***************************************************************************/
/** Wrapper functions ******************************************************/
/***************************************************************************/
int Socket(int domain, int type, int protocol){
	int sockfd;
	if((sockfd = socket(domain, type, protocol)) == -1)
		error("socket didn't set up correctly\n");
	return sockfd;
}

int Bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen){
	int rc;
	if((rc = bind(sockfd, my_addr, addrlen)) == -1){
		perror("Bind:");
		error("bind  didn't work \n");
	}
	return rc; 
}


int Sendto(int s, void *buf, size_t len, int flags, 
		struct sockaddr *to, socklen_t tolen){
	int rc;
	if((rc = sendto(s, buf, len, flags, to, tolen))== -1){
		perror("Error in sendto");
		error("sendto didn't work \n");
	}
	return rc; 
}

ssize_t Write(int fd, const void *buf, size_t count){
	int ret;
	if((ret = write(fd, buf, count)) == -1 || ret != count){
		error("Write failed!\n");
	}
	return ret;
}


ssize_t Recvfrom(int s, void *buf, size_t len, int flags,
		struct sockaddr *from, socklen_t *fromlen){
	int rc;
	if((rc = recvfrom(s,buf,len,flags,from,fromlen)) == -1)
		error("recvfrom is broken.");
	return rc;
}

FILE *Fopen(char *path, char *mode){
	FILE *f;
	if((f=fopen(path, mode))==NULL)
		error("fopen failed, file not found\n");
	return f;
}

int Fclose(FILE *fp){
	int ret_val;
	if((ret_val = fclose(fp)) != 0){
		error("fclose was not successful\n");
	}
	return ret_val;
}

size_t Fwrite(void *ptr, size_t size, size_t nmemb, FILE* stream){
	int ret_val;
	if((ret_val = fwrite(ptr, size, nmemb, stream)) == 0){
		error("fwrite was not successful\n");
	}
	return ret_val;
}


/* parses optarg assumed to be a port and then returns, error checking */
unsigned int options_parse_port(){
	unsigned int port = atoi(optarg);
	if(port < 1024 || port > 65536)
		error("Invalid entry for port. Must be in range 1024 - 65536\n");
	return port;
}
/* simple gettimeofday() wrapper,  returns time as a double */
double get_time(){
	struct timeval tv;
	if(gettimeofday(&tv, NULL)!=0) error("gettimeofday failed.");
	return (double) ((double)tv.tv_sec + ((double)tv.tv_usec / 1e6));
}

void get_current_time(char* time_str){
	char tstr[20], nano_str[10];
	time_t t = time(NULL);
	(void) strftime(tstr, 20, "%H:%M:%S", localtime(&t));
	double nano = get_time();
	nano = nano - floor(nano);
	sprintf(nano_str, "%0.3f", nano);
	sprintf(time_str, "%s%s", tstr, (nano_str + 1));
}

struct packet* nonblocking_receive_packet(int sockfd){
	socklen_t len;
	uint8_t buffer[MAXLEN];
	struct sockaddr_in addr;
	struct packet *packet;
	int ret_recvfrom;
	if((ret_recvfrom = recvfrom(sockfd, buffer, MAXLEN, MSG_DONTWAIT, 
					(struct sockaddr*) &(addr), 
					&len)) == -1/* && errno == EAGAIN*/){
		return NULL;
	}
	else if(ret_recvfrom == -1){
		error("Problem trying to do the non-blocking recvfrom function\n");
	}
//	fprintf(stderr, "nonblocking: real length: %02i            | ", ret_recvfrom);
	/* Note: some error checking could feasibly be introduced here, by way 
	** of checking if len and the packet->length match up */
	packet = buffer_to_packet(buffer);
//	fprintf(stderr, "nonblocking: packet length: %02i\n", packet->length);
//	packet->source = addr; // TODO this probably breaks everything
	return packet;
}
/* identifal to packet_receive, except it has a timeout in milliseconds */
struct packet* packet_receive_timeout(int sockfd, int timeout){
	struct packet* packet = NULL; 
	struct timespec sleep_amount;
	sleep_amount.tv_sec = 0;
	sleep_amount.tv_nsec = NANO_TO_MILLI;
	while(timeout--){
		if((packet = nonblocking_receive_packet(sockfd)))
			break;
		nanosleep(&sleep_amount, NULL);
	}
	return packet;
}


struct packet* packet_receive(int sockfd){
	socklen_t len;
	uint8_t buffer[MAXLEN];
	struct sockaddr_in addr;
	struct packet *packet;
	Recvfrom(sockfd, buffer, MAXLEN, 0, (struct sockaddr*) &(addr), &len);
	/* Note: some error checking could feasibly be introduced here, by way 
	** of checking if len and the packet->length match up */
	packet = buffer_to_packet(buffer);
	return packet;
}

void packet_send_to_addr(int sockfd, struct packet* packet,
		struct sockaddr_in* addr){
	unsigned int buffer_size;
	uint8_t *buffer;
	buffer = packet_to_buffer(packet, &buffer_size);
	if(sendto(sockfd, buffer, buffer_size, 0, (struct sockaddr*) addr, 
				sizeof(struct sockaddr_in)) == -1){
		error("Error in the sendto function!\n");
	}
}
void get_my_addr(struct sockaddr_in *addr){
	char dot_dec[HOSTNAME_LENGTH];
	gethostname(dot_dec, HOSTNAME_LENGTH);
	str_to_sockaddr_in(addr, dot_dec);
}

void str_to_sockaddr_in(struct sockaddr_in *addr, const char *s){
	/* NOTE: redo this function probably, making it do fewer steps */
	/* MEMORY LEAK: does host need to be freed? */ 
	char dot_dec[32];
	struct hostent *host;
	if((host = gethostbyname(s)) == NULL){
		fprintf(stderr, "Invalid address: %s\n", s);
		error("Could not find the address specifed.\n");
	}
	addr->sin_family = host->h_addrtype;
	if(inet_ntop(host->h_addrtype, host->h_addr, dot_dec, HOSTNAME_LENGTH) == 0)
		error("inet_ntop() failed!\n");
	if(inet_pton(host->h_addrtype, dot_dec, &addr->sin_addr) == 0)
		error("inet_pton() failed...\n");
}

void sockaddr_in_to_str(char** s, struct sockaddr_in *addr){
	*s =  inet_ntoa(addr->sin_addr);
}

void num_to_buffer32(uint8_t* b, uint32_t num){
	*(b+3) = num>>24; *(b+2) = num>>16; *(b+1) = num>>8; *b = num;
}
void num_to_buffer16(uint8_t* b, uint16_t num){
	*(b+1) = num>>8; *b = num;
}
uint32_t buffer_to_num32(uint8_t* b){
	return  ((uint32_t) b[3]<<24) |
		((uint32_t) b[2]<<16) |
		((uint32_t) b[1]<<8)  |
		((uint32_t) b[0]);
}
uint16_t buffer_to_num16(uint8_t* b){
	return  ((uint16_t) b[1]<<8) |
		((uint16_t) b[0]);
}

/* Converts a sockaddr_in to a buffer */
void sockaddr_in_to_buffer(uint8_t *buffer, struct sockaddr_in* addr){
	num_to_buffer32(buffer, htonl((uint32_t) addr->sin_addr.s_addr));
	buffer += PACKET_INT_SIZE;
	num_to_buffer16(buffer, htons((uint16_t) addr->sin_port));
}

/* Converts a buffer to a sockaddr_in */
void buffer_to_sockaddr_in(struct sockaddr_in* addr, uint8_t *buffer){
	addr->sin_addr.s_addr = ntohl(buffer_to_num32(buffer));
	buffer += PACKET_INT_SIZE;
	addr->sin_port = ntohs(buffer_to_num16(buffer));
	addr->sin_family = AF_INET; /* set sin family to the default */
}


/*
**
**  .---------------------------------------------------------------------------.
**  | 8 bit   | 32 bit    | 32 bit src | 16 bit src | 32 bit dest | 16 bit dest | 
**  | Type    |  TTL      | IP address |  port      | IP address  |    port     | 
**  '---------------------------------------------------------------------------'
**
**/

uint8_t* packet_to_buffer(struct packet *packet, unsigned int *packet_size){
	uint8_t  *buffer;
	uint8_t *buffer_start;
	*packet_size = PACKET_SIZE;
	buffer_start = buffer = malloc(*packet_size);
	
	/* Insert packet type */
	*buffer = packet->type;
	buffer += PACKET_TYPE_SIZE;
	
	/* Insert 32 bit TTL */
	num_to_buffer32(buffer, htonl(packet->ttl));
	buffer += PACKET_TTL_SIZE;

	/* 32 bit src IP addr, and 16 bit src port */
	sockaddr_in_to_buffer(buffer, &packet->source);
	buffer += PACKET_ADDR_SIZE;
	
	/* 32 bit dest IP addr, and 16 bit dest port */
	sockaddr_in_to_buffer(buffer, &packet->destination);
	buffer += PACKET_ADDR_SIZE;
	
	return buffer_start;
}

struct packet* buffer_to_packet(uint8_t *buffer){
	struct packet *packet = malloc(sizeof(struct packet));
	
	/* Get packet type */
	packet->type = (char) *buffer;
	buffer += PACKET_TYPE_SIZE;
	
	/* Extract 32 bit sequence */
	packet->ttl = ntohl(buffer_to_num32(buffer));
	buffer += PACKET_TTL_SIZE;
	
	/* extract 32 bit src IP addr & 16 bit port */
	buffer_to_sockaddr_in(&packet->source, buffer);
	buffer += PACKET_ADDR_SIZE;
	
	/* extract 32 bit destination IP addr  & 16 bit port*/
	buffer_to_sockaddr_in(&packet->destination, buffer);
	buffer += PACKET_ADDR_SIZE;
	
	return packet;
}



