/*
 * Mixed hash table / binary tree code.
 * (c) Thomas Pornin 2002
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 4. The name of the authors may not be used to endorse or promote
 *    products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <stddef.h>
#include <string.h>
#include <limits.h>
#include "nhash.h"
#include "mem.h"

/*
 * Hash a string into an `unsigned' value. This function is derived
 * from the hash function used in the ELF binary object file format
 * hash tables. The result size is a 32-bit number if the `unsigned'
 * type is big enough to hold 32-bit arbitrary numbers, a 16-bit number
 * otherwise.
 */
static unsigned hash_string(char *name)
{
	unsigned h = 0;

	for (h = 0; *name; name ++) {
		unsigned g;

		h = (h << 4) + *(unsigned char *)name;
#if UINT_MAX >= 0xffffffffU
		g = h & 0xF0000000U;
		h ^= (g >> 24);
#else
		g = h & 0xF000U;
		h ^= (g >> 12);
#endif
		h &= ~g;
	}
	return h;
}

/*
 * Each item in the table is a structure beginning with a `hash_item_header'
 * structure. Those headers define binary trees such that all left-descendants
 * (respectively right-descendants) of a given tree node have an associated
 * hash value strictly smaller (respectively greater) than the hash value
 * associated with this node.
 *
 * The `ident' field points to an array of char. The `sizeof(unsigned)'
 * first `char' contain a copy of an `unsigned' value which is the hashed
 * string, except the least significant bit. When this bit is set to 0,
 * the node contains the unique item using that hash value. If the bit
 * is set to 1, then there are several items with that hash value.
 *
 * When several items share the same hash value, they are linked together
 * in a linked list by their `left' field. The node contains no data;
 * it is a "fake item".
 *
 * The `char' following the hash value encode the item name for true items.
 * For fake items, they contain the pointer to the first true item of the
 * corresponding link list (suitably aligned).
 *
 * There are HTT_NUM_TREES trees; the items are sorted among trees by the
 * lest significant bits of their hash value.
 */

static void internal_init(HTT *htt, void (*deldata)(void *), int reduced)
{
	htt->deldata = deldata;
	if (reduced) {
		HTT2 *htt2 = (HTT2 *)htt;

		htt2->tree[0] = htt2->tree[1] = NULL;
	} else {
		unsigned u;

		for (u = 0; u < HTT_NUM_TREES; u ++) htt->tree[u] = NULL;
	}
}

/* see nhash.h */
void HTT_init(HTT *htt, void (*deldata)(void *))
{
	internal_init(htt, deldata, 0);
}

/* see nhash.h */
void HTT2_init(HTT2 *htt, void (*deldata)(void *))
{
	internal_init((HTT *)htt, deldata, 1);
}

#define PTR_SHIFT    (sizeof(hash_item_header *) * \
                      ((sizeof(unsigned) + sizeof(hash_item_header *) - 1) / \
                       sizeof(hash_item_header *)))

#define TREE(u)    (*(reduced ? ((HTT2 *)htt)->tree + ((u) & 1) \
                              : htt->tree + ((u) & (HTT_NUM_TREES - 1))))

/*
 * Find a node for the given hash value. If `father' is not NULL, fill
 * `*father' with a pointer to the node's father.
 * If the return value is NULL, then no existing node was found; if `*father'
 * is also  NULL, the tree is empty. If the return value is not NULL but
 * `*father' is NULL, then the found node is the tree root.
 *
 * If `father' is not NULL, then `*leftson' is filled with 1 if the node
 * was looked for as the father left son, 0 otherwise.
 */
static hash_item_header *find_node(HTT *htt, unsigned u,
	hash_item_header **father, int *leftson, int reduced)
{
	hash_item_header *node = TREE(u);
	hash_item_header *nodef = NULL;
	int ls = 0;

	u &= ~1U;
	while (node != NULL) {
		unsigned v = *(unsigned *)(node->ident);
		unsigned w = v & ~1U;

		if (u == w) break;
		nodef = node;
		if (u < w) {
			node = node->left;
			ls = 1;
		} else {
			node = node->right;
			ls = 0;
		}
	}
	if (father != NULL) {
		*father = nodef;
		*leftson = ls;
	}
	return node;
}

static void *internal_get(HTT *htt, char *name, int reduced)
{
	unsigned u = hash_string(name), v;
	hash_item_header *node = find_node(htt, u, NULL, NULL, reduced);

	if (node == NULL) return NULL;
	v = *(unsigned *)(node->ident);
	if ((v & 1U) == 0) {
		return (strcmp(HASH_ITEM_NAME(node), name) == 0) ? node : NULL;
	}
	node = *(hash_item_header **)(node->ident + PTR_SHIFT);
	while (node != NULL) {
		if (strcmp(HASH_ITEM_NAME(node), name) == 0) return node;
		node = node->left;
	}
	return NULL;
}

