#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <glib.h>
#include "h4sh/enc_base32.h"

#define SERVER "/tmp/shaf"
#define MAXMSG 512
#define MAX_HEADER_LEN 80

#define HASH_SIZE 20
#define DEVID_SIZE 4
#define OFFSET_SIZE 8
#define LENGTH_SIZE 8
#define ENTRY_SIZE HASH_SIZE + DEVID_SIZE + OFFSET_SIZE + LENGTH_SIZE


typedef struct rec* RecordPtr;

typedef struct rec
{
	guchar hash[HASH_SIZE];
	guint32 dev_id;
	guint64 offset;
	guint64 length;
} Record;

RecordPtr ralloc(void)
{
	RecordPtr temp = (RecordPtr) malloc(sizeof(Record));
	if(temp == NULL)
	{
		printf("Error! Could not malloc memory for a Record.\n");
		exit(1);
	}
	return temp;
}


int make_named_socket(const char *filename)
{
	struct sockaddr_un name;
	int sock;

	/* Create the socket. */
	sock = socket (PF_LOCAL, SOCK_DGRAM, 0);
	if (sock < 0)
	{
		perror ("socket");
		exit (EXIT_FAILURE);
	}

	/* Bind a name to the socket. */
	name.sun_family = AF_UNIX;
	strcpy(name.sun_path, filename);

	if (bind(sock, (struct sockaddr *) &name, strlen(name.sun_path) + sizeof(name.sun_family)) < 0)
	{
		perror ("bind");
		exit (EXIT_FAILURE);
	}

	return sock;
}

guint my_hashfunc(gconstpointer key)
{
	return *(guint *)key;
}

gboolean my_equalfunc(gconstpointer v1, gconstpointer v2)
{
	if(0 == memcmp(((RecordPtr)v1)->hash, ((RecordPtr)v2)->hash, HASH_SIZE))
		return TRUE;
	return FALSE;
}


int main(int argc, char* argv[])
{
	// Standard usage
	if(argc != 2)
	{
		fprintf(stderr, "Please provide a file to act as a FAT\n");
		fprintf(stderr, "Usage: %s fat-file\n", argv[0]);
		exit(1);
	}

	// Attempt to open the FAT
	FILE* fat = fopen(argv[1], "rb");
	if(NULL == fat)
	{
		fprintf(stderr, "Can't open the file `%s`, does it exist?\n", argv[1]);
		exit(1);
	}
	
	struct stat mystat;
	if (fstat(fileno(fat),&mystat)!=0){
		fclose(fat);
		perror("fstat() failed");
		exit(1);
	}
	
	char* fatmap;
	fatmap = mmap(NULL,mystat.st_size,PROT_READ,MAP_SHARED, fileno(fat), 0);
	
	GHashTable* htable = g_hash_table_new((GHashFunc) &my_hashfunc, (GEqualFunc) &my_equalfunc);
	guint64 hash_count = 0;

	// Make the root node
	size_t status;
	guchar 	hashbuf[20];
	guchar	buffer32[4];
	guchar	buffer64[8];
	guint32	dev_id;
	guint64	offset;
	guint64	length;

	char base32[33];
	base32[32] = '\0';

	// Read in the FAT to the list
	char* o;
	for(o=fatmap; o < (fatmap + mystat.st_size-ENTRY_SIZE); o += ENTRY_SIZE)
	{
		h4sh_generic_encode(&enc_base32, base32, (gchar*)o, HASH_SIZE);
		dev_id = GINT32_FROM_BE(*(guint32*)(o+HASH_SIZE));
		offset = GINT64_FROM_BE(*(guint64*)(o+HASH_SIZE+DEVID_SIZE));
		length = GINT64_FROM_BE(*(guint64*)(o+HASH_SIZE+DEVID_SIZE+OFFSET_SIZE));

		RecordPtr new_node = ralloc();
		g_memmove(new_node->hash, o, HASH_SIZE);
		new_node->dev_id = dev_id;
		new_node->offset = offset;
		new_node->length = length;
		printf("Read a node:\nhash=%32s dev=%u offset=%llu length=%llu\n\n\n", base32, dev_id, offset, length);

		g_hash_table_insert(htable, new_node->hash, new_node);
		hash_count++;
	}
	fprintf(stderr, "Read %llu hashes\n----------------------------------------\n", hash_count);

	// Tidy up ASAP
	fclose(fat);



	// Write the new FAT out
	// printf("Writing the new FAT file\n");
	//write_fat("new_fat", htable);


	// Start serving
	int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
	char message[MAXMSG];
	memset(message, '\0', (size_t) MAXMSG);
	struct sockaddr_un name;
	size_t size;
	int nbytes;

	unlink(SERVER);
	sock = make_named_socket(SERVER);
	while(1)
	{
		/* Wait for a datagram. */
		size = sizeof(name);
		nbytes = recvfrom(sock, message, MAXMSG, 0, (struct sockaddr *) &name, &size);
		if(nbytes < 0)
		{
			perror("recfrom (server)");
			exit(EXIT_FAILURE);
		}

		/* Give a diagnostic message. */
		fprintf (stderr, "Server: got message: %s\n", message);


		if(0 == strncmp(message, "GET ", 4))
		{
			char* query_hash = strdup(message+4);
			char binary_hash[HASH_SIZE];
			h4sh_generic_decode(&enc_base32, binary_hash, (gchar*) query_hash, 32);

			RecordPtr node = (RecordPtr) g_hash_table_lookup(htable, binary_hash);
			if(node == NULL)
			{
				strcpy(message, "ERR Not found\n");
			}
			else
			{
				sprintf(message, "OK! dev=%u offset=%llu length=%llu\n", node->dev_id, node->offset, node->length);
			}
			free(query_hash);
		}/*
		else if(0 == strncmp(message, "ADD ", 4))
		{
			printf("Adding node\n");
			char* new_entry = strdup(message+4);
			if(EOF != sscanf(new_entry, "%32s:%u:%llu:%llu", hash, &input_dev_id, &input_offset, &input_length))
			{
				cur = insert_node(cur, hash, input_dev_id, input_offset, input_length);
				fat = fopen(argv[1], "ab");
				if(NULL == fat)
				{
					fprintf(stderr, "Could not open file `%s` for appending to FAT\n", argv[1]);
					sprintf(message, "ERR ");
				}
				else
				{
					fprintf(fat, "\n%32s:%u:%llu:%llu", hash, input_dev_id, input_offset, input_length);
					sprintf(message, "OK! ");
				}
				fclose(fat);
			}
			else
			{
				sprintf(message, "sscanf failed\n");
			}
			free(new_entry);
			print_list(root);
		}
		else if(0 == strncmp(message, "DEL ", 4))
		{
			printf("Deleting node\n");
			char* delete_hash = strdup(message+4);
			RecordPtr node = NULL;
			if(NULL == node)
			{
				strcpy(message, "Sorry, hash not found\n");
			}
			else
			{
				cur = delete_node(node, &root);
				// Write the new FAT out
				printf("Writing the new FAT file\n");
				write_fat(argv[1], root);
				sprintf(message, "OK! ");
			}
			free(delete_hash);
			print_list(root);
		}*/
		else
		{
			strcpy(message, "ERR ");
		}
			

		/* Bounce the message back to the sender. */
		nbytes = sendto (sock, message, strlen(message)+1, 0, (struct sockaddr *) & name, size);
		if (nbytes < 0)
		{
			perror("sendto (server)");
			exit (EXIT_FAILURE);
		}
	}

	return 0;
}
