/*****************************************************************************\
 *  xahash.c - functions used for arbitrary hash table
 *****************************************************************************
 *  Copyright (C) 2024 SchedMD LLC.
 *
 *  This file is part of Slurm, a resource management program.
 *  For details, see <https://slurm.schedmd.com/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  Slurm 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.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  Slurm 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 Slurm; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
\*****************************************************************************/

/*
 * Fixed size xahash_t is designed to have all operations with a cost of O(1).
 * This requires a good bit of design to achieve the goal.
 *
 * Fixed size hash table: xahash_table_t
 * The fixed count xahash_table_t does 1 xmalloc() call unless there are hash
 * collisions. Then it does 1 xmalloc() per each insert with a hash collision.
 *
 * This is the memory layout of each hash table:
 *
 * |----------------------------|
 * | hash table header          | xahash_table_t* and xahash_table_header_t*
 * |                            | body of xahash_table_header_t
 * |----------------------------|
 * | state blob                 | _get_state()
 * |                            | xahash_table_header_t->state_blob_bytes
 * |----------------------------|
 * | |----------------|         | _get_fentry(index), fentry_header_t*
 * | | entry header   |         |
 * | |----------------| @ index | _get_fentry_blob(fentry_header_t*)
 * | | entry blob     |         |
 * | |----------------|         | _get_fentry(index,depth) + xahash_table_header_t->bytes_per_entry_blob
 * |----------------------------|
 *
 * Each entry fails down into a linked list on a hash collision:
 *
 *                       <-------------- xmalloc() per entry --->
 * |----------------|    |----------------|    |----------------|
 * | entry header   |    | entry header   |    | entry header   |
 * |           next*| -> |           next*| -> |            NULL|
 * |----------------|    |----------------|    |----------------|
 * | entry blob     |    | entry blob     |    | entry blob     |
 * |----------------|    |----------------|    |----------------|
 *
 * Hash collisions are slow and break the O(1) design and should be avoided.
 *
 */

#include "src/common/log.h"
#include "src/common/macros.h"
#include "src/common/read_config.h"
#include "src/common/timers.h"
#include "src/common/xahash.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"

/* Macro to only include the code inside of ONLY_DEBUG if !NDEBUG */
#ifndef NDEBUG
#define ONLY_DEBUG(...) __VA_ARGS__
#else
#define ONLY_DEBUG(...)
#endif

#define HASH_TABLE_MAGIC 0x131e1aff
#define HASH_FENTRY_MAGIC(index) (((uint32_t) 0xffff0000) ^ ((uint32_t) index))

typedef enum {
	HT_FLAG_STATE_MASK = 0xff,
	HT_FLAG_INVALID = 0,
	HT_FLAG_FIXED_SIZE = SLURM_BIT(0), /* hash table is expected to be fixed and use linked list of entries for hash collisions */
	HT_FLAG_DYNAMIC_SIZE = SLURM_BIT(1), /* hash table is expected to resize as needed */
	HT_FLAG_INVALID_MAX,
} hash_flags_t;

typedef struct {
	ONLY_DEBUG(int magic); /* HASH_TABLE_MAGIC  */
	hash_flags_t flags;
	xahash_func_t hash_func;
	xahash_match_func_t match_func;
	const char *match_func_string;
	xahash_on_insert_func_t on_insert_func;
	const char *on_insert_func_string;
	xahash_on_free_func_t on_free_func;
	const char *on_free_func_string;
	size_t state_blob_bytes;
	size_t bytes_per_entry_blob;
	union {
		struct {
			size_t count;
		} fixed;
	} type;
} xahash_table_header_t;

typedef enum {
	FENTRY_FLAG_INVALID = 0,
	FENTRY_FLAG_UNSET = SLURM_BIT(0), /* entry is not populated */
	FENTRY_FLAG_SET = SLURM_BIT(1), /* entry is populated */
	FENTRY_FLAG_INVALID_MAX
} fentry_flags_t;