/* see nhash.h */
void *HTT_get(HTT *htt, char *name)
{
	return internal_get(htt, name, 0);
}

/* see nhash.h */
void *HTT2_get(HTT2 *htt, char *name)
{
	return internal_get((HTT *)htt, name, 1);
}

/*
 * Make an item identifier from its name and its hash value.
 */
static char *make_ident(char *name, unsigned u)
{
	size_t n = strlen(name) + 1;
	char *ident = getmem(n + sizeof(unsigned));

	*(unsigned *)ident = u & ~1U;
	memcpy(ident + sizeof(unsigned), name, n);
	return ident;
}

/*
 * Make an identifier for a fake item, pointing to a true item.
 */
static char *make_fake_ident(unsigned u, hash_item_header *next)
{
	char *ident = getmem(PTR_SHIFT + sizeof(hash_item_header *));

	*(unsigned *)ident = u | 1U;
	*(hash_item_header **)(ident + PTR_SHIFT) = next;
	return ident;
}

/*
 * Adding an item is straightforward:
 *  1. look for its emplacement
 *  2. if no node is found, use the item as a new node and link it to the tree
 *  3. if a node is found:
 *     3.1. if the node is real, check for name inequality, then create a
 *          fake node and assemble the two-element linked list
 *     3.2. if the node is fake, look for the name in the list; if not found,
 *          add the node at the list end
 */
static void *internal_put(HTT *htt, void *item, char *name, int reduced)
{
	unsigned u = hash_string(name), v;
	int ls;
	hash_item_header *father;
	hash_item_header *node = find_node(htt, u, &father, &ls, reduced);
	hash_item_header *itemg = item, *pnode = NULL;

	if (node == NULL) {
		itemg->left = itemg->right = NULL;
		itemg->ident = make_ident(name, u);
		if (father == NULL) {
			TREE(u) = itemg;
		} else if (ls) {
			father->left = itemg;
		} else {
			father->right = itemg;
		}
		return NULL;
	}
	v = *(unsigned *)(node->ident);
	if ((v & 1U) == 0) {
		if (strcmp(HASH_ITEM_NAME(node), name) == 0)
			return node;
		pnode = getmem(sizeof *pnode);
		pnode->left = node->left;
		pnode->right = node->right;
		pnode->ident = make_fake_ident(u, node);
		node->left = itemg;
		node->right = NULL;
		itemg->left = itemg->right = NULL;
		itemg->ident = make_ident(name, u);
		if (father == NULL) {
			TREE(u) = pnode;
		} else if (ls) {
			father->left = pnode;
		} else {
			father->right = pnode;
		}
		return NULL;
	}
	node = *(hash_item_header **)(node->ident + PTR_SHIFT);
	while (node != NULL) {
		if (strcmp(HASH_ITEM_NAME(node), name) == 0) return node;
		pnode = node;
		node = node->left;
	}
	itemg->left = itemg->right = NULL;
	itemg->ident = make_ident(name, u);
	pnode->left = itemg;
	return NULL;
}

/* see nhash.h */
void *HTT_put(HTT *htt, void *item, char *name)
{
	return internal_put(htt, item, name, 0);
}

/* see nhash.h */
void *HTT2_put(HTT2 *htt, void *item, char *name)
{
	return internal_put((HTT *)htt, item, name, 1);
}

/*
 * A fake node subnode list has shrunk to one item only; make the
 * node real again.
 *   fnode    the fake node
 *   node     the last remaining node
 *   father   the fake node father (NULL if the fake node is root)
 *   leftson  1 if the fake node is a left son, 0 otehrwise
 *   u        the hash value for this node
 */
static void shrink_node(HTT *htt, hash_item_header *fnode,
	hash_item_header *node, hash_item_header *father, int leftson,
	unsigned u, int reduced)
{
	node->left = fnode->left;
	node->right = fnode->right;
	if (father == NULL) {
		TREE(u) = node;
	} else if (leftson) {
		father->left = node;
	} else {
		father->right = node;
	}
	freemem(fnode->ident);
	freemem(fnode);
}

