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

#include "file.h"
#include "tree.h"

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

struct iso_file *
iso_file_new(struct iso_tree_node_file *f)
{
	struct iso_file *file = calloc(1, sizeof(struct iso_file));
	file->path = f->path;  /*TODO strdup? it needs to be free on clear then */
	file->size = f->node.attrib.st_size;
	file->nlink = 1;
	file->real_dev = f->node.attrib.st_dev;
	file->real_ino = f->node.attrib.st_ino;
	file->sort_weight = f->sort_weight;
	return file;
}

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)
{
	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)
{
	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;

	/* find the hash number */
	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;
}

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;
	
	/* find the hash number */
	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->path);
	
	node = ft->table[hash_num];

	if (!node)
		return NULL;

	equal = ft->cache_inodes ? 
	        ((f->node.attrib.st_dev == node->file->real_dev) 
	          && (f->node.attrib.st_ino == node->file->real_ino))
	        : !strcmp(f->path, node->file->path);
	if (equal)
		return node->file;	

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

		equal = ft->cache_inodes ? 
	        ((f->node.attrib.st_dev == node->file->real_dev) 
	          && (f->node.attrib.st_ino == node->file->real_ino))
	        : !strcmp(f->path, node->file->path);
		if (equal)
			return node->file;
	}

	return NULL;
}