/* Fixed size entry struct */
typedef struct fentry_header_s fentry_header_t;
struct fentry_header_s {
	ONLY_DEBUG(int magic); /* HASH_FENTRY_MAGIC % index */
	fentry_flags_t flags;
	/* next entry in linked list */
	fentry_header_t *next;
};

static const struct {
	const char *str;
	xahash_foreach_control_t value;
} foreach_control_strings[] = {
	{ "INVALID", XAHASH_FOREACH_INVALID },
	{ "CONTINUE", XAHASH_FOREACH_CONT },
	{ "STOP", XAHASH_FOREACH_STOP },
	{ "FAIL", XAHASH_FOREACH_FAIL },
	{ "INVALID", XAHASH_FOREACH_INVALID_MAX },
};

static void *_get_state(const xahash_table_t *ht);
static fentry_header_t *_get_fentry(xahash_table_t *ht,
				    xahash_table_header_t *hth, int index);

/* Macro place holders for sanity checks */
#define _is_debug() (false)
#define DEF_DEBUG_TIMER
#define START_DEBUG_TIMER
#define END_DEBUG_TIMER
#define _check_magic(...)
#define _check_fentry_bounds(...)
#define _check_fentry_magic(...)

static bool _is_fixed(const xahash_table_t *ht,
		      const xahash_table_header_t *hth)
{
	_check_magic(ht);

	return ((hth->flags & HT_FLAG_STATE_MASK) == HT_FLAG_FIXED_SIZE);
}

static int _fixed_hash_to_index(const xahash_table_t *ht,
				const xahash_table_header_t *hth,
				const xahash_hash_t hash)
{
	xassert(_is_fixed(ht, hth));

	return hash % hth->type.fixed.count;
}

static void *_get_state(const xahash_table_t *ht)
{
	return ((void *) ht) + sizeof(xahash_table_header_t);
}

static xahash_table_header_t *_get_header(xahash_table_t *ht)
{
	xahash_table_header_t *hth = (void *) ht;

	xassert(hth->magic == HASH_TABLE_MAGIC);

	return hth;
}

static const xahash_table_header_t *_get_header_const(const xahash_table_t *ht)
{
	return _get_header((xahash_table_t *) ht);
}

/*
 * Get pointer to entry based on index
 * WARNING: magic is not checked
 * WARNING: entry may not be set
 */
static fentry_header_t *_get_fentry(xahash_table_t *ht,
				    xahash_table_header_t *hth, int index)
{
	fentry_header_t *fe;
	void *start_ht = ht;
	void *end_hth = start_ht + sizeof(*hth);
	void *end_state_blob = end_hth + hth->state_blob_bytes;
	const size_t bytes_per_fentry =
		(sizeof(fentry_header_t) + hth->bytes_per_entry_blob);

	xassert(index >= 0);
	xassert(index < hth->type.fixed.count);

	fe = end_state_blob + (bytes_per_fentry * index);

	_check_fentry_bounds(ht, hth, fe);

	return fe;
}

static bool _is_fentry_set(const xahash_table_t *ht,
			   const xahash_table_header_t *hth,
			   const fentry_header_t *fe)
{
	_check_fentry_magic(ht, hth, fe, -1, -1);

	return (fe->flags & FENTRY_FLAG_SET);
}

/* WARNING: does not call _check_fentry_magic() */
static void *_get_fentry_blob_ptr(const xahash_table_t *ht,
				  const xahash_table_header_t *hth,
				  const fentry_header_t *fe)
{
	void *ptr;

	_check_fentry_magic(ht, hth, fe, -1, -1);

	ptr = ((void *) fe) + sizeof(*fe);

	/* verify pointer is in bounds of the ht's malloc() */
	xassert(ptr);
	xassert((void *) ptr > (void *) fe);
	xassert((void *) ptr >= ((void *) fe) + sizeof(*fe));

	return ptr;
}

