#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <limits.h>

#include "file.h"
#include "tree.h"
#include "file_src.h"
#include "messages.h"
#include "ecma119.h"

//TODO: refactor both hash and this hash table into a single one??

struct iso_file *
iso_file_new(struct ecma119_write_target *t, struct iso_tree_node_file *f)
{
	struct iso_file *file = calloc(1, sizeof(struct iso_file));
	if (!file)
		return NULL;
	if (f->node.procedence == LIBISO_NEW) {
		file->path = f->loc.path;  /*TODO strdup? it needs to be free on clear then */
		file->real_dev = f->node.attrib.st_dev;
		file->real_ino = f->node.attrib.st_ino;
		file->src = iso_file_src_from_path(file->path);
	} else {
		file->block = f->loc.block;
		file->prev_img = 1;
		file->real_dev = 0; /* we use 0 as dev for prev. session files */
		/* don't take care about inode number read from RR TX, block
		 * number is good enouht for this. Moreover, when we are modifying
		 * an image, we will modify file->block with the block where the
		 * file needs to be written in the new image. So, we store the block
		 * in original image here */
		file->real_ino = f->loc.block;
		file->src = iso_file_src_from_prev_img(t->src, f->loc.block, 
		                                       f->node.attrib.st_size);
	}
	if (!file->src)
		goto creation_error;
	file->size = file->src->get_size(file->src);
	
	if (file->size != f->node.attrib.st_size) {
		char msg[PATH_MAX + 32];
		/* can only happen on path files */
		assert(f->node.procedence == LIBISO_NEW);
		sprintf(msg, "Size of file %s has changed\n", file->path);
		iso_msg_sorry(LIBISO_CANT_READ_FILE, msg);
	}
	file->nlink = 1;
	file->sort_weight = f->sort_weight;
	return file;
creation_error:;
	free(file);
	return NULL;
}

static unsigned int
iso_file_table_hash(const char *path)
{
	unsigned int hash_num=0;
	const char *c;

	c=path;
	while(*c)
		hash_num = (hash_num << 15) + (hash_num << 3) + (hash_num >> 3) + *c++;

	return hash_num % FILE_HASH_NODES;
}

static inline unsigned int
iso_file_table_hash_inode(dev_t dev, ino_t ino)
{
	return (dev ^ ino) % FILE_HASH_NODES;
}

struct iso_file_table* 
iso_file_table_new(int cache_inodes) 
{
	struct iso_file_table *table = calloc(1, sizeof(struct iso_file_table));
	table->cache_inodes = cache_inodes;
	return table;
}

static struct iso_file_hash_node *
iso_file_table_node_new(struct iso_file *file)
{
	struct iso_file_hash_node *node;
	node = calloc(1, sizeof(struct iso_file_hash_node) );
	node->file = file;
	return node;
}

static void
iso_file_table_node_free(struct iso_file_hash_node *node)
{
	iso_file_src_free(node->file->src);
	free(node->file);
	free(node);
}

void 
iso_file_table_clear(struct iso_file_table *ft) 
{
	int i;

	for (i=0; i < FILE_HASH_NODES; i++) {
		struct iso_file_hash_node *node;

		node=ft->table[i];
		if (!node)
			continue;

		ft->table[i] = NULL;

		do {
			struct iso_file_hash_node *next;

			next = node->next;
			iso_file_table_node_free(node);
			node = next;
		} while (node);
	}
	ft->count = 0;
}

/**
 * return 0 if equal, !=  0 if not
 */
static int
iso_table_compare_files(struct iso_file_table *ft, 
					    struct iso_file *f1, struct iso_file *f2)
{
	assert(ft && f1 && f2);
	if (f1->prev_img || f2->prev_img) {
		if (f1->prev_img && f2->prev_img)
			return f1->real_ino != f2->real_ino;
		else
			return 1;
	}
	if (ft->cache_inodes) {
		return (f1->real_dev != f2->real_dev) || (f1->real_ino != f2->real_ino);
	} else {
		return strcmp(f1->path, f2->path);
	}
}

int 
iso_file_table_add_file(struct iso_file_table *ft, struct iso_file *f) 
{
	struct iso_file_hash_node *node;
	unsigned int hash_num;

	assert(ft && f);

	/* find the hash number */
	if (f->prev_img)
		hash_num = f->real_ino % FILE_HASH_NODES;
	else if (ft->cache_inodes)
		hash_num = iso_file_table_hash_inode(f->real_dev, f->real_ino);
	else
		hash_num = iso_file_table_hash(f->path);

	/* insert it */
	node = ft->table[hash_num];

	/* unfortunately, we can't safely consider that a file
	 * won't be twice in the hash table so make sure it
	 * doesn't already exists */
	if (!node) {
		ft->table[hash_num]=iso_file_table_node_new(f);
		ft->count++;
		return 1;
	}

	/* if it's already in, we don't do anything */
	if (!iso_table_compare_files(ft, f, node->file))
		return 0;

	while (node->next) {
		node = node->next;

		/* if it's already in, we don't do anything */
		if (!iso_table_compare_files(ft, f, node->file))
			return 0;
	}

	node->next = iso_file_table_node_new(f);
	ft->count++;
	return 1;
}

/** 0 on equal, != 0 otherwise */
static int
iso_table_compare_node_file(struct iso_file_table *ft, 
					    struct iso_tree_node_file *f1, struct iso_file *f2)
{
	assert(ft && f1 && f2);
	
	if (f1->node.procedence || f2->prev_img) {
		if (f1->node.procedence && f2->prev_img)
			return f1->loc.block != f2->real_ino;
		else
			return 1;
	}
	if (ft->cache_inodes) {
		return (f1->node.attrib.st_dev != f2->real_dev) 
		       || (f1->node.attrib.st_ino != f2->real_ino);
	} else {
		return strcmp(f1->loc.path, f2->path);
	}
}

struct iso_file *
iso_file_table_lookup(struct iso_file_table *ft, struct iso_tree_node_file *f) 
{
	struct iso_file_hash_node *node;
	unsigned int hash_num;
	int equal;
	
	assert(ft && f);
	
	/* find the hash number */
	if (f->node.procedence == LIBISO_PREVIMG)
		hash_num = f->loc.block % FILE_HASH_NODES;
	else if ( ft->cache_inodes )
		hash_num = iso_file_table_hash_inode(f->node.attrib.st_dev, 
		                                     f->node.attrib.st_ino);
	else 
		hash_num = iso_file_table_hash(f->loc.path);
	
	node = ft->table[hash_num];

	if (!node)
		return NULL;

	equal = !iso_table_compare_node_file(ft, f, node->file);
	if (equal)
		return node->file;	

	while (node->next) {
		node = node->next;

		equal = !iso_table_compare_node_file(ft, f, node->file);
		if (equal)
			return node->file;
	}

	return NULL;
}