/*
 * Deletion algorithm:
 *  1. look for the node; if not found, exit
 *  2. if the node is real:
 *     2.1. check for equality; exit otherwise
 *     2.2. delete the node
 *     2.3. promote the leftest of right descendants or rightest of left
 *          descendants
 *  3. if the node is fake:
 *     3.1. check the list items for equality; exit otherwise
 *     3.2. delete the correct item
 *     3.3. if there remains only one item, supress the fake node
 */
static int internal_del(HTT *htt, char *name, int reduced)
{
	unsigned u = hash_string(name), v;
	int ls;
	hash_item_header *father;
	hash_item_header *node = find_node(htt, u, &father, &ls, reduced);
	hash_item_header *pnode, *fnode, *znode;
	char *tmp;

	if (node == NULL) return 0;
	v = *(unsigned *)(node->ident);
	if ((v & 1U) != 0) {
		fnode = node;
		node = znode = *(hash_item_header **)(node->ident + PTR_SHIFT);
		pnode = NULL;
		while (node != NULL) {
			if (strcmp(HASH_ITEM_NAME(node), name) == 0) break;
			pnode = node;
			node = node->left;
		}
		if (node == NULL) return 0;
		if (pnode == NULL) {
			/*
			 * We supress the first item in the list.
			 */
			*(hash_item_header **)(fnode->ident + PTR_SHIFT) =
				node->left;
			if (node->left->left == NULL) {
				shrink_node(htt, fnode, node->left,
					father, ls, u, reduced);
			}
		} else {
			pnode->left = node->left;
			if (pnode->left == NULL && znode == pnode) {
				shrink_node(htt, fnode, pnode,
					father, ls, u, reduced);
			}
		}
	} else {
		if (strcmp(HASH_ITEM_NAME(node), name) != 0) return 0;
		if (node->left != NULL) {
			for (znode = node, pnode = node->left; pnode->right;
				znode = pnode, pnode = pnode->right);
			if (znode != node) {
				znode->right = pnode->left;
				pnode->left = node->left;
			}
			pnode->right = node->right;
		} else if (node->right != NULL) {
			for (znode = node, pnode = node->right; pnode->left;
				znode = pnode, pnode = pnode->left);
			if (znode != node) {
				znode->left = pnode->right;
				pnode->right = node->right;
			}
			pnode->left = node->left;
		} else pnode = NULL;
		if (father == NULL) {
			TREE(u) = pnode;
		} else if (ls) {
			father->left = pnode;
		} else {
			father->right = pnode;
		}
	}
	tmp = node->ident;
	htt->deldata(node);
	freemem(tmp);
	return 1;
}

/* see nhash.h */
int HTT_del(HTT *htt, char *name)
{
	return internal_del(htt, name, 0);
}

/* see nhash.h */
int HTT2_del(HTT2 *htt, char *name)
{
	return internal_del((HTT *)htt, name, 1);
}

/*
 * Apply `action()' on all nodes of the tree whose root is given as
 * parameter `node'. If `wipe' is non-zero, the nodes are removed
 * from memory.
 */
static void scan_node(hash_item_header *node, void (*action)(void *), int wipe)
{
	unsigned v;

	if (node == NULL) return;
	scan_node(node->left, action, wipe);
	scan_node(node->right, action, wipe);
	v = *(unsigned *)(node->ident);
	if ((v & 1U) != 0) {
		hash_item_header *pnode, *nnode;

		for (pnode = *(hash_item_header **)(node->ident + PTR_SHIFT);
			pnode != NULL; pnode = nnode) {
			char *tmp = pnode->ident;

			nnode = pnode->left;
			action(pnode);
			if (wipe) freemem(tmp);
		}
		if (wipe) {
			freemem(node->ident);
			freemem(node);
		}
	} else {
		char *tmp = node->ident;

		action(node);
		if (wipe) freemem(tmp);
	}
}

/* see nhash.h */
void HTT_scan(HTT *htt, void (*action)(void *))
{
	unsigned u;

	for (u = 0; u < HTT_NUM_TREES; u ++) {
		scan_node(htt->tree[u], action, 0);
	}
}

/* see nhash.h */
void HTT2_scan(HTT2 *htt, void (*action)(void *))
{
	scan_node(htt->tree[0], action, 0);
	scan_node(htt->tree[1], action, 0);
}

/* see nhash.h */
void HTT_kill(HTT *htt)
{
	unsigned u;

	for (u = 0; u < HTT_NUM_TREES; u ++) {
		scan_node(htt->tree[u], htt->deldata, 1);
	}
}

/* see nhash.h */
void HTT2_kill(HTT2 *htt)
{
	scan_node(htt->tree[0], htt->deldata, 1);
	scan_node(htt->tree[1], htt->deldata, 1);
}