static void *_get_fentry_blob(const xahash_table_t *ht,
			      const xahash_table_header_t *hth,
			      const fentry_header_t *fe)
{
	void *blob;

	xassert(fe);
	_check_fentry_magic(ht, hth, fe, -1, -1);
	xassert(_is_fentry_set(ht, hth, fe));

	if (!fe || !_is_fentry_set(ht, hth, fe))
		return NULL;

	blob = _get_fentry_blob_ptr(ht, hth, fe);

	xassert(blob);

	return blob;
}

static fentry_header_t *_init_fentry(xahash_table_t *ht,
				     xahash_table_header_t *hth,
				     fentry_header_t *fe, bool is_new,
				     const int index, const int depth)
{
	if (is_new) {
		log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] initializing fentry[%d][%d]@0x%"PRIxPTR,
			 __func__, (uintptr_t) ht, index, depth,
			 (uintptr_t) fe);
	} else {
		_check_fentry_magic(ht, hth, fe, index, depth);
		log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] reinitializing fentry[%d][%d]@0x%"PRIxPTR,
			 __func__, (uintptr_t) ht, index, depth,
			 (uintptr_t) fe);
	}

	*fe = (fentry_header_t) {
		ONLY_DEBUG(.magic = HASH_FENTRY_MAGIC(index),)
		.flags = FENTRY_FLAG_UNSET,
	};

	_check_fentry_magic(ht, hth, fe, index, depth);

	ONLY_DEBUG(memset(_get_fentry_blob_ptr(ht, hth, fe), 0,
			  hth->bytes_per_entry_blob));

	_check_fentry_magic(ht, hth, fe, index, depth);

	return fe;
}

static xahash_table_t *_new_fixed_table(xahash_func_t hash_func,
					const char *hash_func_string,
					xahash_match_func_t match_func,
					const char *match_func_string,
					xahash_on_insert_func_t on_insert_func,
					const char *on_insert_func_string,
					xahash_on_free_func_t on_free_func,
					const char *on_free_func_string,
					const size_t state_bytes,
					const size_t bytes_per_entry,
					const size_t fixed_table_size)
{
	size_t bytes;
	xahash_table_t *ht;
	xahash_table_header_t *hth;

	xassert(fixed_table_size > 0);
	xassert(bytes_per_entry > 0);

	bytes = sizeof(*hth) + state_bytes +
		((sizeof(fentry_header_t) + bytes_per_entry) *
		 fixed_table_size);

	log_flag(DATA, "%s: initializing fixed xahash_table_t with fixed %zu entries and %zu bytes per entry and %zu state bytes for %zu bytes total. Callbacks: hash_func=%s()@0x%"PRIxPTR" match_func=%s()@0x%"PRIxPTR" on_insert_func=%s()@0x%"PRIxPTR" on_free_func=%s()@0x%"PRIxPTR,
		__func__, fixed_table_size, bytes_per_entry, state_bytes, bytes,
		hash_func_string, (uintptr_t) hash_func, match_func_string,
		(uintptr_t) match_func, on_insert_func_string,
		(uintptr_t) on_insert_func, on_free_func_string,
		(uintptr_t) on_free_func);

	hth = ht = xmalloc_nz(bytes);
	*hth = (xahash_table_header_t){
		ONLY_DEBUG(.magic = HASH_TABLE_MAGIC,)
		.flags = HT_FLAG_FIXED_SIZE,
		.hash_func = hash_func,
		.match_func = match_func,
		.match_func_string = match_func_string,
		.on_insert_func = on_insert_func,
		.on_insert_func_string = on_insert_func_string,
		.on_free_func = on_free_func,
		.on_free_func_string = on_free_func_string,
		.state_blob_bytes = state_bytes,
		.bytes_per_entry_blob = bytes_per_entry,
		.type.fixed = {
			.count = fixed_table_size,
		}
	};

	for (int i = 0; i < hth->type.fixed.count; i++)
		(void) _init_fentry(ht, hth, _get_fentry(ht, hth, i), true, i,
				    0);

	ONLY_DEBUG(memset(_get_state(ht), 0, state_bytes));

	return ht;
}

