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

#include "memory.h"

#define hashtable_IMPORT
#include "hashtable.h"


typedef struct hashtable_Entry {
	struct hashtable_Entry *next; // Next entry that shares the same slot.
	int key_hash;  // Hash of the key of this entry.
	void *key;  // Key.
	void *value;  // Value.
} hashtable_Entry;


struct hashtable_Type {
	hashtable_getKeyHash getKeyHash; // Computes the hash of the key.
	hashtable_equalKeys equalKeys; // Compares two keys for equality.
	int length;  // No.of stored key/value pairs.
	int slots_no;  // No. of entries in the array of slots (aka buckets).
	hashtable_Entry **slots;  // Slots.
	int cursor_next_slot_no;  // Sequential access, next slot no.
	hashtable_Entry *cursor_next_entry;  // Sequential access, next entry.
};


/**
 * Destructor call-back for the memory module.
 * @param p Hash table to destruct.
 */
static void hashtable_destruct(void *p)
{
	int i;
	hashtable_Type *ht = p;
	if( ht == NULL )
		return;
	for(i = ht->slots_no-1; i >= 0; i--){
		hashtable_Entry *curr, *next;
		curr = ht->slots[i];
		while( curr != NULL ){
			next = curr->next;
			memory_dispose(curr->key);
			memory_dispose(curr->value);
			memory_dispose(curr);
			curr = next;
		}
	}
	memory_dispose(ht->slots);
}


static void hashtable_resetCursor(hashtable_Type *ht)
{
	ht->cursor_next_slot_no = -1;
	ht->cursor_next_entry = NULL;
}


hashtable_Type *hashtable_new(
	hashtable_getKeyHash getKeyHash,
	hashtable_equalKeys equalKeys)
{
	hashtable_Type *ht = memory_allocate(sizeof(hashtable_Type), hashtable_destruct);
	ht->getKeyHash = getKeyHash;
	ht->equalKeys = equalKeys;
	ht->length = 0;
	ht->slots_no = 0;
	ht->slots = NULL;
	hashtable_resetCursor(ht);
	return ht;
}


int hashtable_length(hashtable_Type *ht)
{
	return ht->length;
}


static int hashtable_getSlotIndexFromHash(int slots_no, int hash)
{
	int i = hash % slots_no;
	if( i < 0 )
		i = - i - 1;
	return i;
}


/**
 * Re-allocates the hash table array of slots when the number of key/value
 * pairs equals its length. Attempts to double the length.
 */
static void hashtable_realloc(hashtable_Type *ht)
{
	hashtable_Entry **slots2;
	int i;
	int slots_no2 = 2 * ht->slots_no + 1;
	int size = slots_no2 * sizeof(hashtable_Entry *);
	if( size <= 0 )
		return;
	slots2 = memory_allocate(size, NULL);
	memset(slots2, 0, size);
	for(i = ht->slots_no-1; i >= 0; i--){
		hashtable_Entry *curr = ht->slots[i];
		while(curr != NULL){
			hashtable_Entry *next = curr->next;
			int i2 = hashtable_getSlotIndexFromHash(slots_no2, curr->key_hash);
			curr->next = slots2[i2];
			slots2[i2] = curr;
			curr = next;
		}
	}
	memory_dispose(ht->slots);
	ht->slots = slots2;
	ht->slots_no = slots_no2;
}


static hashtable_Entry *hashtable_getEntry(hashtable_Type *ht,
	int key_hash, void *key)
{
	if( ht->slots_no == 0 )
		return NULL;
	int i = hashtable_getSlotIndexFromHash(ht->slots_no, key_hash);
	hashtable_Entry *e = ht->slots[i];
	while( e != NULL ){
		if( e->key_hash == key_hash && ht->equalKeys(e->key, key) )
			return e;
		e = e->next;
	}
	return NULL;
}


void hashtable_put(hashtable_Type *ht, void *key, void *value)
{
	int key_hash = ht->getKeyHash(key);
	hashtable_Entry *e = hashtable_getEntry(ht, key_hash, key);
	
	if( e != NULL ){
		/* Key already in table. Replace existing value. */
		if( e->value != value )
			memory_dispose(e->value);
		e->value = value;
		return;
			
	} else if( ht->slots_no == 0 ){
		/* Table still empty. First allocation. */
		ht->slots_no = 123;
		ht->slots = memory_allocate(ht->slots_no * sizeof(hashtable_Entry *), NULL);
		memset(ht->slots, 0, ht->slots_no * sizeof(hashtable_Entry *));
		
	} else if( ht->slots_no == ht->length ){
		/* Time to try to double the length of the array of slots. */
		hashtable_realloc(ht);
		hashtable_resetCursor(ht);
	}
	
	/* Add entry. */
	int i = hashtable_getSlotIndexFromHash(ht->slots_no, key_hash);
	e = memory_allocate(sizeof(hashtable_Entry), NULL);
	e->key_hash = key_hash;
	e->key = key;
	e->value = value;
	e->next = ht->slots[i];
	ht->slots[i] = e;
	ht->length++;
}


