/*
 * hash.c
 * (C) Christian Magnusson 1999, Email: d1mag@dtek.chalmers.se
 * This program is copyright, and covered by the Gnu Public License.
 * The Q bot.
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "hash.h"
#include "bottypes.h"
#include "globals.h"
#include "dl.h"


/*
  Compare functions for Find() should return: 
  1 if not equal
  0 if equal
*/
int compare_strcmp(const void *i, const void *j)
{
	return strcmp((char *) i, (char *) j);
}

int compare_strcasecmp(const void *i, const void *j)
{
	return strcasecmp((char *) i, (char *) j);
}

int compare_int(const void *i, const void *j)
{
	return (*((int *) i) != *((int *) j));
}

int compare_pointer(const void *i, const void *j)
{
	return (i != j);
}

/*
  Return a hash value for a NOT casesensitive string.
*/
int hash_string(char *d)
{
	unsigned int c, i, s = 0;
	if (d) {
		/* for(i=0; (c = (tolower(((unsigned char)d[i])))); i++) s += c; */
		do {
			i = 0;
			c = (unsigned char) tolower(d[i++]);
			if (!c)
				break;
			s += c;
			s <<= 5;
			c = (unsigned char) tolower(d[i++]);
			if (!c)
				break;
			s += c;
			s <<= 3;
			c = (unsigned char) tolower(d[i++]);
			if (!c)
				break;
			s += c;
			s <<= 1;
			c = (unsigned char) tolower(d[i++]);
			if (!c)
				break;
			s += c;
			s <<= 1;
			c = (unsigned char) tolower(d[i++]);
			if (!c)
				break;
			s += c;
			/* 
			   if( ! (c = (tolower(((unsigned char)d[i++]))))) break; s += c; if( ! (c =
			   (tolower(((unsigned char)d[i++]))))) break; s += c; */
		} while (0);
	}
	return s;
}

/*
  Return a hash value for a casesensitive string.
*/
int hash_string_casesens(char *d)
{
	unsigned int c, i, s = 0;
	if (d) {
		/* for(i=0; (c = ((unsigned char)d[i])); i++) s += c; */
		do {
			i = 0;
			c = (unsigned char) d[i++];
			if (!c)
				break;
			s += c;
			s <<= 5;
			c = (unsigned char) d[i++];
			if (!c)
				break;
			s += c;
			s <<= 3;
			c = (unsigned char) d[i++];
			if (!c)
				break;
			s += c;
			s <<= 1;
			c = (unsigned char) d[i++];
			if (!c)
				break;
			s += c;
			/* 
			   if( ! (c = (((unsigned char)d[i++])))) break; s += c; if( ! (c = (((unsigned
			   char)d[i++])))) break; s += c; */
		} while (0);
	}
	return s;
}

/*
  Return a pointer to the hash-key in the object.
*/
void *GetData(void *data, int hash_type)
{
	void *res;
	switch (hash_type) {
	case H_AUSER_NICK:
		res = (((struct activeuser *) (data))->nick);
		break;
	case H_AUSER_YXX:
		res = (((struct activeuser *) (data))->yxx);
		break;
	case H_DBUSER_NICK:
		res = (((struct dbaseuser *) (data))->nick);
		break;
	case H_DBUSER_YXX:
		res = (((struct dbaseuser *) (data))->last_yxx);
		break;
	case H_DBUSER_USERID:
		res = &(((struct dbaseuser *) (data))->userid);
		break;
	case H_CHANNEL_NAME:
		res = (((struct chanopts *) (data))->channel);
		break;
	case H_COMMAND_NAME:
		res = (((dl_command_entry_t *) (data))->name);
		break;
#ifdef DMALLOC
	case H_MEMBLOCK:
		res = (((mb *) (data))->ptr);
		break;
	case H_MEMSIZE:
		res = &(((mb *) (data))->sz);
		break;
#endif
	default:
#if DEBUG
		log("*** GetData ERROR unknown hash_type %d\n", hash_type);
		printf("*** GetData ERROR unknown hash_type %d\n", hash_type);
#endif
		res = data;
		break;
	}
	return res;
}