extern xahash_table_t *xahash_new_table_funcname(
	xahash_func_t hash_func, const char *hash_func_string,
	xahash_match_func_t match_func, const char *match_func_string,
	xahash_on_insert_func_t on_insert_func,
	const char *on_insert_func_string, xahash_on_free_func_t on_free_func,
	const char *on_free_func_string, const size_t state_bytes,
	const size_t bytes_per_entry, const size_t fixed_table_size)
{
	if (fixed_table_size > 0)
		return _new_fixed_table(hash_func, hash_func_string, match_func,
					match_func_string, on_insert_func,
					on_insert_func_string, on_free_func,
					on_free_func_string, state_bytes,
					bytes_per_entry, fixed_table_size);

	/* TODO: dynamic sizing not implmented yet */
	fatal_abort("should never execute");
}

static void _free_fentry(xahash_table_t *ht, xahash_table_header_t *hth,
			 int index, int depth, fentry_header_t *fe,
			 fentry_header_t *parent)
{
	fentry_header_t *next = fe->next;

	if (next) {
		_check_fentry_magic(ht, hth, fe->next, index, (depth + 1));
		xassert(parent != next);
		xassert(next != fe);
	}

	if (hth->on_free_func && _is_fentry_set(ht, hth, fe)) {
		log_flag_hex(DATA, _get_fentry_blob_ptr(ht, hth, fe),
			     hth->bytes_per_entry_blob,
			     "%s: [hashtable@0x%"PRIxPTR"] calling %s()@0x%"PRIxPTR" for fentry[%d][%d]@0x%"PRIxPTR,
			     __func__, (uintptr_t) ht, hth->on_free_func_string,
			     (uintptr_t) hth->on_free_func, index, depth,
			     (uintptr_t) fe);

		hth->on_free_func(_get_fentry_blob(ht, hth, fe),
				  _get_state(ht));
	}

	if (parent) {
		log_flag_hex(DATA, _get_fentry_blob_ptr(ht, hth, fe),
			     hth->bytes_per_entry_blob,
			     "%s: [hashtable@0x%"PRIxPTR"] dropping linked fentry[%d][%d]@0x%"PRIxPTR" -> fentry[%d][%d]@0x%"PRIxPTR,
			     __func__, (uintptr_t) ht, index, (depth - 1),
			     (uintptr_t) parent, index, depth, (uintptr_t) fe);

		xassert(parent->next == fe);
		xassert(depth > 0);
		_check_fentry_magic(ht, hth, parent, index, (depth - 1));

		/* unlink fe from list */
		parent->next = next;

		ONLY_DEBUG(fe->magic = ~HASH_FENTRY_MAGIC(index));
		xfree(fe);

		_check_fentry_magic(ht, hth, parent, index, (depth - 1));
		if (next)
			_check_fentry_magic(ht, hth, next, index, depth);
	} else {
		xassert(!depth);

		log_flag_hex(DATA, _get_fentry_blob_ptr(ht, hth, fe),
			     hth->bytes_per_entry_blob,
			     "%s: [hashtable@0x%"PRIxPTR"] releasing fentry[%d][%d]@0x%"PRIxPTR,
			     __func__, (uintptr_t) ht, index, depth,
			     (uintptr_t) fe);

		(void) _init_fentry(ht, hth, fe, false, index, depth);
		fe->next = next;
	}
}

static void _free_fixed_table_members(xahash_table_t *ht,
				      xahash_table_header_t *hth)
{
	for (int i = 0; i < hth->type.fixed.count; i++) {
		fentry_header_t *fe = _get_fentry(ht, hth, i);

		_check_fentry_magic(ht, hth, fe, i, 0);

		while (fe->next) {
			_check_fentry_magic(ht, hth, fe, i, 1);
			_free_fentry(ht, hth, i, 1, fe->next, fe);
		}

		_free_fentry(ht, hth, i, 0, fe, NULL);
	}
}

