/*
 * Copyright (C) 2012 Andrew Mortensen
 *
 * This file is part of the sca module for Kamailio, a free SIP server.
 *
 * The sca module is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * The sca module is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA. 02110-1301 USA
 */
#include "sca_common.h"

#include <assert.h>

#include "sca.h"
#include "sca_hash.h"

int sca_hash_table_create(sca_hash_table **ht, unsigned int size)
{
	int i;

	assert(ht != NULL);

	*ht = shm_malloc(sizeof(sca_hash_table));
	if(*ht == NULL) {
		LM_ERR("Failed to shm_malloc space for hash table\n");
		return (-1);
	}

	(*ht)->size = size;
	(*ht)->slots = (sca_hash_slot *)shm_malloc(size * sizeof(sca_hash_slot));
	if((*ht)->slots == NULL) {
		LM_ERR("Failed to shm_malloc hash table slots\n");
		shm_free(*ht);
		*ht = NULL;
		return (-1);
	}
	memset((*ht)->slots, 0, size * sizeof(sca_hash_slot));

	for(i = 0; i < (*ht)->size; i++) {
		if(lock_init(&(*ht)->slots[i].lock) == NULL) {
			LM_ERR("Failed to initialized lock in hash table slot %d\n", i);
			shm_free(*ht);
			*ht = NULL;
			return (-1);
		}
	}

	return (0);
}

int sca_hash_table_slot_kv_insert_unsafe(sca_hash_slot *slot, void *value,
		int (*e_compare)(str *, void *), void (*e_description)(void *),
		void (*e_free)(void *))
{
	sca_hash_entry *new_entry;
	sca_hash_entry **cur_entry;

	assert(slot != NULL);
	assert(value != NULL);
	assert(e_free != NULL);

	new_entry = (sca_hash_entry *)shm_malloc(sizeof(sca_hash_entry));
	if(new_entry == NULL) {
		LM_ERR("Failed to shm_malloc new hash table entry for slot %p\n", slot);
		return (-1);
	}
	new_entry->value = value;
	new_entry->compare = e_compare;
	new_entry->description = e_description;
	new_entry->free_entry = e_free;
	new_entry->slot = slot;

	cur_entry = &slot->entries;
	new_entry->next = *cur_entry;
	*cur_entry = new_entry;

	return (0);
}

int sca_hash_table_slot_kv_insert(sca_hash_slot *slot, void *value,
		int (*e_compare)(str *, void *), void (*e_description)(void *),
		void (*e_free)(void *))
{
	int rc;

	lock_get(&slot->lock);

	rc = sca_hash_table_slot_kv_insert_unsafe(
			slot, value, e_compare, e_description, e_free);

	lock_release(&slot->lock);

	return (rc);
}

int sca_hash_table_index_kv_insert(sca_hash_table *ht, int slot_idx,
		void *value, int (*e_compare)(str *, void *),
		void (*e_description)(void *), void (*e_free)(void *))
{
	assert(ht != NULL);
	assert(ht->slots != NULL);
	assert(slot_idx >= 0 && slot_idx < ht->size);

	return (sca_hash_table_slot_kv_insert(
			&ht->slots[slot_idx], value, e_compare, e_description, e_free));
}

int sca_hash_table_kv_insert(sca_hash_table *ht, str *key, void *value,
		int (*e_compare)(str *, void *), void (*e_description)(void *),
		void (*e_free)(void *))
{
	int hash_idx;
	int rc;

	assert(ht != NULL && !SCA_STR_EMPTY(key) && value != NULL);

	hash_idx = sca_hash_table_index_for_key(ht, key);
	rc = sca_hash_table_index_kv_insert(
			ht, hash_idx, value, e_compare, e_description, e_free);

	return (rc);
}

void *sca_hash_table_slot_kv_find_unsafe(sca_hash_slot *slot, str *key)
{
	sca_hash_entry *e;
	void *value = NULL;

	assert(slot != NULL && !SCA_STR_EMPTY(key));

	for(e = slot->entries; e != NULL; e = e->next) {
		if(e->compare(key, e->value) == 0) {
			value = e->value;
		}
	}

	return (value);
}

void *sca_hash_table_slot_kv_find(sca_hash_slot *slot, str *key)
{
	void *value;

	lock_get(&slot->lock);
	value = sca_hash_table_slot_kv_find_unsafe(slot, key);
	lock_release(&slot->lock);

	return (value);
}

void *sca_hash_table_index_kv_find_unsafe(
		sca_hash_table *ht, int slot_idx, str *key)
{
	assert(ht != NULL && !SCA_STR_EMPTY(key));
	assert(slot_idx >= 0 && slot_idx < ht->size);

	return (sca_hash_table_slot_kv_find_unsafe(&ht->slots[slot_idx], key));
}

