/*
** Not A Hash Table
**
** Joshua Reich 2006
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef THREAD_SAFE
#include <pthread.h>
#endif 

#include "naht.h"

unsigned long naht_round (unsigned long a)
{
	/*
	** Rounds an unsigned long up to the nearest
	** power of 2. For example, naht_round(3000)
	** returns 4096.
	*/

	unsigned long x, i;

	x = 1;
	i = 0;
	while ((i < 64) &&  (x < a))
	{
		x = x << 1;
		i++;
	}

	return (x);
}

nahtT *naht_init (size_t item_size, float mb, int (*compare)(void *a, void *b), void (*discard)(void *a))
{
	/*
	** Initialize a naht for storing items, where each
	** item is of size size item_size. Allocates at least 
	** mb megabytes of storage. 
	** You supply a comparison function that
	** compares 2 elements & returns 1 if they are equal,
	** 0 otherwise.
	**
	** Returns a pointer to the newly created naht, NULL
	** if any of the malloc's fail.
	**
	** NB: Rather than making a distinction between data
	** and item, each element is assumed to contain both
	** or neither; whatever your application demands.
	*/

	nahtT 	*n;
#ifdef THREAD_SAFE
	int		i;
#endif

	if (!compare)
	{
		fprintf (stderr, "Must set a compare() function\n");
		return NULL;
	}

	if (!discard)
	{
		fprintf (stderr, "Must set a discard() function\n");
		return NULL;
	}

	n = (nahtT *) malloc (sizeof (nahtT));
	if (!n)
	{
		return NULL;
	}

	n->item_size = item_size;
	n->chain_length = naht_round (NAHT_CHAIN_LENGTH_BYTES / item_size);
	n->compare = compare;
	n->discard = discard;
	n->buckets = naht_round ((1024 * 1024 * mb) / (n->chain_length * item_size));
	if (n->chain_length > n->buckets)
	{
		unsigned long tmp;
		tmp = n->chain_length;
		n->chain_length = n->buckets;
		n->buckets = tmp;
	}
	n->mask = n->buckets - 1;
	n->chain_mask = n->chain_length - 1;

	n->bins = (int *) malloc (sizeof (int) * n->buckets);
	if (!n->bins)
	{
		free (n);
		return NULL;
	}

	n->idx = (int *) malloc (sizeof (int) * n->buckets);
	if (!n->idx)
	{
		free (n->bins);
		free (n);
		return NULL;
	}

#ifdef THREAD_SAFE
	n->lock = (pthread_mutex_t *) malloc (sizeof (pthread_mutex_t) * n->buckets);
	if (!n->lock)
	{
		free (n->idx);
		free (n->bins);
		free (n);
		return NULL;
	}

	for (i=0; i<n->buckets; i++)
	{
		pthread_mutex_init (&n->lock[i], NULL);
	}
#endif

	n->bin = malloc (n->item_size * n->buckets * n->chain_length);
	if (!n->bin)
	{
		free (n->idx);
		free (n->bins);
#ifdef THREAD_SAFE
		free (n->lock);
#endif
		free (n);
		return NULL;
	}

	memset (n->idx, 0, sizeof (int) * n->buckets);
	memset (n->bins, 0, sizeof (int) * n->buckets);


	return n;
}

void	naht_free (nahtT *n)
{
	if (!n) return;
	free (n->idx);
	free (n->bins);
#ifdef THREAD_SAFE
	free (n->lock);
#endif
	free (n->bin);
	free (n);
	n = NULL;
}


void	*naht_find (nahtT *n, unsigned long hash, void *item)
{	
	/*
	** Looks for 'item' within n. Note, you need to 
	** supply your own hash of the item. We assume you are
	** using a decent hash function that will use at least
	** enough least significant bits.
	**
	** Returns a pointer to the found item, otherwise NULL
	** if nothing is found.
	*/

	void			*offset;
	unsigned long	b;
	int				i;

	b = hash & n->mask;

#ifdef THREAD_SAFE
	pthread_mutex_lock (&n->lock[b]);
#endif

	offset = n->bin + (b * n->chain_length + n->idx[b]) * n->item_size;

	/* Search from idx --> 0 */
	i = n->idx[b]-1;
	while ((i >= 0) && !n->compare (offset, item)) 
	{
		i--;
		offset -= n->item_size;
	}
	if (i < 0)
	{
		/* Search from bins --> idx */
		i = n->bins[b];
		offset = n->bin + (b * n->chain_length + i - 1) * n->item_size;
		while ((i > n->idx[b]) && !n->compare (offset, item)) 
		{
			i--;
			offset -= n->item_size;
		}
		if (i == n->idx[b])
		{
#ifdef THREAD_SAFE
			pthread_mutex_unlock (&n->lock[b]);
#endif
			return NULL;
		}
	}
#ifdef THREAD_SAFE	
	pthread_mutex_unlock (&n->lock[b]);
#endif
	return offset;
}

void naht_insert (nahtT *n, unsigned long hash, void *item)
{
	/*
	** Inserts 'item' in n. Once again, supply the hash.
	**
	** NB: If we had to over-write something to fit this
	** thing into the naht, we will call the discard() 	
	** function on the item that we are about to over-
	** write. 
	*/

	void			*offset;
	unsigned long	b;
	int				i;
	
	b = hash & n->mask;
#ifdef THREAD_SAFE
	pthread_mutex_lock (&n->lock[b]);
#endif

	offset = n->bin + b * n->chain_length * n->item_size;

	if (n->bins[b] == n->chain_length)
	{
		n->idx[b] = (n->idx[b] + 1) & n->chain_mask;	
		i = n->idx[b];
		n->discard (offset + i*n->item_size);
	} else
	{
		i = n->bins[b]++;
		n->idx[b] = i;
	}
	memcpy ((offset + i*n->item_size), item, n->item_size);

#ifdef THREAD_SAFE
	pthread_mutex_unlock (&n->lock[b]);
#endif
}

void	naht_walk (nahtT *n, void (*walk)(void *a))
{
	void			*offset;
	unsigned long	b, i;

	offset = n->bin;

	for (b=0; b<n->buckets; b++)
	{
#ifdef THREAD_SAFE
		pthread_mutex_lock (&n->lock[b]);
#endif
		offset = n->bin + b * n->chain_length * n->item_size;
		for (i=0; i<n->bins[b]; i++)
		{
			walk(offset);
			offset += n->item_size;
		}
#ifdef THREAD_SAFE
		pthread_mutex_unlock (&n->lock[b]);
#endif
	}
}
