/*
 * Copyright (C) 2007 François Pesce : francois.pesce (at) gmail (dot) com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * 	http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "lookup3.h"
#include "debug.h"
#include "napr_hash.h"

#define hashsize(n) ((apr_size_t)1<<(n))
#define hashmask(n) (hashsize(n)-1)

static const void *str_get_key(const void *opaque)
{
    return opaque;
}

static apr_size_t str_get_key_len(const void *opaque)
{
    const char *str = opaque;
    return strlen(str);
}

static int str_key_cmp(const void *opaque1, const void *opaque2, apr_size_t len)
{
    const char *str1 = opaque1;
    const char *str2 = opaque2;

    return memcmp(str1, str2, len);
}

static apr_uint32_t str_hash(register const void *opaque, register apr_size_t len)
{
    return hashlittle(opaque, len, 0x1337cafe);
}

struct napr_hash_index_t
{
    napr_hash_t *hash;
    apr_size_t bucket;
    apr_size_t element;		/* of a bucket */
};

struct napr_hash_t
{
    /* void *table[size][ffactor] */
    void ***table;
    /* apr_size_t table[size] contain the number of element for each bucket */
    apr_size_t *filling_table;
    /* parent pool */
    apr_pool_t *pool;
    /* own pool that will be cleaned if a grow of the table occured */
    apr_pool_t *own_pool;
    /* Function to get the key from the datum */
    get_key_callback_fn_t *get_key;
    /* Function to get the key len from the datum */
    get_key_len_callback_fn_t *get_key_len;
    /* Function to compare two keys */
    key_cmp_callback_fn_t *key_cmp;
    /* hash function */
    hash_callback_fn_t *hash;

    /* the number of element contained in all the buckets of the table */
    apr_size_t nel;
    /* the number of buckets */
    apr_size_t size;
    /* desired density */
    apr_size_t ffactor;
    /* the binary mask to apply to the result of a hash function that return a
     * number < size */
    apr_uint32_t mask;
    /* size of the hash is hashsize(power) as mask is hashmask(power) */
    unsigned char power;
};

extern napr_hash_t *napr_hash_str_make(apr_pool_t *pool, apr_size_t nel, apr_size_t ffactor)
{
    return napr_hash_make(pool, nel, ffactor, str_get_key, str_get_key_len, str_key_cmp, str_hash);
}

extern napr_hash_t *napr_hash_make(apr_pool_t *pool, apr_size_t nel, apr_size_t ffactor, get_key_callback_fn_t get_key,
				   get_key_len_callback_fn_t get_key_len, key_cmp_callback_fn_t key_cmp,
				   hash_callback_fn_t hash)
{
    napr_hash_t *result;
    apr_status_t status;

    if (NULL == (result = apr_pcalloc(pool, sizeof(struct napr_hash_t)))) {
	DEBUG_ERR("allocation error");
	return NULL;
    }

    while (hashsize(result->power) < nel)
	result->power++;

    result->size = hashsize(result->power);
    result->mask = hashmask(result->power);
    result->ffactor = ffactor;
    result->get_key = get_key;
    result->get_key_len = get_key_len;
    result->key_cmp = key_cmp;
    result->hash = hash;
    result->pool = pool;

    if (APR_SUCCESS != (status = apr_pool_create(&(result->own_pool), pool))) {
	char errbuf[128];
	DEBUG_ERR("error calling apr_pool_create: %s", apr_strerror(status, errbuf, 128));
	return NULL;
    }
    /*DEBUG_DBG("readjusting size to %" APR_SIZE_T_FMT " to store %" APR_SIZE_T_FMT " elements", result->size, nel); */
    /*DEBUG_DBG("bit mask will be 0x%x", result->mask); */

    if (NULL == (result->table = apr_pcalloc(result->own_pool, result->size * sizeof(void **)))) {
	DEBUG_ERR("allocation error");
	return NULL;
    }

    if (NULL == (result->filling_table = apr_pcalloc(result->own_pool, result->size * sizeof(apr_size_t)))) {
	DEBUG_ERR("allocation error");
	return NULL;
    }

    return result;
}

