/*
 * Odyssey.
 *
 * Scalable PostgreSQL connection pooler.
 */

#include <odyssey.h>

#include <machinarium/machinarium.h>

#include <hashmap.h>
#include <od_memory.h>

od_hashmap_list_item_t *od_hashmap_list_item_create(void)
{
	od_hashmap_list_item_t *list;
	list = od_malloc(sizeof(od_hashmap_list_item_t));
	if (list == NULL) {
		return NULL;
	}

	memset(list, 0, sizeof(od_hashmap_list_item_t));
	od_list_init(&list->link);
	return list;
}

od_retcode_t od_hashmap_list_item_free(od_hashmap_list_item_t *l)
{
	od_list_unlink(&l->link);
	od_free(l->key.data);
	if (l->value.data) {
		od_free(l->value.data);
	}
	od_free(l);

	return OK_RESPONSE;
}

static inline od_retcode_t od_hash_bucket_init(od_hashmap_bucket_t *b)
{
	mm_mutex_init(&b->mu);
	od_list_init(&b->items);

	return OK_RESPONSE;
}

static inline od_retcode_t od_hash_bucket_free(od_hashmap_bucket_t *b)
{
	mm_mutex_destroy(&b->mu);
	return OK_RESPONSE;
}

od_hashmap_t *od_hashmap_create(size_t sz)
{
	od_hashmap_t *hm;

	hm = od_malloc(sizeof(od_hashmap_t));
	if (hm == NULL) {
		return NULL;
	}

	hm->size = sz;
	hm->dtor = NULL;
	hm->buckets = od_malloc(sz * sizeof(od_hashmap_bucket_t));
	if (hm->buckets == NULL) {
		od_free(hm);
		return NULL;
	}

	memset(hm->buckets, 0, sz * sizeof(od_hashmap_bucket_t));

	for (size_t i = 0; i < sz; ++i) {
		if (od_hash_bucket_init(&hm->buckets[i]) == NOT_OK_RESPONSE) {
			od_free(hm->buckets);
			od_free(hm);
			return NULL;
		}
	}

	return hm;
}

od_hashmap_t *od_hashmap_create_with_dtor(size_t sz, od_hashmap_item_cb_t dtor)
{
	od_hashmap_t *hm = od_hashmap_create(sz);
	if (hm != NULL) {
		hm->dtor = dtor;
	}

	return hm;
}

od_retcode_t od_hashmap_free(od_hashmap_t *hm)
{
	for (size_t i = 0; i < hm->size; ++i) {
		od_hashmap_bucket_t *bucket = &hm->buckets[i];
		od_list_t *j, *n;

		od_list_foreach_safe (&bucket->items, j, n) {
			od_hashmap_list_item_t *it;
			it = od_container_of(j, od_hashmap_list_item_t, link);
			if (hm->dtor) {
				hm->dtor(it);
			}
			od_hashmap_list_item_free(it);
		}

		od_hash_bucket_free(bucket);
	}

	od_free(hm->buckets);
	od_free(hm);

	return OK_RESPONSE;
}

od_retcode_t od_hashmap_empty(od_hashmap_t *hm)
{
	for (size_t i = 0; i < hm->size; ++i) {
		od_hashmap_bucket_t *bucket = &hm->buckets[i];
		mm_mutex_lock(&bucket->mu, UINT32_MAX);

		od_list_t *j, *n;

		od_list_foreach_safe (&bucket->items, j, n) {
			od_hashmap_list_item_t *it;
			it = od_container_of(j, od_hashmap_list_item_t, link);
			if (hm->dtor != NULL) {
				hm->dtor(it);
			}
			od_hashmap_list_item_free(it);
		}

		mm_mutex_unlock(&bucket->mu);
	}

	return OK_RESPONSE;
}

static inline od_hashmap_elt_t *od_bucket_search(od_hashmap_bucket_t *b,
						 void *value, size_t value_len)
{
	od_list_t *i;
	od_list_foreach (&b->items, i) {
		od_hashmap_list_item_t *item;
		item = od_container_of(i, od_hashmap_list_item_t, link);
		if (item->key.len == value_len &&
		    memcmp(item->key.data, value, value_len) == 0) {
			/* find */
			return &item->value;
		}
	}

	return NULL;
}

static inline int od_hashmap_elt_copy(od_hashmap_elt_t *dst,
				      od_hashmap_elt_t *src)
{
	dst->len = src->len;
	dst->data = od_malloc(src->len * sizeof(char));
	if (dst->data == NULL) {
		return -1;
	}

	memcpy(dst->data, src->data, src->len);
	return 0;
}

int od_hashmap_insert(od_hashmap_t *hm, od_hash_t keyhash,
		      od_hashmap_elt_t *key, od_hashmap_elt_t **value)
{
	size_t bucket_index = keyhash % hm->size;
	od_hashmap_bucket_t *bucket = &hm->buckets[bucket_index];

	mm_mutex_lock(&bucket->mu, UINT32_MAX);

	od_hashmap_elt_t *ptr = od_bucket_search(bucket, key->data, key->len);

	int ret = 1;

	if (ptr == NULL) {
		od_hashmap_list_item_t *it = od_hashmap_list_item_create();
		if (it == NULL) {
			/* oom or other error */
			mm_mutex_unlock(&bucket->mu);
			return -1;
		}

		od_hashmap_elt_copy(&it->key, key);
		od_hashmap_elt_copy(&it->value, *value);
		od_list_append(&bucket->items, &it->link);
		ret = 0;
	} else {
		/* element already exists,
		* copy *value content to ptr data
		* free previous value */
		od_free(ptr->data);
		od_hashmap_elt_copy(ptr, *value);
		*value = ptr;
	}

	mm_mutex_unlock(&bucket->mu);
	return ret;
}

od_hashmap_elt_t *od_hashmap_find(od_hashmap_t *hm, od_hash_t keyhash,
				  od_hashmap_elt_t *key)
{
	size_t bucket_index = keyhash % hm->size;
	od_hashmap_bucket_t *bucket = &hm->buckets[bucket_index];
	mm_mutex_lock(&bucket->mu, UINT32_MAX);

	od_hashmap_elt_t *ptr = od_bucket_search(bucket, key->data, key->len);

	mm_mutex_unlock(&bucket->mu);
	return ptr;
}

od_hashmap_elt_t *od_hashmap_lock_key(od_hashmap_t *hm, od_hash_t keyhash,
				      od_hashmap_elt_t *key)
{
	size_t bucket_index = keyhash % hm->size;
	od_hashmap_bucket_t *bucket = &hm->buckets[bucket_index];

	mm_mutex_lock(&bucket->mu, UINT32_MAX);

	od_hashmap_elt_t *ptr = od_bucket_search(bucket, key->data, key->len);
	if (ptr == NULL) {
		od_hashmap_list_item_t *it = od_hashmap_list_item_create();
		if (it == NULL) {
			/* oom or other error */
			return NULL;
		}

		od_hashmap_elt_copy(&it->key, key);
		od_list_append(&bucket->items, &it->link);
		return &it->value;
	}

	/* element already exists, simpty return locked key */
	return ptr;
}

int od_hashmap_unlock_key(od_hashmap_t *hm, od_hash_t keyhash,
			  od_hashmap_elt_t *key)
{
	(void)key;
	size_t bucket_index = keyhash % hm->size;
	od_hashmap_bucket_t *bucket = &hm->buckets[bucket_index];
	mm_mutex_unlock(&bucket->mu);
	return 0 /* OK */;
}