extern void xahash_free_table(xahash_table_t *ht)
{
	xahash_table_header_t *hth;

	_check_magic(ht);
	if (!ht)
		return;

	hth = _get_header(ht);

	log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] request free hashtable",
		 __func__, (uintptr_t) ht);

	if (_is_fixed(ht, hth))
		_free_fixed_table_members(ht, hth);

	ONLY_DEBUG(hth->magic = ~HASH_TABLE_MAGIC);
	xfree(ht);
}

extern void *xahash_get_state_ptr(xahash_table_t *ht)
{
	DEF_DEBUG_TIMER;
	xahash_table_header_t *hth = (void *) ht;
	void *ptr;

	START_DEBUG_TIMER;
	_check_magic(ht);

	ptr = _get_state(ht);

	log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] request table state=0x%"PRIxPTR"[%zu]",
		 __func__, (uintptr_t) ht, (uintptr_t) ptr,
		 hth->state_blob_bytes);

	END_DEBUG_TIMER;
	return ptr;
}

static bool _match_fixed_entry(xahash_table_t *ht, xahash_table_header_t *hth,
			       const xahash_hash_t hash, const void *key,
			       const size_t key_bytes, fentry_header_t *fe,
			       const int index, const int depth)
{
	_check_fentry_magic(ht, hth, fe, index, depth);

	if (!_is_fentry_set(ht, hth, fe)) {
		log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] skip unset fentry[%d][%d]@0x%"PRIxPTR" != 0x%"PRIxPTR"[%zu]=#0x%x",
				 __func__, (uintptr_t) ht, index, depth,
				 (uintptr_t) fe, (uintptr_t) key, key_bytes,
				 hash);
		return false;
	}

	if (hth->match_func(_get_fentry_blob(ht, hth, fe), key, key_bytes,
			    _get_state(ht))) {
		log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] %s()@0x%"PRIxPTR"=true accepted fentry[%d][%d]@0x%"PRIxPTR" == 0x%"PRIxPTR"[%zu]=#0x%x",
			 __func__, (uintptr_t) ht, hth->match_func_string,
			 (uintptr_t) hth->match_func, index, depth,
			 (uintptr_t) fe, (uintptr_t) key, key_bytes, hash);
		return true;
	} else {
		log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] %s()@0x%"PRIxPTR"=false rejected fentry[%d][%d]@0x%"PRIxPTR" != 0x%"PRIxPTR"[%zu]=#0x%x",
			 __func__, (uintptr_t) ht, hth->match_func_string,
			 (uintptr_t) hth->match_func, index, depth,
			 (uintptr_t) fe, (uintptr_t) key, key_bytes, hash);
		return false;
	}
}

static fentry_header_t *_find_fixed_entry(xahash_table_t *ht,
					  xahash_table_header_t *hth,
					  const xahash_hash_t hash,
					  const void *key,
					  const size_t key_bytes)
{
	const int index = _fixed_hash_to_index(ht, hth, hash);
	fentry_header_t *fe = _get_fentry(ht, hth, index);
	int depth = 0;

	_check_fentry_magic(ht, hth, fe, index, depth);

	do {
		if (_match_fixed_entry(ht, hth, hash, key, key_bytes, fe, index,
				       depth))
			return fe;

		depth++;
	} while ((fe = fe->next));

	log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] unable to find fentry for 0x%"PRIxPTR"[%zu]=#0x%x",
			 __func__, (uintptr_t) ht, (uintptr_t) key, key_bytes,
			 hash);

	return NULL;
}

