/*
** CBLM v3
** $Id: data_hash.c,v 1.8 2007/12/11 06:59:52 sella Exp $
** Copyright (c) 2004-2007 James M. Sella. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

static const char rcsid[] = "$Id: data_hash.c,v 1.8 2007/12/11 06:59:52 sella Exp $";

#include "data_hash.h"

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

int initialized = 0, debug = 0, warn = 1;
unsigned long long inserts = 0, deletes = 0, collisions = 0;
struct hash_key_t hash_key[HASH_WIDTH];

void hash_init() {
	if (initialized == 0) {
		initialized = 1;

		inserts = 0;
		deletes = 0;
		collisions = 0;

		memset(hash_key, 0, sizeof(hash_key));
	}

#ifdef HASH_DEBUG
	debug = 1;
#endif
}

void hash_free() {
	unsigned long long index;

	/* Walk each index within the hash_key. */
	for (index = 0; index < HASH_WIDTH; index++) {
		/* Delete all entries for this index. */
		while (hash_key[index].entry != NULL) {
			if (hash_del(hash_key[index].entry->data) != 0) {
				printf("Failed to delete from hash_key[%llu].entry\n", index);

				break;
			}
		}
	}

	initialized = 0;
}

void hash_add(const struct data_t *data) {
	unsigned long long index;
	struct data_t **data_h;
	struct hash_entry_t *entry_p, **entry_h;

	assert(data != NULL);
	
	index = hash(data);

	if (hash_key[index].entry == NULL) {
		if (debug) printf("hash_add: Found unused index: %lu (key: %u,%u, %u)\n", index, data->dst.s_addr, data->tos, data->pri);

		/* Allocate hash_entry_t and initialize memory. */
		entry_p = (struct hash_entry_t*) malloc(sizeof(struct hash_entry_t));
		memset(entry_p, 0, sizeof(struct hash_entry_t));

		/* Attach to hash_key[index]. */
		hash_key[index].entry = entry_p;

		/* Grab handle of data_t */
		data_h = &entry_p->data;

		/* Increment counters. */
		hash_key[index].depth++;
		inserts++;
	} else {
		if (debug) printf("hash_add: Found an *used* index (collision): %lu (key: %u,%u, %u)\n", index, data->dst.s_addr, data->tos, data->pri);

		/* Walk though entries until we find a match or the end. */
		entry_h = &hash_key[index].entry;
		while (*entry_h != NULL && (*entry_h)->data->dst.s_addr != data->dst.s_addr && (*entry_h)->data->tos != data->tos && (*entry_h)->data->pri != data->pri) {
			entry_h = &(*entry_h)->next;
		}

		/* Allocate new hash_entry_t, if needed. */
		if (*entry_h == NULL) {
			if (debug) printf("hash_add: inserting new hash_entry_t\n");

			/* Allocate hash_entry_t and initialize memory. */
			*entry_h = (struct hash_entry_t*) malloc(sizeof(struct hash_entry_t));
			memset(*entry_h, 0, sizeof(struct hash_entry_t));

			/* Increament counters. */
			hash_key[index].depth++;
			inserts++;
			collisions++;
		} else {
			if (debug) printf("hash_add: found existing entry, replacing it.\n");
		}

		/* Grab handle of data_t */
		data_h = &(*entry_h)->data;
	}

	/* Allocate data_t, if needed. */
	if (*data_h == NULL) {
		*data_h = (struct data_t*) malloc(sizeof(struct data_t));
	}

	/* Store the data */
	memmove(*data_h, data, sizeof(struct data_t));
}

int hash_del(const struct data_t *data) {
	unsigned long long index;
	struct hash_entry_t *entry_p = NULL, **entry_h = NULL;

	assert(data != NULL);
	
	index = hash(data);

	/* If no data at this key's index, do nothing and exist with 1. */
	if (hash_key[index].entry == NULL) {
		if (debug) printf("hash_del: Key not found at index: %llu\n", index);

		return 1;
	}

	/* Walk though entries until we find a match or the end. */
	entry_h = &hash_key[index].entry;
	while (*entry_h != NULL && (*entry_h)->data->dst.s_addr != data->dst.s_addr && (*entry_h)->data->tos != data->tos && (*entry_h)->data->pri != data->pri) {
		entry_h = &(*entry_h)->next;
	}

	/* If key wasn't found in the data at this index, do nothing and exit with 1. */
	if (*entry_h == NULL) {
		if (debug) printf("hash_del: Key not found at index: %llu\n", index);

		return 1;
	}

	if (debug) printf("hash_del: Found entry. Deleting it. Next is: %08x\n", (*entry_h)->next);

	/* Free data */
	free((*entry_h)->data);
	(*entry_h)->data = NULL;

	/* Grab pointer to hash_entry_t */
	entry_p = *entry_h;

	/* Link previous with (*entry_h)->next. */
	*entry_h = (*entry_h)->next;

	/* Free hash_entry_t */
	free(entry_p);
	entry_p = NULL;

	/* Adjust counters. */
	hash_key[index].depth--;
	deletes++;

	return 0;
}