/*
  Return a hash key from a specific data type
*/
unsigned int Hash(void *data, int hash_type, int max)
{
	unsigned int s;
	if (!data) {
#if DEBUG
		log("*** Hash ERROR called with NULL pointer\n");
		printf("*** Hash ERROR called with NULL pointer\n");
#endif
		return 0;
	}
	switch (hash_type) {
	case H_AUSER_NICK:
		s = hash_string((char *) data);
		break;
	case H_AUSER_YXX:
		s = hash_string_casesens((char *) data);
		break;
	case H_DBUSER_NICK:
		s = hash_string((char *) data);
		break;
	case H_DBUSER_YXX:
		s = hash_string_casesens((char *) data);
		break;
	case H_DBUSER_USERID:
		s = *((int *) data);
		break;
	case H_CHANNEL_NAME:
		s = hash_string((char *) data);
		break;
	case H_COMMAND_NAME:
		s = hash_string((char *) data);
		break;
#ifdef DMALLOC
	case H_MEMBLOCK:
		s = (unsigned int) data;
		break;
	case H_MEMSIZE:
		s = *((unsigned int *) data);
		break;
#endif
	default:
#if DEBUG
		log("*** Hash ERROR unknown hash_type %d\n", hash_type);
		printf("*** Hash ERROR unknown hash_type %d\n", hash_type);
#endif
		if (data) {
			s = *((unsigned char *) data);
		} else {
			s = 0;
		}
		break;
	}
	return (s % max);
}


unsigned int HashElem(ListElem * node, int hash_type, int max)
{
	if (!node) {
#if DEBUG
		log("*** HashElem: ERROR node==NULL\n");
		printf("*** HashElem: ERROR node==NULL\n");
#endif
		return 0;
	}
	return Hash(GetData(node->data, hash_type), hash_type, max);
}


ListElem *Find(HashTable * hash, void *data, int (*compare) (const void *, const void *))
{
	unsigned int i;
	int hash_type;
	List *list;
	ListElem *el;

	if (!hash) {
#if DEBUG
		printf("*** Find: ERROR hash==NULL\n");
		log("*** Find: ERROR hash==NULL\n");
#endif
		return NULL;
	}
	hash_type = hash->hash_type;
	i = Hash(data, hash_type, hash->sz);
	list = (*(hash->list))[i];

	if (!(el = GetHead(list))) {
		return NULL;
	}

	while (el && compare(GetData(el->data, hash_type), data)) {
		el = GetSucc(el);
	}
	/* 
	   If el!=NULL then we have found an object matching the search If el==NULL then we failed to
	   find anything */
	return el;
}

HashTable *NewHashTable(int sz, int hash_type)
{
	/* initialize a new hashtable */
	int i;
	HashTable *hash = Malloc(sizeof(HashTable));
	if (hash) {
		hash->sz = sz;
		hash->hash_type = hash_type;
		hash->list = (List * (*)[])Malloc(sz * sizeof(List *));
		for (i = 0; i < sz; i++) {
			(*(hash->list))[i] = (List *) NewList(hash_type);
		}
	}
	return hash;
}

void FreeHashTable(HashTable * hash)
{
	/* Free up a hashtable and all the nodes in it */
	int sz, i;
	if (!hash)
		return;
	sz = hash->sz;
	for (i = 0; i < sz; i++) {
		FreeList((*(hash->list))[i]);
	}
	Free(hash->list);
	hash->sz = 0;
	Free(hash);
	return;
}

#if 1
/*
  Here are some REAL ugly functions to search linear in a hash table!
  Only used in the debug malloc function which dump the stats. Only called
  if asked for it.
*/
ListElem *GetFirstH(HashTable * hash)
{
	int i;
	List *list;
	for (i = 0; i < hash->sz; i++) {
		list = (*(hash->list))[i];
		if (list && list->head)
			return list->head;
	}
	return NULL;
}

ListElem *GetSuccH(HashTable * hash, ListElem * el)
{
	int i;
	List *list;
	if (!el)
		return NULL;
	if (el->next)
		return el->next;

	for (i = 0; i < hash->sz; i++) {
		list = (*(hash->list))[i];
		if (list) {
			if (list->tail == el)
				break;
		}
	}
	i++;
	if (i >= hash->sz)
		return NULL;

	for (; i < hash->sz; i++) {
		list = (*(hash->list))[i];
		if (list && list->head)
			return list->head;
	}
	return NULL;
}
#endif

List *NewList(int type)
{
	/* initialize a new list */
	List *list = (List *) Malloc(sizeof(List));
	if (list) {
		list->head = NULL;
		list->tail = NULL;
		list->type = type;
	}
	return list;
}

