/*
 * Copyright (c) 2007,2008 Paulo Cesar Pereira de Andrade
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Author: Paulo Cesar Pereira de Andrade
 */

#include "util.h"
#include <stdlib.h>
#include <string.h>

/*
 *   This is a very simplified and adapted version of the hash tables I am
 * using in a personal project. It was added to try to have a single hash
 * table implementation in xedit. The lisp (for user code) version was not
 * converted, but the following hastables have been converted:
 *  ispell.c - list of replace and ignore words
 *  hook.c   - list of auto replace words
 *  internal lisp data structures:
 *	atoms
 *	strings
 *	packages
 *	opaque types
 *   also, all code traversing hash tables is now using
 *	hash_iter_first() and hash_iter_next()
 *  conversion isn't as good as I originally wanted, code is using hash_check
 *  instead of hash_get, but this is due to the code not having a basic
 *  { void *data; int length; } object to store string like objects
 *
 *   Also, this hash table implementation was added mainly for the tags
 * support.
 */

/*
 * Prototypes
 */
static int hash_equal(hash_table *hash, hash_key *left, hash_key *right);
static unsigned int hash_value(hash_key *key);


/*
 * Implementation
 */
static int
hash_equal(hash_table *hash, hash_key *left, hash_key *right)
{
    if (left->length == right->length) {
	if (left == right)
	    return (1);
	if (hash->compare)
	    return (hash->compare(left, right));
	return (memcmp(left->value, right->value, left->length) == 0);
    }

    return (0);
}

static unsigned int
hash_data(const char *value, unsigned int length)
{
    const char		*ptr;
    unsigned int	i, key;

    for (i = key = 0, ptr = value; i < length; i++)
	key = (key << (key & 1)) ^ ptr[i];

    return (key);
}

static unsigned int
hash_value(hash_key *key)
{
    return (hash_data(key->value, key->length));
}

hash_table *
hash_new(unsigned int length, hash_compare compare)
{
    hash_table	*hash;

    hash = calloc(1, sizeof(hash_table));
    if (hash) {
	hash->entries = calloc(length, sizeof(hash_entry *));
	if (hash->entries) {
	    hash->length = length;
	    hash->compare = compare;
	    hash->iter.offset = -1;
	}
	else {
	    free(hash);
	    hash = (hash_table *)0;
	}
    }

    return (hash);
}

hash_entry *
hash_put(hash_table *hash, hash_entry *entry)
{
    unsigned int	key;
    hash_entry		*prev, *ptr;

    /* Offset in hash table vector for this entry */
    key = hash_value(entry->key) % hash->length;

    /* We hope this is nil for most calls */
    ptr = hash->entries[key];

    /* Check if clashed with another entry */
    for (prev = ptr; ptr; prev = ptr, ptr = ptr->next) {
	/* Replace current entry */
	if (hash_equal(hash, entry->key, ptr->key)) {
	    /* If not trying to readd same value */
	    if (entry != ptr) {
		if (ptr == prev)
		    hash->entries[key] = entry;
		else
		    prev->next = entry;
		entry->next = ptr->next;
		/* Finished */
	    }
	    else
		ptr = (hash_entry *)0;
	    goto hash_put_done;
	}
    }

    /* Add new entry */
    if (prev == (hash_entry *)0)
	/* If no entry in offset */
	hash->entries[key] = entry;
    else
	/* Add to end of clashing list */
	prev->next = entry;
    entry->next = (hash_entry *)0;

    /* Increase sum of entries counter*/
    ++hash->count;

hash_put_done:
    /* ptr will be nil if no entry was replaced, of tried to add
     * again an entry already in the hash table */
    return (ptr);
}

hash_entry *
hash_get(hash_table *hash, hash_key *name)
{
    unsigned int	key;
    hash_entry		*entry;

    key = hash_value(name) % hash->length;
    for (entry = hash->entries[key]; entry; entry = entry->next) {
	if (hash_equal(hash, name, entry->key)) {

	    return (entry);
	}
    }

    return ((hash_entry *)0);
}

hash_entry *
hash_check(hash_table *hash, const char *name, unsigned int length)
{
    unsigned int	key;
    hash_entry		*entry;

    key = hash_data(name, length) % hash->length;
    for (entry = hash->entries[key]; entry; entry = entry->next) {
	if (length == entry->key->length &&
	    memcmp(name, entry->key->value, length) == 0) {

	    return (entry);
	}
    }

    return ((hash_entry *)0);
}

hash_entry *
hash_rem_no_free(hash_table *hash, hash_entry *entry)
{
    unsigned int	key;
    hash_entry		*ptr, *prev;

    key = hash_value(entry->key) % hash->length;
    for (ptr = prev = hash->entries[key]; ptr; prev = ptr, ptr = ptr->next) {
	if (ptr == entry) {
	    --hash->count;
	    if (ptr == prev)
		hash->entries[key] = ptr->next;
	    else
		prev->next = ptr->next;
	    break;
	}
    }

    if (ptr && ptr == hash->iter.entry)
	hash->iter.entry = ptr->next;

    /* If entry wasn't in hash table ptr will be nil */
    return (ptr);
}

void
hash_rem(hash_table *hash, hash_entry *entry)
{
    entry = hash_rem_no_free(hash, entry);
    if (entry) {
	free(entry->key->value);
	free(entry->key);
	free(entry);
    }
}

void
hash_rehash(hash_table *hash, unsigned int length)
{
    unsigned int	i, key;
    hash_entry		*entry, *next, **entries;

    entries = (hash_entry **)calloc(length, sizeof(hash_entry *));
    if (entries) {
	/* Populate the new table, note that clashes are now in reverse order */
	for (i = 0; i < hash->length; i++) {
	    for (entry = hash->entries[i]; entry; entry = next) {
		next = entry->next;
		key = hash_value(entry->key) % length;
		entry->next = entries[key];
		entries[key] = entry;
	    }
	}

	/* Finish updating hash table */
	free(hash->entries);
	hash->entries = entries;
	hash->length = length;
    }
    hash->iter.offset = -1;
}

hash_entry *
hash_iter_first(hash_table *hash)
{
    hash->iter.offset = 0;
    hash->iter.entry = (hash_entry *)0;

    return (hash_iter_next(hash));
}

hash_entry *
hash_iter_next(hash_table *hash)
{
    if (hash->iter.offset >= 0) {
	if (hash->iter.entry) {
	    if ((hash->iter.entry = hash->iter.entry->next))
		return (hash->iter.entry);
	    ++hash->iter.offset;
	}
	for (; hash->iter.offset < hash->length; hash->iter.offset++) {
	    if ((hash->iter.entry = hash->entries[hash->iter.offset]))
		return (hash->iter.entry);
	}
	hash->iter.entry = (hash_entry *)0;
	hash->iter.offset = -1;
    }

    return ((hash_entry *)0);
}

void
hash_clr(hash_table *hash)
{
    unsigned int	i;
    hash_entry		*entry, *next;

    /* Extra data should be free'd with the iterator */
    for (i = 0; i < hash->length; i++) {
	entry = hash->entries[i];
	if (entry) {
	    for (next = entry; entry; entry = next) {
		next = entry->next;
		free(entry->key->value);
		free(entry->key);
		free(entry);
	    }
	    hash->entries[i] = (hash_entry *)0;
	}
    }

    hash->count = 0;
    hash->iter.offset = -1;
}

void
hash_del(hash_table *hash)
{
    hash_clr(hash);
    free(hash->entries);
    free(hash);
}