struct data_t* hash_find(const struct data_t *data) {
	unsigned long long index;
	struct hash_entry_t **entry_h = NULL;

	assert(data != NULL);

	index = hash(data);

	if (hash_key[index].entry == NULL) {
		if (debug) printf("hash_find: Key not found at index: %llu\n", index);

		return NULL;
	}

	/* Walk though entries until we find a match or the end. */
	entry_h = &hash_key[index].entry;
	while (*entry_h != NULL && (*entry_h)->data->dst.s_addr != data->dst.s_addr && (*entry_h)->data->tos != data->tos && (*entry_h)->data->pri != data->pri) {
		entry_h = &(*entry_h)->next;
	}

	/* Allocate new hash_entry_t, if needed. */
	if (*entry_h == NULL) {
		if (debug) printf("hash_find: Key not found at index: %llu\n", index);

		return NULL;
	}

	if (debug) printf("hash_find: Key found at index: %llu addr: %8p\n", index, (*entry_h)->data);

	return (*entry_h)->data;
}

int hash_stats_print(void) {
	int val;
	char stats[1024];

	val = hash_stats(stats, sizeof(stats));

	printf("%s\n", stats);

	if (val != 0) {
		printf("increase HASH_WIDTH in prefs.h and recompile to lower depth (improve efficiency)\n");
	}

	return val;
}

int hash_stats(char* stats, size_t len) {
	unsigned long long i, zero = 0, one = 0, two = 0, max = 0;
	float depth = 0, effic = 0;

	for (i = 0; i < HASH_WIDTH; i++) {
		if (hash_key[i].depth == 0) zero++;
		if (hash_key[i].depth == 1) one++;
		if (hash_key[i].depth == 2) two++;
		if (hash_key[i].depth > max) max = hash_key[i].depth;
		depth += hash_key[i].depth;
	}
	depth = depth / HASH_WIDTH;
	effic = (float) collisions / ((float) inserts + 0.01) * 100.0;

	snprintf(stats, len, "add: %llu  del: %llu  col: %llu (%0.1f%%)  width: %u  depth: %0.1f avg, %llu max, %llu two, %llu one, %llu zero\n", inserts, deletes, collisions, effic, HASH_WIDTH, depth, max, two, one, zero);
	if (depth >= 2.0 && warn) {
		warn = 0;
		return 1;
	}

	return 0;
}

unsigned long long hash(const struct data_t *data) {
	unsigned long long k1, k2, k3, hash;
	
	k1 = data->dst.s_addr;
	k2 = data->tos;
	k3 = data->pri;

//	if (debug) printf(">k1 %08x, k2 %u, k3 %u, hash %u\n", k1, k2, k3, 0);

#if defined HASH_ALGO_INT1
	k1 = (unsigned int) (((k1 & 0xF0F0F0F0) >> 4) | ((k1 & 0x0F0F0F0F) << 4));
	k2 = k2 << 22;
	k3 = k3 << 15;
#elif defined HASH_ALGO_INT2
	k1 = (unsigned int) ((k1 << 16) | (k1 >> 16)) ^ 0xF0F0F0F0;
	k2 = k2 << 22;
	k3 = k3 << 15;
#elif defined HASH_ALGO_INT3
	k2 = k2 << 22;
	k3 = k3 << 15;
#else
	#error No hash algo selected. Specify HASH_ALGO_INT1, HASH_ALGO_INT2 or HASH_ALGO_INT3.
#endif

	hash = (unsigned long long) ((k1 ^ k2 ^ k3) % HASH_WIDTH);

//	if (debug) printf("<k1 %08x, k2 %u, k3 %u, hash %u\n", k1, k2, k3, hash);

	return hash;
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim: noet ts=3 sw=3
*/