void FreeElem(ListElem * el)
{
	struct chanpriv *cp, *cp2;
	struct chanmod *cm, *cm2;
	struct ban *b, *bp;
	if (!el) {
#if DEBUG
		log("*** FreeElem: el==NULL\n");
		printf("*** FreeElem: el==NULL\n");
#endif
		return;
	}
#if DEBUG
	if (el->next) {
		log("FreeElem: el->next!=NULL\n");
		printf("FreeElem: el->next!=NULL\n");
	}
	if (el->prev) {
		log("FreeElem: el->prev!=NULL\n");
		printf("FreeElem: el->prev!=NULL\n");
	}
#endif

	if (el->data) {
		switch ((el->list)->type) {
		case L_AUSER:
			/* free the chanmode list */
			cm = ((struct activeuser *) el->data)->chanmodes;
			while (cm) {
				cm2 = cm->next;
				Free(cm->channel);
				cm->channel = NULL;
				Free(cm);
				cm = cm2;
			}
			((struct activeuser *) el->data)->chanmodes = NULL;
			Free(el->data);
			el->data = NULL;
			break;
		case L_DBUSER:
			/* 
			   Free the channel list for a user */
			cp = ((struct dbaseuser *) el->data)->privs;
			while (cp) {
				cp2 = cp->next;
				Free(cp);
				cp = cp2;
			}
			((struct dbaseuser *) el->data)->privs = NULL;
			if (((struct dbaseuser *) el->data)->nick) {
				Free(((struct dbaseuser *) el->data)->nick);
				((struct dbaseuser *) el->data)->nick = NULL;
			}
			if (((struct dbaseuser *) el->data)->email) {
				Free(((struct dbaseuser *) el->data)->email);
				((struct dbaseuser *) el->data)->email = NULL;
			}
			if (((struct dbaseuser *) el->data)->last_hostname) {
				Free(((struct dbaseuser *) el->data)->last_hostname);
				((struct dbaseuser *) el->data)->last_hostname = NULL;
			}
			Free(el->data);
			el->data = NULL;
			break;
		case L_CHANNEL:
			b = ((struct chanopts *) el->data)->bans;
			while (b) {
				bp = b->next;
				Free(b);
				b = bp;
			}
			((struct chanopts *) el->data)->bans = NULL;
			if (((struct chanopts *) el->data)->suspended
				&& ((struct chanopts *) el->data)->suspend_reason) {
				Free(((struct chanopts *) el->data)->suspend_reason);
				((struct chanopts *) el->data)->suspend_reason = NULL;
			}
			Free(el->data);
			el->data = NULL;
			break;
		case L_GLINE:
		case L_TRUSTED:
		case L_CHANTYPE:
		case L_NICKCHANGE:
			Free(el->data);
			el->data = NULL;
			break;

			/* 
			   don't do a sh*t here... The data which the hash tables points at, are freed when the
			   corresponding list is free'd. Set the data pointer yo NULL since there are more objects
			   pointing to it. Just free the ListElem pointer below. */
		case H_COMMAND_NAME:
			el->data = NULL;
			break;
		case H_CHANNEL_NAME:
			el->data = NULL;
			break;
		case H_DBUSER_NICK:
			el->data = NULL;
			break;
		case H_DBUSER_YXX:
			el->data = NULL;
			break;
		case H_DBUSER_USERID:
			el->data = NULL;
			break;
		case H_AUSER_NICK:
			el->data = NULL;
			break;
		case H_AUSER_YXX:
			el->data = NULL;
			break;
		default:
			/* 
			   This should never happen. */
#if DEBUG
			log("*** FreeElem: ERROR free'd unknown object type\n");
			printf("*** FreeElem: ERROR free'd unknown object type\n");
#endif
			Free(el->data);
			el->data = NULL;
			break;
		}
	} else {
#if DEBUG
		log("*** FreeElem: ERROR Empty object ???\n");
		printf("*** FreeElem: ERROR Empty object ???\n");
#endif
	}
	el->list = NULL;
	Free(el);
	return;
}

void FreeList(List * list)
{
	ListElem *el, *el2;
	/* Free up a list and all the nodes in it */
	if (!list) {
#if DEBUG
		printf("*** FreeList: list==NULL\n");
		log("*** FreeList: list==NULL\n");
#endif
		return;
	}
	el = list->head;
	while (el) {
		el2 = el->next;
		Remove(el);
		FreeElem(el);
		el = el2;
	}
	Free(list);
	return;
}

ListElem *NewElem(void *val)
{
	ListElem *p = (ListElem *) Malloc(sizeof(ListElem));
	if (p) {
		p->prev = NULL;
		p->next = NULL;
		p->list = NULL;
		p->data = val;
	} else {
#if DEBUG
		printf("*** NewElem: ERROR malloc\n");
		log("*** NewElem: ERROR malloc\n");
#endif
	}
	return p;
}