static const fentry_header_t *_find_fixed_entry_const(
	const xahash_table_t *ht, const xahash_table_header_t *hth,
	const void *key, const size_t key_bytes)
{
	const xahash_hash_t hash =
		hth->hash_func(key, key_bytes, _get_state(ht));

	/* de-constify and then return as const */

	return _find_fixed_entry((xahash_table_t *) ht,
				 (xahash_table_header_t *) hth, hash, key,
				 key_bytes);
}

static void *_find_fixed_entry_blob(const xahash_table_t *ht,
				    const xahash_table_header_t *hth,
				    const void *key, const size_t key_bytes)
{
	const fentry_header_t *fe =
		_find_fixed_entry_const(ht, hth, key, key_bytes);

	if (!fe || !_is_fentry_set(ht, hth, fe))
		return NULL;

	return _get_fentry_blob(ht, hth, fe);
}

extern void *xahash_find_entry(const xahash_table_t *ht, const void *key,
			       const size_t key_bytes)
{
	DEF_DEBUG_TIMER;
	const xahash_table_header_t *hth;
	void *ptr = NULL;

	_check_magic(ht);

	if (!ht || !key || !key_bytes)
		return NULL;

	START_DEBUG_TIMER;
	hth = _get_header_const(ht);

	log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] request find entry for 0x%"PRIxPTR"[%zu]=#0x%x",
		 __func__, (uintptr_t) ht, (uintptr_t) key, key_bytes,
		 hth->hash_func(key, key_bytes, _get_state(ht)));

	if (_is_fixed(ht, hth))
		ptr = _find_fixed_entry_blob(ht, hth, key, key_bytes);
	else
		fatal_abort("should never execute");

	END_DEBUG_TIMER;
	return ptr;
}

static fentry_header_t *_append_fentry(xahash_table_t *ht,
				       xahash_table_header_t *hth,
				       fentry_header_t *fe,
				       const xahash_hash_t hash,
				       const int index, int *depth_ptr)
{
	xassert(_is_fentry_set(ht, hth, fe));

	/* Find unset entry if one is already in chain */
	while (fe) {
		if (!_is_fentry_set(ht, hth, fe)) {
			/* found empty entry so return it instead */
			return fe;
		}

		if (!fe->next) {
			size_t bytes = sizeof(*fe);
			bytes += hth->bytes_per_entry_blob;
			fe->next = xmalloc_nz(bytes);

			log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] new linked fentry[%d][%d]@0x%"PRIxPTR" -> fentry[%d][%d]@0x%"PRIxPTR"=#0x%x",
				 __func__, (uintptr_t) ht, index, *depth_ptr,
				 (uintptr_t) fe, index, (*depth_ptr + 1),
				 (uintptr_t) fe->next, hash);

			(*depth_ptr)++;
			return _init_fentry(ht, hth, fe->next, true, index,
					    *depth_ptr);
		}

		fe = fe->next;
		(*depth_ptr)++;
	}

	fatal_abort("should never execute");
}