void *hashtable_get(hashtable_Type *ht, void *key)
{
	int key_hash = ht->getKeyHash(key);
	hashtable_Entry *e = hashtable_getEntry(ht, key_hash, key);
	if( e == NULL )
		return NULL;
	else
		return e->value;
}


void hashtable_remove(hashtable_Type *ht, void *key)
{
	int key_hash = ht->getKeyHash(key);
	hashtable_Entry *e = hashtable_getEntry(ht, key_hash, key);
	if( e == NULL )
		return;
	int i = hashtable_getSlotIndexFromHash(ht->slots_no, key_hash);
	if( ht->slots[i] == e ){
		ht->slots[i] = e->next;
	} else {
		hashtable_Entry *curr = ht->slots[i];
		while(curr->next != e)
			curr = curr->next;
		curr->next = e->next;
	}
	if( e == ht->cursor_next_entry ){
		/*
		 * FIXME: unlikely removing of entry past the current cursor position.
		 * We might support this, but worth the effort? For now simply reset:
		 */
		hashtable_resetCursor(ht);
	}
	memory_dispose(e->key);
	memory_dispose(e->value);
	memory_dispose(e);
}


void *hashtable_firstKey(hashtable_Type *ht)
{
	int i;
	for(i = ht->slots_no - 1; i >= 0; i--)
		if( ht->slots[i] != NULL )
			break;
	if( i < 0 ){
		hashtable_resetCursor(ht);
		return NULL;
	}
	hashtable_Entry *e = ht->slots[i];
	if( e->next == NULL ){
		do {
			i--;
		}while( i >= 0 && ht->slots[i] == NULL );
		ht->cursor_next_slot_no = i;
		ht->cursor_next_entry = i >= 0? ht->slots[i] : NULL;
	} else {
		ht->cursor_next_slot_no = i;
		ht->cursor_next_entry = e->next;
	}
	return e->key;
}


void *hashtable_nextKey(hashtable_Type *ht)
{
	if( ht->cursor_next_entry == NULL )
		return NULL;
	int i = ht->cursor_next_slot_no;
	hashtable_Entry *e = ht->cursor_next_entry;
	if( e->next == NULL ){
		do {
			i--;
		}while( i >= 0 && ht->slots[i] == NULL );
		ht->cursor_next_slot_no = i;
		ht->cursor_next_entry = i >= 0? ht->slots[i] : NULL;
	} else {
		ht->cursor_next_entry = e->next;
	}
	return e->key;
}


int hashtable_getHashOfMem(void *buf, int buf_len)
{
	char *charptr;
	int *intptr = buf;
	int hash = 1;
	while(buf_len >= sizeof(int)){
		hash = 573 * hash + *intptr;
		intptr++;
		buf_len -= sizeof(int);
	}
	charptr = (char *) intptr;
	while(buf_len > 0){
		hash = 573 * hash + *charptr;
		charptr++;
		buf_len--;
	}
	return hash;
}


int hashtable_getHashOfString(void *s)
{
	return hashtable_getHashOfMem(s, strlen(s));
}


int hashtable_equalStrings(void *a, void *b)
{
	return strcmp(a, b) == 0;
}


void hashtable_report(hashtable_Type *ht)
{
	int i, si, simax, sisum, used;
	double si2sum, avg, var, Q;
	hashtable_Entry *s;
	
	if( ht->slots_no == 0 ){
		printf("    empty table.\n");
		return;
	}
	simax = 0;
	sisum = 0;
	si2sum = 0.0;
	used = 0;
	for(i = ht->slots_no-1; i >= 0; i--){
		s = ht->slots[i];
		if( s != NULL )
			used++;
		si = 0;
		while(s != NULL){
			si++;
			s = s->next;
		}
		if( si > simax )
			simax = si;
		sisum += si;
		si2sum += si*si;
	}
	avg = (double) sisum / used; /* mean length of occupied slots */
	var = si2sum / used - avg*avg; /* variance of the occupied slot length */
	Q = 0.5*(si2sum/sisum + 1.0); /* mean no. of comparisons per search */
	printf("    %d total table length\n", ht->slots_no);
	printf("    %d occupied table entries\n", used);
	printf("    %d max table entry length\n", simax);
	printf("    %g average table entry length\n", avg);
	printf("    %g variance table entry length\n", var);
	printf("    %g mean no. of comparisons per search\n", Q);
}