ListElem *GetHead(List * list)
{
	if (!list) {
#if DEBUG
		printf("GetHead: NULL\n");
#endif
		return NULL;
	}
	return list->head;
}

ListElem *GetTail(List * list)
{
	if (!list) {
#if DEBUG
		printf("GetTail: NULL\n");
#endif
		return NULL;
	}
	return list->tail;
}

ListElem *RemTail(List * list)
{
	ListElem *el;
	if (!list) {
#if DEBUG
		printf("RemTail: NULL\n");
#endif
		return NULL;
	}
	el = list->tail;
	if (el) {
		if (list->head == el) {
			list->head = NULL;
		}
		if (el->prev) {
			(el->prev)->next = NULL;
			list->tail = el->prev;
		}
	}
	return el;
}

ListElem *GetPred(ListElem * el)
{
	/* get predecessor */
#if 0
	if (!el) {
		printf("GetPred: NULL\n");
		return NULL;
	}
#endif
	return el->prev;
}

ListElem *GetSucc(ListElem * el)
{
	/* get successor */
#if 0
	if (!el) {
		printf("GetSucc: NULL\n");
		return NULL;
	}
#endif
	return el->next;
}

void Remove(ListElem * el)
{
	/* removes node from list, don't Free it */
	List *list;
	if (!el) {
		/* this has happened once... don't know why */
#ifdef DEBUG
		printf("*** Remove: ERROR el=NULL\n");
		log("*** Remove: ERROR el=NULL\n");
#endif
		return;
	}
	list = el->list;
	if (!list) {
#ifdef DEBUG
		printf("*** Remove: ERROR no list set\n");
		log("*** Remove: ERROR no list set\n");
#endif
		return;
	}

	/* 
	   unlink from list and make sure head & tail pointers are updated */
	if (el->prev) {
		(el->prev)->next = el->next;	/* this row has caused a coredump once ??!! */
	}
	if (el->next) {
		(el->next)->prev = el->prev;
	}
	if (el == list->head) {
		list->head = el->next;
	}
	if (el == list->tail) {
		list->tail = el->prev;
	}
	/* 
	   it's now unlinked from the list Save the el->list AND el->data value to make sure it's freed
	   correct. */
	el->next = NULL;
	el->prev = NULL;
	return;
}

void AddHead(List * list, ListElem * new)
{
	if (!list) {
#if DEBUG
		printf("AddHead: NULL\n");
#endif
		return;
	}
	new->list = list;
	new->next = list->head;
	new->prev = NULL;
	if (list->head) {
		(list->head)->prev = new;
	}
	list->head = new;
	if (!list->tail) {
		/* first AND last object in list */
		list->tail = new;
	}
	return;
}

void AddTail(List * list, ListElem * new)
{
	if (!list) {
#if DEBUG
		log("*** AddTail: ERROR list==NULL\n");
		printf("*** AddTail: ERROR list==NULL\n");
#endif
		return;
	}
	new->list = list;
	new->prev = list->tail;
	new->next = NULL;
	if (list->tail) {
		(list->tail)->next = new;
	}
	list->tail = new;
	if (!list->head) {
		/* first object in list */
		list->head = new;
	}
	return;
}

void AddElem(HashTable * hash, ListElem * new)
{
	int i;
	if (!hash) {
#if DEBUG
		log("*** AddElem: ERROR hash==NULL\n");
		printf("*** AddElem: ERROR hash==NULL\n");
#endif
		return;
	}
	i = HashElem(new, hash->hash_type, hash->sz);
	if (!(*(hash->list))[i]) {
#if DEBUG
		log("*** AddElem: ERROR NULL  i=%d %d %d\n", i, hash->hash_type, hash->sz);
		printf("*** AddElem: ERROR NULL  i=%d %d %d\n", i, hash->hash_type, hash->sz);
#endif
		return;
	}
	AddTail((*(hash->list))[i], new);
	return;
}

ListElem *RemHead(List * list)
{
	/* remove and return the head */
	ListElem *el;
	if (!list) {
#if DEBUG
		log("*** RemHead: ERROR list==NULL\n");
		printf("*** RemHead: ERROR list==NULL\n");
#endif
		return NULL;
	}
	el = list->head;
	if (el->next) {
		(el->next)->prev = NULL;
	}
	list->head = el->next;
	if (el == list->tail) {
		list->tail = NULL;
	}
	return el;
}