static void *_insert_fixed_entry(xahash_table_t *ht, xahash_table_header_t *hth,
				 const void *key, const size_t key_bytes)
{
	int depth = -1, index = -1;
	const xahash_hash_t hash =
		hth->hash_func(key, key_bytes, _get_state(ht));
	fentry_header_t *fe = _find_fixed_entry(ht, hth, hash, key, key_bytes);

	if (fe) {
		xassert(_is_fentry_set(ht, hth, fe));

		log_flag_hex(DATA, _get_fentry_blob_ptr(ht, hth, fe),
			     hth->bytes_per_entry_blob,
			     "%s: [hashtable@0x%"PRIxPTR"] ignoring duplicate insert on existing fentry@0x%"PRIxPTR,
			     __func__, (uintptr_t) ht, (uintptr_t) fe);

		return _get_fentry_blob(ht, hth, fe);
	}

	/* not found: find and place a new entry */
	index = _fixed_hash_to_index(ht, hth, hash);
	depth = 0;
	fe = _get_fentry(ht, hth, index);
	_check_fentry_magic(ht, hth, fe, index, depth);

	if (_is_fentry_set(ht, hth, fe)) {
		/* need to add to linked list */
		fe = _append_fentry(ht, hth, fe, hash, index, &depth);
	}

	/* check for clobbering */
	xassert(fe->flags == FENTRY_FLAG_UNSET);

	fe->flags = FENTRY_FLAG_SET;

	if (hth->on_insert_func) {
		hth->on_insert_func(_get_fentry_blob(ht, hth, fe), key,
				    key_bytes, _get_state(ht));

		log_flag_hex(DATA, _get_fentry_blob_ptr(ht, hth, fe),
			     hth->bytes_per_entry_blob,
			     "%s: [hashtable@0x%"PRIxPTR"] inserted after %s()@0x%"PRIxPTR" for fentry[%d][%d]@0x%"PRIxPTR"=#0x%x",
			     __func__, (uintptr_t) ht,
			     hth->on_insert_func_string,
			     (uintptr_t) hth->on_insert_func, index, depth,
			     (uintptr_t) fe, hash);
	} else {
		log_flag(DATA,
			 "%s: [hashtable@0x%" PRIxPTR
			 "] inserted fentry[%d][%d]@0x%" PRIxPTR "=#0x%x",
			 __func__, (uintptr_t) ht, index, depth, (uintptr_t) fe,
			 hash);
	}

	return _get_fentry_blob(ht, hth, fe);
}

extern void *xahash_insert_entry(xahash_table_t *ht, const void *key,
				 const size_t key_bytes)
{
	DEF_DEBUG_TIMER;
	xahash_table_header_t *hth;
	void *ptr = NULL;

	xassert(key);
	xassert(key_bytes > 0);
	_check_magic(ht);
	if (!ht || !key || !key_bytes)
		return NULL;

	START_DEBUG_TIMER;
	hth = _get_header(ht);

	log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] request insert entry for 0x%"PRIxPTR"[%zu]=#0x%x",
		 __func__, (uintptr_t) ht, (uintptr_t) key, key_bytes,
		 hth->hash_func(key, key_bytes, _get_state(ht)));

	if (_is_fixed(ht, hth))
		ptr = _insert_fixed_entry(ht, hth, key, key_bytes);
	else
	fatal_abort("should never execute");

	END_DEBUG_TIMER;

	return ptr;
}

static bool _find_and_free_fentry(xahash_table_t *ht,
				  xahash_table_header_t *hth, const void *key,
				  const size_t key_bytes)
{
	const xahash_hash_t hash =
		hth->hash_func(key, key_bytes, _get_state(ht));
	const int index = _fixed_hash_to_index(ht, hth, hash);
	fentry_header_t *fe = _get_fentry(ht, hth, index);
	fentry_header_t *parent = NULL;
	int depth = 0;

	_check_fentry_magic(ht, hth, fe, index, -1);

	while (fe) {
		if (_is_fentry_set(ht, hth, fe)) {
			if (hth->match_func(_get_fentry_blob(ht, hth, fe), key,
					    key_bytes,  _get_state(ht))) {
				log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] matched fentry[%d][%d]@0x%"PRIxPTR" == 0x%"PRIxPTR"[%zu]=#0x%x",
					 __func__, (uintptr_t) ht, index, depth,
					 (uintptr_t) fe, (uintptr_t) key,
					 key_bytes, hash);
				break;
			} else {
				log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] match_func rejected fentry[%d][%d]@0x%"PRIxPTR" != 0x%"PRIxPTR"[%zu]=#0x%x",
					 __func__, (uintptr_t) ht, index, depth,
					 (uintptr_t) fe, (uintptr_t) key,
					 key_bytes, hash);
			}
		}

		parent = fe;
		fe = fe->next;
		depth++;
	}

	if (!fe)
		return false;

	_free_fentry(ht, hth, index, depth, fe, parent);
	return true;
}