void *sca_hash_table_index_kv_find(sca_hash_table *ht, int slot_idx, str *key)
{
	assert(ht != NULL && !SCA_STR_EMPTY(key));
	assert(slot_idx >= 0 && slot_idx < ht->size);

	return (sca_hash_table_slot_kv_find(&ht->slots[slot_idx], key));
}

void *sca_hash_table_kv_find(sca_hash_table *ht, str *key)
{
	int slot_idx;

	slot_idx = sca_hash_table_index_for_key(ht, key);

	return (sca_hash_table_index_kv_find(ht, slot_idx, key));
}

sca_hash_entry *sca_hash_table_slot_kv_find_entry_unsafe(
		sca_hash_slot *slot, str *key)
{
	sca_hash_entry *e = NULL;

	assert(slot != NULL && !SCA_STR_EMPTY(key));

	for(e = slot->entries; e != NULL; e = e->next) {
		if(e->compare(key, e->value) == 0) {
			break;
		}
	}

	return (e);
}

sca_hash_entry *sca_hash_table_slot_kv_find_entry(sca_hash_slot *slot, str *key)
{
	sca_hash_entry *e;

	lock_get(&slot->lock);
	e = sca_hash_table_slot_kv_find_entry(slot, key);
	lock_release(&slot->lock);

	return (e);
}

void sca_hash_entry_free(sca_hash_entry *e)
{
	assert(e != NULL);

	e->free_entry(e->value);
	shm_free(e);
}

sca_hash_entry *sca_hash_table_slot_unlink_entry_unsafe(
		sca_hash_slot *slot, sca_hash_entry *e)
{
	sca_hash_entry **cur_e;

	assert(slot != NULL);
	assert(e != NULL);

	for(cur_e = &slot->entries; *cur_e != NULL; cur_e = &(*cur_e)->next) {
		if(*cur_e == e) {
			*cur_e = e->next;

			/* ensure any attempted traversal using this entry goes nowhere */
			e->next = NULL;
			e->slot = NULL;

			break;
		}
	}

	return (e);
}

int sca_hash_table_slot_kv_delete_unsafe(sca_hash_slot *slot, str *key)
{
	sca_hash_entry *e;

	e = sca_hash_table_slot_kv_find_entry_unsafe(slot, key);
	if(e == NULL) {
		return (-1);
	}

	e = sca_hash_table_slot_unlink_entry_unsafe(slot, e);
	if(e) {
		e->free_entry(e->value);
		shm_free(e);
	}

	return (0);
}

int sca_hash_table_slot_kv_delete(sca_hash_slot *slot, str *key)
{
	int rc;

	lock_get(&slot->lock);
	rc = sca_hash_table_slot_kv_delete_unsafe(slot, key);
	lock_release(&slot->lock);

	return (rc);
}

int sca_hash_table_index_kv_delete(sca_hash_table *ht, int slot_idx, str *key)
{
	return (sca_hash_table_slot_kv_delete(&ht->slots[slot_idx], key));
}

int sca_hash_table_kv_delete(sca_hash_table *ht, str *key)
{
	int slot_idx;

	slot_idx = sca_hash_table_index_for_key(ht, key);

	return (sca_hash_table_index_kv_delete(ht, slot_idx, key));
}

static void sca_hash_slot_print(sca_hash_slot *hs)
{
	sca_hash_entry *e;

	for(e = hs->entries; e != NULL; e = e->next) {
		if(e->description != NULL) {
			e->description(e->value);
		} else {
			LM_DBG("0x%p\n", e->value);
		}
	}
}

void sca_hash_table_print(sca_hash_table *ht)
{
	unsigned int i;

	for(i = 0; i < ht->size; i++) {
		LM_DBG("SLOT %d:\n", i);
		sca_hash_slot_print(&ht->slots[i]);
	}
}

void sca_hash_table_free(sca_hash_table *ht)
{
	sca_hash_entry *e, *e_tmp;
	unsigned int i;

	if(ht == NULL) {
		return;
	}

	for(i = 0; i < ht->size; i++) {
		if(ht->slots[i].entries == NULL) {
			continue;
		}

		sca_hash_table_lock_index(ht, i);

		for(e = ht->slots[i].entries; e != NULL; e = e_tmp) {
			e_tmp = e->next;

			e->free_entry(e->value);

			shm_free(e);
		}

		sca_hash_table_unlock_index(ht, i);

		lock_destroy(&ht->slots[i].lock);
		lock_dealloc(&ht->slots[i].lock);
	}

	shm_free(ht->slots);
	shm_free(ht);
}