/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com>
 *
 * 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 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.
 */

#include "private-lib-core.h"

typedef struct lws_map_hashtable {
	struct lws_map			*map_owner; /* so items can find map */
	lws_dll2_owner_t		ho;
} lws_map_hashtable_t;

struct lws_map {
	lws_map_info_t			info;

	/* array of info.modulo x lws_map_hashtable_t overallocated */
};

typedef struct lws_map_item {
	lws_dll2_t			list; /* owned by hashtable */

	size_t				keylen;
	size_t				valuelen;

	/* key then value is overallocated */
} lws_map_item_t;

/*
 * lwsac-aware allocator
 */

void *
lws_map_alloc_lwsac(struct lws_map *map, size_t x)
{
	return lwsac_use((struct lwsac **)map->info.opaque, x,
					(size_t)map->info.aux);
}

void
lws_map_free_lwsac(void *v)
{
}

/*
 * Default allocation / free if none given in info
 */

void *
lws_map_alloc_lws_malloc(struct lws_map *mo, size_t x)
{
	return lws_malloc(x, __func__);
}

void
lws_map_free_lws_free(void *v)
{
	lws_free(v);
}

/*
 * This just needs to approximate a flat distribution, it's not related to
 * security at all.
 */

lws_map_hash_t
lws_map_hash_from_key_default(const lws_map_key_t key, size_t kl)
{
	lws_map_hash_t h = 0x12345678;
	const uint8_t *u = (const uint8_t *)key;

	while (kl--)
		h = ((((h << 7) | (h >> 25)) + 0xa1b2c3d4) ^ (*u++)) ^ h;

	return h;
}

int
lws_map_compare_key_default(const lws_map_key_t key1, size_t kl1,
			    const lws_map_value_t key2, size_t kl2)
{
	if (kl1 != kl2)
		return 1;

	return memcmp(key1, key2, kl1);
}

lws_map_t *
lws_map_create(const lws_map_info_t *info)
{
	lws_map_t *map;
	lws_map_alloc_t a = info->_alloc;
	size_t modulo = info->modulo;
	lws_map_hashtable_t *ht;
	size_t size;

	if (!a)
		a = lws_map_alloc_lws_malloc;

	if (!modulo)
		modulo = 8;

	size = sizeof(*map) + (modulo * sizeof(lws_map_hashtable_t));
	map = lws_malloc(size, __func__);
	if (!map)
		return NULL;

	memset(map, 0, size);

	map->info = *info;

	map->info._alloc = a;
	map->info.modulo = modulo;
	if (!info->_free)
		map->info._free = lws_map_free_lws_free;
	if (!info->_hash)
		map->info._hash = lws_map_hash_from_key_default;
	if (!info->_compare)
		map->info._compare = lws_map_compare_key_default;

	ht = (lws_map_hashtable_t *)&map[1];
	while (modulo--)
		ht[modulo].map_owner = map;

	return map;
}

static int
ho_free_item(struct lws_dll2 *d, void *user)
{
	lws_map_item_t *i = lws_container_of(d, lws_map_item_t, list);

	lws_map_item_destroy(i);

	return 0;
}

void
lws_map_destroy(lws_map_t **pmap)
{
	lws_map_hashtable_t *ht;
	lws_map_t *map = *pmap;

	if (!map)
		return;

	/* empty out all the hashtables */

	ht = (lws_map_hashtable_t *)&(map[1]);
	while (map->info.modulo--) {
		lws_dll2_foreach_safe(&ht->ho, ht, ho_free_item);
		ht++;
	}

	/* free the map itself */

	lws_free_set_NULL(*pmap);
}

lws_map_item_t *
lws_map_item_create(lws_map_t *map,
		    const lws_map_key_t key, size_t keylen,
		    const lws_map_value_t value, size_t valuelen)
{
	lws_map_hashtable_t *ht;
	lws_map_item_t *item;
	lws_map_hash_t h;
	size_t hti;
	uint8_t *u;

	item = lws_map_item_lookup(map, key, keylen);
	if (item)
		lws_map_item_destroy(item);

	item = map->info._alloc(map, sizeof(*item) + keylen + valuelen);
	if (!item)
		return NULL;

	lws_dll2_clear(&item->list);
	item->keylen = keylen;
	item->valuelen = valuelen;

	u = (uint8_t *)&item[1];
	memcpy(u, key, keylen);
	u += keylen;
	if (value)
		memcpy(u, value, valuelen);

	h = map->info._hash(key, keylen);

	hti = h % map->info.modulo;
	ht = (lws_map_hashtable_t *)&map[1];

	lws_dll2_add_head(&item->list, &ht[hti].ho);

	return item;
}

void
lws_map_item_destroy(lws_map_item_t *item)
{
	lws_map_hashtable_t *ht = lws_container_of(item->list.owner,
						   lws_map_hashtable_t, ho);

	lws_dll2_remove(&item->list);
	ht->map_owner->info._free(item);
}

lws_map_item_t *
lws_map_item_lookup(lws_map_t *map, const lws_map_key_t key, size_t keylen)
{
	lws_map_hash_t h = map->info._hash(key, keylen);
	lws_map_hashtable_t *ht = (lws_map_hashtable_t *)&map[1];

	lws_start_foreach_dll(struct lws_dll2 *, p,
			      ht[h % map->info.modulo].ho.head) {
		lws_map_item_t *i = lws_container_of(p, lws_map_item_t, list);

		if (!map->info._compare(key, keylen, &i[1], i->keylen))
			return i;
	} lws_end_foreach_dll(p);

	return NULL;
}

const void *
lws_map_item_key(lws_map_item_t *_item)
{
	return ((void *)&_item[1]);
}

const void *
lws_map_item_value(lws_map_item_t *_item)
{
	return (void *)(((uint8_t *)&_item[1]) + _item->keylen);
}

size_t
lws_map_item_key_len(lws_map_item_t *_item)
{
	return _item->keylen;
}

size_t
lws_map_item_value_len(lws_map_item_t *_item)
{
	return _item->valuelen;
}