extern void *napr_hash_search(napr_hash_t *hash, const void *key, apr_size_t key_len, apr_uint32_t *hash_value)
{
    apr_uint32_t key_hash;
    apr_size_t i, nel, bucket;

    key_hash = hash->hash(key, key_len);

    if (NULL != hash_value)
	*hash_value = key_hash;

    bucket = key_hash & hash->mask;
    if (0 != (nel = hash->filling_table[bucket])) {
	for (i = 0; i < nel; i++) {
	    /*DEBUG_DBG( "key[%p] bucket[%"APR_SIZE_T_FMT"][%"APR_SIZE_T_FMT"]=[%p]", key, bucket, i, hash->get_key(hash->table[bucket][i])); */
	    if (key_len == hash->get_key_len(hash->table[bucket][i]))
		if (0 == (hash->key_cmp(key, hash->get_key(hash->table[bucket][i]), key_len)))
		    return hash->table[bucket][i];
	}
    }

    return NULL;
}

static inline apr_status_t napr_hash_rebuild(napr_hash_t *hash)
{
    napr_hash_t *tmp;
    apr_size_t i, j;
    apr_status_t status;

    if (NULL ==
	(tmp =
	 napr_hash_make(hash->pool, hashsize(hash->power + 1), hash->ffactor, hash->get_key, hash->get_key_len,
			hash->key_cmp, hash->hash))) {
	DEBUG_ERR("error calling napr_hash_init");
	return APR_EGENERAL;
    }

    for (i = 0; i < hash->size; i++) {
	for (j = 0; j < hash->filling_table[i]; j++) {
	    /*
	     * no need to do doublon test here as we take data directly from a
	     * hash table
	     */
	    if (APR_SUCCESS !=
		(status =
		 napr_hash_set(tmp, hash->table[i][j],
			       hash->hash(hash->get_key(hash->table[i][j]), hash->get_key_len(hash->table[i][j]))))) {
		char errbuf[128];
		DEBUG_ERR("error calling napr_hash_set: %s", apr_strerror(status, errbuf, 128));
		return status;
	    }
	}
    }
    hash->table = tmp->table;
    hash->filling_table = tmp->filling_table;
    hash->nel = tmp->nel;
    hash->size = tmp->size;
    hash->mask = tmp->mask;
    hash->power = tmp->power;
    apr_pool_destroy(hash->own_pool);
    hash->own_pool = tmp->own_pool;

    return APR_SUCCESS;
}

extern void napr_hash_remove(napr_hash_t *hash, void *data, apr_uint32_t hash_value)
{
    apr_size_t nel, bucket, i, key_len;
    const void *key;

    bucket = hash_value & hash->mask;
    key = hash->get_key(data);
    key_len = hash->get_key_len(data);
    if (0 != (nel = hash->filling_table[bucket])) {
	for (i = 0; i < nel; i++) {
	    //DEBUG_DBG( "key[%.*s] bucket[%i]=[%.*s]", key_len, key, i, hash->get_key_len(hash->table[bucket][i]), hash->get_key(hash->table[bucket][i]));
	    if (key_len == hash->get_key_len(hash->table[bucket][i]))
		if (0 == (hash->key_cmp(key, hash->get_key(hash->table[bucket][i]), key_len))) {
		    /* Remove it, by replacing with the last element if present */
		    if (i != nel - 1) {
			hash->table[bucket][i] = hash->table[bucket][nel - 1];
			hash->table[bucket][nel - 1] = NULL;
		    }
		    else {
			hash->table[bucket][i] = NULL;
		    }
		    hash->filling_table[bucket]--;
		    hash->nel--;
		    break;
		}
	}
    }
    else {
	DEBUG_DBG("try to remove something that is not here");
    }
}