extern bool xahash_free_entry(xahash_table_t *ht, const void *key,
			      const size_t key_bytes)
{
	DEF_DEBUG_TIMER;
	xahash_table_header_t *hth;
	bool rc = false;

	_check_magic(ht);
	xassert(key);
	xassert(key_bytes > 0);
	if (!ht || !key || !key_bytes)
		return false;

	START_DEBUG_TIMER;
	hth = _get_header(ht);

	log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] request free entry for 0x%"PRIxPTR"[%zu]=#0x%x",
		 __func__, (uintptr_t) ht, (uintptr_t) key, key_bytes,
		 hth->hash_func(key, key_bytes, _get_state(ht)));

	if (_is_fixed(ht, hth)) {
		rc = _find_and_free_fentry(ht, hth, key, key_bytes);
	} else {
		fatal_abort("should never execute");
	}

	END_DEBUG_TIMER;
	return rc;
}

static const char *_foreach_control_string(xahash_foreach_control_t control)
{
	for (int i = 0; i < ARRAY_SIZE(foreach_control_strings); i++)
		if (foreach_control_strings[i].value == control)
			return foreach_control_strings[i].str;

	fatal_abort("should never execute");
}

static xahash_foreach_control_t _foreach_fentry_entry(
	xahash_table_t *ht, xahash_table_header_t *hth,
	xahash_foreach_func_t callback, const char *callback_string, void *arg,
	fentry_header_t *fe, const int index, const int depth)
{
	xahash_foreach_control_t rc;

	rc = callback(_get_fentry_blob(ht, hth, fe), _get_state(ht), arg);

	log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] called after %s()@0x%"PRIxPTR"=%s for fentry[%d][%d]@0x%"PRIxPTR,
		 __func__, (uintptr_t) ht, callback_string,
		 (uintptr_t) callback, _foreach_control_string(rc), index,
		 depth, (uintptr_t) fe);

	return rc;
}

static int _foreach_fentry(xahash_table_t *ht, xahash_table_header_t *hth,
			   xahash_foreach_func_t callback,
			   const char *callback_string, void *arg)
{
	uint32_t count = 0;

	for (uint32_t i = 0; i < hth->type.fixed.count; i++) {
		uint32_t depth = 0;
		fentry_header_t *fe = _get_fentry(ht, hth, i);

		do {
			xahash_foreach_control_t rc;

			if (!_is_fentry_set(ht, hth, fe))
				continue;

			count++;

			rc = _foreach_fentry_entry(ht, hth, callback,
						   callback_string, arg, fe, i,
						   depth);

			xassert(rc > XAHASH_FOREACH_INVALID);
			xassert(rc < XAHASH_FOREACH_INVALID_MAX);

			switch (rc) {
			case XAHASH_FOREACH_CONT:
				/* do nothing */
				break;
			case XAHASH_FOREACH_STOP:
				return count;
			case XAHASH_FOREACH_FAIL:
				return (count * -1);
			case XAHASH_FOREACH_INVALID:
			case XAHASH_FOREACH_INVALID_MAX:
				fatal_abort("should never execute");
			}
		} while (depth++, (fe = fe->next));
	}

	return count;
}

extern int xahash_foreach_entry_funcname(xahash_table_t *ht,
					 xahash_foreach_func_t callback,
					 const char *callback_string, void *arg)
{
	DEF_DEBUG_TIMER;
	xahash_table_header_t *hth;
	int rc = 0;

	_check_magic(ht);
	if (!ht)
		return rc;

	START_DEBUG_TIMER;
	hth = _get_header(ht);

	log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] request foreach func:%s()@0x%"PRIxPTR" arg:0x%"PRIxPTR,
		 __func__, (uintptr_t) ht, callback_string,
		 (uintptr_t) callback, (uintptr_t) arg);

	if (_is_fixed(ht, hth))
		rc = _foreach_fentry(ht, hth, callback, callback_string, arg);
	else
		fatal_abort("should never execute");

	END_DEBUG_TIMER;
	return rc;
}