extern apr_status_t napr_hash_set(napr_hash_t *hash, void *data, apr_uint32_t hash_value)
{
    apr_size_t nel, bucket;
    apr_status_t status;

    bucket = hash_value & hash->mask;

    if ((0 == (nel = hash->filling_table[bucket])) & (NULL == hash->table[bucket])) {
	hash->table[bucket] = apr_pcalloc(hash->own_pool, hash->ffactor * sizeof(void *));
    }
    // DEBUG_DBG( "set data %.*s in bucket %u at nel %u", hash->datum_get_key_len(data), hash->datum_get_key(data), bucket, nel);
    hash->table[bucket][nel] = data;
    hash->filling_table[bucket]++;
    hash->nel++;

    if (hash->ffactor <= hash->filling_table[bucket]) {
	// int i;
	// DEBUG_DBG( "rebuilding hash table, because there's %u element in bucket %u ffactor is %u (total element = %u)", 
	//      hash->filling_table[bucket], bucket, hash->ffactor, hash->nel);
	// for(i = 0; i < hash->ffactor; i++) {
	//     DEBUG_DBG( "%.*s", hash->datum_get_key_len(hash->table[bucket][i]), hash->datum_get_key(hash->table[bucket][i]));
	// }

	if (APR_SUCCESS != (status = napr_hash_rebuild(hash))) {
	    char errbuf[128];
	    DEBUG_ERR("error calling napr_hash_rebuild: %s", apr_strerror(status, errbuf, 128));
	    return status;
	}
    }

    return APR_SUCCESS;
}

extern apr_status_t napr_hash_apply_function(const napr_hash_t *hash, function_callback_fn_t function, void *param)
{
    apr_size_t i, j;
    apr_status_t status;

    if (NULL != hash) {
	for (i = 0; i < hash->size; i++) {
	    for (j = 0; j < hash->filling_table[i]; j++) {
		if (APR_SUCCESS != (status = function(hash->table[i][j], param))) {
		    char errbuf[128];
		    DEBUG_ERR("error calling function: %s", apr_strerror(status, errbuf, 128));
		    return status;
		}
	    }
	}
    }

    return APR_SUCCESS;
}

extern apr_size_t napr_hash_get_size(const napr_hash_t *hash)
{
    return hash->size;
}

extern apr_size_t napr_hash_get_nel(const napr_hash_t *hash)
{
    return hash->nel;
}

apr_pool_t *napr_hash_pool_get(const napr_hash_t *thehash)
{
    return thehash->pool;
}

napr_hash_index_t *napr_hash_first(apr_pool_t *pool, napr_hash_t *hash)
{
    napr_hash_index_t *hash_index;
    hash_index = apr_palloc(pool, sizeof(struct napr_hash_index_t));
    hash_index->hash = hash;
    hash_index->bucket = 0;
    hash_index->element = 0;

    if (hash->filling_table[0] > 0)
	return hash_index;

    return napr_hash_next(hash_index);
}

napr_hash_index_t *napr_hash_next(napr_hash_index_t *hash_index)
{
    if ((0 != hash_index->hash->filling_table[hash_index->bucket])
	&& (hash_index->element < (hash_index->hash->filling_table[hash_index->bucket] - 1))) {
	hash_index->element++;
	return hash_index;
    }
    else {
	hash_index->element = 0;
	for (hash_index->bucket += 1; hash_index->bucket < hash_index->hash->size; hash_index->bucket++) {
	    if (0 != hash_index->hash->filling_table[hash_index->bucket])
		break;
	}
	if (hash_index->bucket < hash_index->hash->size)
	    return hash_index;
    }

    return NULL;
}

void napr_hash_this(napr_hash_index_t *hi, const void **key, apr_size_t *klen, void **val)
{
    if (key)
	*key = hi->hash->get_key(hi->hash->table[hi->bucket][hi->element]);
    if (klen)
	*klen = hi->hash->get_key_len(hi->hash->table[hi->bucket][hi->element]);
    if (val)
	*val = hi->hash->table[hi->bucket][hi->element];
}


syntax highlighted by Code2HTML, v. 0.9.1