
#include "taisei.h"

#include "hashtable.h"
#include "list.h"
#include "util/stringops.h"
#include "util/assert.h"
#include "log.h"

#include <SDL.h>

/******************************************************************************\
 *                                      ,.                                    *
 *    ```             `.,:::,.    `;'';,;'.`                                  *
 *   `,`,;'''''';.+###############''''''' ,.`                                 *
 *  ,.''.:''''+@@####################'';,' `:                                 *
 *  ,`',;'''+#@@#########################++;,                                 *
 *   :,';+++@@#############################:,:                                *
 *  .::,;++@@###############################;                                 *
 *  .,:+;+,,,:+##@##########@##############+:,                                *
 *   +,:+#@@##'::::++#######@###@##@##+'::,;'#`                               *
 *   @++:@@@@@#@@@@###@#@##@##'@@##@@##@@@##@##                               *
 *  .+'++@@@@##@@@@#@#@@#+#@@#:'@##@@@@@#@#@@##                               *
 * .,+;+'+@@@##@@@@@+@+@@;';@@.`:#@;';@##@#@#@@#,                             *
 * :#:+'+@@@#@@@@@:+;:@::':@,``.:@,+::@:@#@@#@#;                              *
 *  '::+'@@@@@@@@#`,+`++';.`````:``+++,+:@@@.;@;                              *
 *  +@'+;@@@@#:,;;,`, ''';`````````+''+:.';.: '`                              *
 *  +@@@@@@@#:+',:`.,,.````````````.,,,.,:''.                                 *
 *  '@@@@@@@':'+'+`....```````````````...:'+   Type-safe generics? In my C?   *
 *  ;@@@@@@@':'+''`````````.`````````````,'+                                  *
 *  :#@@@@@@@:'+''`````````::::''````````:;; It's more likely than you think. *
 *  .;@@@@@@@@++,'`````````'::::.````````;:`:                                 *
 *   .@@#@@@@#;'.:.`````````':'``````````@.:                                  *
 *   ,#@,@@;@##@##``````````````````````@@#,        [FREE HEADER CHECK]       *
 *   :.@ +# @##@@# :``````````````````;@:@@:                                  *
 *     # '``.@:,##   :`````````````.+@#,..@:                                  *
 *       `   '`  @      `::,,,:::` ` +; . ;'                                  *
 *                :                        +                                  *
\******************************************************************************/

/***********************\
 * User-defined macros *
\***********************/

/*
 * HT_SUFFIX
 *
 * A name tag associated with your custom hashtable type.
 * You must define this to a valid, unique identifier.
 * Whenever you see XXX appear as part of an identifier in the documentation,
 * substitute it for the value of HT_SUFFIX.
 *
 * Example:
 *
 *         #define HT_SUFFIX str2ptr
 */
#ifndef HT_SUFFIX
	#error HT_SUFFIX not defined
#endif

/*
 * HT_KEY_TYPE
 *
 * The type of hashtable keys. This may be virtually anything, except for
 * incomplete types. It is possible to use a pointer to a heap-allocated
 * data structure here that is to be used as the actual key (see below). This
 * is useful if you want to use strings, for example.
 *
 * Example:
 *
 *         #define HT_KEY_TYPE char*
 */
#ifndef HT_KEY_TYPE
	#error HT_KEY_TYPE not defined
#endif

/*
 * HT_KEY_CONST
 *
 * Optional.
 *
 * If defined, the 'const' qualifier will be prepended to the key type where
 * appropriate. Useful for pointer types.
 */
#ifdef HT_KEY_CONST
	#undef HT_KEY_CONST
	#define HT_KEY_CONST const
#else
	#define HT_KEY_CONST
#endif

/*
 * HT_KEY_TYPE
 *
 * The type of hashtable keys. This must be a complete, non-array type.
 *
 * Example:
 *
 *         #define HT_VALUE_TYPE void*
 */
#ifndef HT_VALUE_TYPE
	#error HT_VALUE_TYPE not defined
#endif

/*
 * HT_FUNC_HASH_KEY(key)
 *
 * A function-like macro used to compute an integer hash based on key. It must
 * produce consistent results for every possible key, and collisions should be
 * minimized to improve performance.
 *
 * Type of the key parameter is the same as HT_KEY_TYPE.
 * Type of the result expression should be hash_t.
 *
 * Example:
 *
 *         #define HT_FUNC_HASH_KEY(key) htutil_hashfunc_string(key)
 */
#ifndef HT_FUNC_HASH_KEY
	#error HT_FUNC_HASH_KEY not defined
#endif

/*
 * HT_FUNC_KEYS_EQUAL(key1, key2)
 *
 * Optional.
 *
 * A function-like macro used to test two keys for equality. Note that if two
 * keys are considered equal, they must also hash to the same value. However,
 * the converse doesn't apply: equality of hashes doesn't imply equality of keys.
 *
 * Type of the key1 and key2 parameters is the same as HT_KEY_TYPE.
 * The result expression must be true if the keys are equal, false otherwise.
 *
 * If no definition is provided, the standard == operator is used.
 *
 * Example:
 *
 *         #define HT_FUNC_KEYS_EQUAL(key1, key2) (!strcmp(key1, key2))
 */
#ifndef HT_FUNC_KEYS_EQUAL
	#define HT_FUNC_KEYS_EQUAL(key1, key2) ((key1) == (key2))
#endif

/*
 * HT_FUNC_COPY_KEY(dst, src)
 *
 * Optional.
 *
 * A function-like macro that specifies how copying a key should be handled.
 * This is useful with pointers to variably-sized structures, such as strings.
 *
 * Type of src is the same as HT_KEY_TYPE.
 * dst is a pointer to HT_KEY_TYPE.
 * Type of the result expression is ignored.
 *
 * If no definition is provided, the key is simply copied by value.
 *
 * Example:
 *
 *        #define HT_FUNC_COPY_KEY(dst, src) (*(dst) = strdup(src))
 */
#ifndef HT_FUNC_COPY_KEY
	#define HT_FUNC_COPY_KEY(dst, src) (*(dst) = (src))
#endif

/*
 * HT_FUNC_FREE_KEY(key)
 *
 * Optional.
 *
 * A function-like macro used to free any resources allocated for a key, if any.
 * Use this to clean up after after any allocations done by HT_FUNC_COPY_KEY.
 *
 * Example:
 *
 *        #define HT_FUNC_FREE_KEY(key) free(key)
 */
#ifndef HT_FUNC_FREE_KEY
	#define HT_FUNC_FREE_KEY(key) ((void)(key))
#endif

/*
 * HT_THREAD_SAFE
 *
 * Optional.
 *
 * If defined, most hashtable operations will be guarded by a readers-writer lock,
 * protecting them from data races when used by multiple threads. This has some
 * impact on performance and memory usage, however.
 *
 * Some additional APIs are provided in this mode, as well as unsafe versions of
 * some of the core APIs. They are documented below.
 *
 * The unsafe variants behave identically to their core counterparts, but they
 * completely bypass the locking mechanism, making them somewhat faster.
 *
 * Example:
 *
 *        #define HT_THREAD_SAFE
 */
#ifndef HT_THREAD_SAFE
	// no default needed
#endif

/*
 * HT_DECL, HT_IMPL
 *
 * You must define at least one of these macros.
 *
 * If HT_DECL is defined, this header will generate declarations of the API types
 * and functions. This should be used in headers.
 *
 * If HT_IMPL is defined, this header will generate definitions of the API functions.
 *
 *  * Example:
 *
 *        #define HT_IMPL
 *        #include "hashtable.inc.h"
 */
#if !defined(HT_DECL) && !defined(HT_IMPL)
	#error neither HT_DECL nor HT_IMPL defined
#endif

/*
 * HT_MIN_SIZE
 *
 * Optional.
 *
 * How many buckets to allocate initially. Higher values increase memory usage,
 * but may improve performance. Must be a power of 2.
 */
#ifndef HT_MIN_SIZE
	#define HT_MIN_SIZE 4
#endif

static_assert((HT_MIN_SIZE & (~HT_MIN_SIZE + 1)) == HT_MIN_SIZE, "HT_MIN_SIZE must be power of two");

/*
 * The following macros comprise the core of the templating machinery.
 * They are used to construct identifiers augmented with HT_SUFFIX.
 */

#define _HT_NAME_INNER1(suffix, name) ht_##suffix##name
#define _HT_NAME_INNER2(suffix, name) _HT_NAME_INNER1(suffix, name)
#define HT_NAME(name) _HT_NAME_INNER2(HT_SUFFIX, name)

#define _HT_PRIV_NAME_INNER1(suffix, name) _ht_##suffix##name
#define _HT_PRIV_NAME_INNER2(suffix, name) _HT_PRIV_NAME_INNER1(suffix, name)
#define HT_PRIV_NAME(name) _HT_PRIV_NAME_INNER2(HT_SUFFIX, name)

#define HT_TYPE(name) HT_NAME(_##name##_t)
#define HT_FUNC(name) HT_NAME(_##name)
#define HT_PRIV_FUNC(name) HT_PRIV_NAME(_##name)
#define HT_BASETYPE HT_NAME(_t)

#define HT_DECLARE_FUNC(return_type, name, arguments) return_type HT_FUNC(name) arguments
#define HT_DECLARE_PRIV_FUNC(return_type, name, arguments) static return_type HT_PRIV_FUNC(name) arguments

/****************\
 * Declarations *
\****************/
#ifdef HT_DECL

/*
 * ht_XXX_t
 *
 * A structure representing a hash table.
 * All its fields are considered private and should be left unmolested,
 * please use the API functiosn instead of manipulating these directly.
 */
typedef struct HT_BASETYPE HT_BASETYPE;

/*
 * ht_XXX_key_t
 *
 * An alias for the key type (HT_KEY_TYPE).
 */
typedef HT_KEY_TYPE HT_TYPE(key);

/*
 * ht_XXX_const_key_t
 *
 * An alias for the key type (HT_KEY_TYPE).
 *
 * If HT_KEY_CONST is defined, this type has the 'const' qualifier.
 */
typedef HT_KEY_CONST HT_KEY_TYPE HT_TYPE(const_key);

/*
 * ht_XXX_value_t
 *
 * An alias for the value type (HT_VALUE_TYPE).
 */
typedef HT_VALUE_TYPE HT_TYPE(value);

/*
 * ht_XXX_key_list_t
 *
 * A list of keys.
 */
typedef struct HT_TYPE(key_list) HT_TYPE(key_list);

/*
 * ht_XXX_foreach_callback_t
 *
 * Pointer to a callback function for use with ht_XXX_foreach().
 */
typedef void* (*HT_TYPE(foreach_callback))(HT_TYPE(const_key) key, HT_TYPE(value) data, void *arg);

/*
 * ht_XXX_iter_t
 *
 * An iterator structure. See ht_XXX_iter_begin().
 */
typedef struct HT_TYPE(iter) HT_TYPE(iter);

/*
 * Forward declaration of the private element struct.
 */
typedef struct HT_TYPE(element) HT_TYPE(element);

/*
 * Definition for ht_XXX_key_list_t.
 */
struct HT_TYPE(key_list) {
	LIST_INTERFACE(HT_TYPE(key_list));
	HT_TYPE(key) key;
};

/*
 * Definition for ht_XXX_t.
 * All of these fields are to be considered private.
 */
struct HT_BASETYPE {
	HT_TYPE(element) *elements;
	ht_size_t num_elements_occupied;
	ht_size_t num_elements_allocated;
	ht_size_t max_psl;
	hash_t hash_mask;

#ifdef HT_THREAD_SAFE
	struct {
		SDL_mutex *mutex;
		SDL_cond *cond;
		uint readers;
		bool writing;
	} sync;
#endif
};

/*
 * Definition for ht_XXX_iter_t.
 */
struct HT_TYPE(iter) {
	HT_BASETYPE *hashtable;
	HT_TYPE(key) key;
	HT_TYPE(value) value;
	bool has_data;

	struct {
		ht_size_t i;
		ht_size_t remaining;
	} private;
};

/*
 * void ht_XXX_create(ht_XXX_t *ht);
 *
 * Initialize a hashtable structure. Must be called before any other API function.
 */
HT_DECLARE_FUNC(void, create, (HT_BASETYPE *ht))
	attr_nonnull(1);

/*
 * void ht_XXX_destroy(ht_XXX_t *ht);
 *
 * Destroy a hashtable, freeing all resources allocated to it.
 * Other API functions, except for ht_XXX_create(), must not be called after this.
 * Does not free the memory pointed to by [ht], as it's not necessarily heap-allocated.
 */
HT_DECLARE_FUNC(void, destroy, (HT_BASETYPE *ht))
	attr_nonnull(1);

/*
 * ht_XXX_t* ht_XXX_new(void);
 *
 * Convenience function; allocates and initializes a new hashtable structure.
 * You must mem_free() it manually when you're done with it (but don't forget to
 * ht_*_destroy() it as well).
 *
 * Returns the allocated hashtable structure.
 */
INLINE attr_returns_allocated
HT_DECLARE_FUNC(HT_BASETYPE*, new, (void))  {
	auto ht = ALLOC(HT_BASETYPE);
	HT_FUNC(create)(ht);
	return ht;
}

#ifdef HT_THREAD_SAFE

/*
 * void ht_XXX_lock(ht_XXX_t *ht);
 *
 * Acquire a read lock on a hashtable. The hashtable is guarateed to stay unmodified
 * while this lock is held. You must not try to modify it from the same thread; that
 * will result in a deadlock. Other threads are able to read the hashtable while the
 * lock is held.
 */
HT_DECLARE_FUNC(void, lock, (HT_BASETYPE *ht))
	attr_nonnull(1);

/*
 * void ht_XXX_unlock(ht_XXX_t *ht);
 *
 * Release a read lock on a hashtable previously acquired via ht_XXX_unlock().
 */
HT_DECLARE_FUNC(void, unlock, (HT_BASETYPE *ht))
	attr_nonnull(1);

/*
 * void ht_XXX_write_lock(ht_XXX_t *ht);
 *
 * Acquire a write lock on a hashtable. All of the thread-safe APIs will block while this lock is
 * held. This applies to the thread holding the lock as well, which will cause a deadlock.
 *
 * This is mostly useful in conjunction with get_ptr_unsafe.
 */
HT_DECLARE_FUNC(void, write_lock, (HT_BASETYPE *ht))
	attr_nonnull(1);

/*
 * void ht_XXX_write_unlock(ht_XXX_t *ht);
 *
 * Release a write lock on a hashtable previously acquired via ht_XXX_write_unlock().
 */
HT_DECLARE_FUNC(void, write_unlock, (HT_BASETYPE *ht))
	attr_nonnull(1);

#endif // HT_THREAD_SAFE

/*
 * ht_XXX_value_t ht_XXX_get(ht_XXX_t *ht, ht_XXX_const_key_t key, ht_XXX_const_value_t fallback);
 *
 * Retrieve a value associated with [key]. If there is no association, [fallback] will
 * be returned instead.
 */
HT_DECLARE_FUNC(HT_TYPE(value), get_prehashed, (HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash, HT_TYPE(value) fallback))
	attr_nonnull(1);

attr_nonnull(1)
INLINE HT_DECLARE_FUNC(HT_TYPE(value), get, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) fallback)) {
	return HT_FUNC(get_prehashed)(ht, key, HT_FUNC_HASH_KEY(key), fallback);
}

#ifdef HT_THREAD_SAFE
/*
 * ht_XXX_value_t ht_XXX_get_unsafe(ht_XXX_t *ht, ht_XXX_const_key_t key, ht_XXX_const_value_t fallback);
 *
 * A non-thread-safe version of ht_XXX_get().
 */
HT_DECLARE_FUNC(HT_TYPE(value), get_unsafe_prehashed, (HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash, HT_TYPE(value) fallback))
	attr_nonnull(1);

attr_nonnull(1)
INLINE HT_DECLARE_FUNC(HT_TYPE(value), get_unsafe, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) fallback)) {
	return HT_FUNC(get_unsafe_prehashed)(ht, key, HT_FUNC_HASH_KEY(key), fallback);
}

#endif // HT_THREAD_SAFE

/*
 * bool ht_XXX_get_ptr_unsafe(ht_XXX_t *ht, ht_XXX_const_key_t key, ht_XXX_value_t **outp, bool create);
 *
 * Retrieve a writeable pointer to a value associated with [key].
 *
 * If the association exists, this function returns true and stores the pointer into [outp].
 *
 * If there is no existing association, this function returns false.
 * If [create] is false, [outp] is not modified.
 * If [create] is true, a new association is created and the pointer is stored into [outp]. The
 * contents of the value are undefined in this case and must be initialized by the caller.
 *
 * This function is not thread-safe. If the hashtable is accessed by more than one thread, you must
 * protect this call as well as any modifications made to the pointed value with a write lock (see
 * ht_XXX_write_lock).
 *
 * The pointer returned by this function may be invalidated by any API that can modify the
 * hashtable. You should not keep any references to it after releasing the write lock.
 */
HT_DECLARE_FUNC(bool, get_ptr_unsafe_prehashed, (
	HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash, HT_TYPE(value) **outp, bool create))
	attr_nonnull(1);

HT_DECLARE_FUNC(bool, get_ptr_unsafe, (
	HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) **outp, bool create))
	attr_nonnull(1);

/*
 * bool ht_XXX_lookup(ht_XXX_t *ht, ht_XXX_const_key_t key, ht_XXX_value_t *out_value);
 *
 * Check whether a key exists in the hashtable. If it does and [out_value] is not NULL,
 * then the value associated with it will be also copied into *out_value.
 *
 * Returns true if an entry is found, false otherwise.
 */
HT_DECLARE_FUNC(bool, lookup_prehashed, (HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash, HT_TYPE(value) *out_value))
	attr_nonnull(1) attr_nodiscard;

attr_nonnull(1) attr_nodiscard
INLINE HT_DECLARE_FUNC(bool, lookup, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) *out_value)) {
	return HT_FUNC(lookup_prehashed)(ht, key, HT_FUNC_HASH_KEY(key), out_value);
}

#ifdef HT_THREAD_SAFE
/*
 * bool ht_XXX_lookup_unsafe(ht_XXX_t *ht, ht_XXX_const_key_t key, ht_XXX_value_t *out_value);
 *
 * A non-thread-safe version of ht_XXX_lookup().
 */
HT_DECLARE_FUNC(bool, lookup_unsafe_prehashed, (HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash, HT_TYPE(value) *out_value))
	attr_nonnull(1) attr_nodiscard;

attr_nonnull(1) attr_nodiscard
INLINE HT_DECLARE_FUNC(bool, lookup_unsafe, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) *out_value)) {
	return HT_FUNC(lookup_unsafe_prehashed)(ht, key, HT_FUNC_HASH_KEY(key), out_value);
}

#endif // HT_THREAD_SAFE

/*
 * void ht_XXX_set(ht_XXX_t *ht, ht_XXX_const_key_t key, ht_XXX_const_value_t value);
 *
 * Store a key-value pair in the hashtable.
 * If this key already has a value associated with it, it will be overwritten.
 *
 * Returns true if a new key was inserted, false if a previous value was overwritten.
 */
HT_DECLARE_FUNC(bool, set, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) value))
	attr_nonnull(1);

/*
 * bool ht_XXX_try_set(ht_XXX_t ht, ht_XXX_const_key_t key, ht_XXX_const_value_t value, ht_XXX_value_t (*value_transform)(ht_XXX_value_t), ht_XXX_value_t *out_value);
 *
 * See if [key] exists in the hashtable, and then:
 *
 * 	if key exists, then:
 * 		if out_value is not NULL, then:
 * 			store value associated with key in *out_value;
 *
 * 		return false;
 * 	else:
 * 		if value_transform is not NULL, then:
 * 			newValue = value_transform(value);
 * 		else
 * 			newValue = value;
 *
 * 		if out_value is not NULL, then:
 * 			store newValue in *out_value;
 *
 * 		associate key with newValue;
 *
 * 		return true;
 *
 * With HT_THREAD_SAFE defined, this is an atomic operation: the algorithm holds
 * a write lock for its whole duration.
 */
HT_DECLARE_FUNC(bool, try_set_prehashed, (HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash, HT_TYPE(value) value, HT_TYPE(value) (*value_transform)(HT_TYPE(value)), HT_TYPE(value) *out_value))
	attr_nonnull(1) attr_nodiscard;

INLINE HT_DECLARE_FUNC(bool, try_set, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) value, HT_TYPE(value) (*value_transform)(HT_TYPE(value)), HT_TYPE(value) *out_value)) {
	return HT_FUNC(try_set_prehashed)(ht, key, HT_FUNC_HASH_KEY(key), value, value_transform, out_value);
}

/*
 * bool ht_XXX_unset(ht_XXX_t *ht, ht_XXX_const_key_t key);
 *
 * If there's a value associated with key, remove that association and return true.
 * Otherwise, return false.
 */
HT_DECLARE_FUNC(bool, unset, (HT_BASETYPE *ht, HT_TYPE(const_key) key))
	attr_nonnull(1);

#ifdef HT_THREAD_SAFE
/*
 * bool ht_XXX_unset_unsafe(ht_XXX_t *ht, ht_XXX_const_key_t key);
 *
 * A non-thread-safe version of ht_XXX_unset().
 */
HT_DECLARE_FUNC(bool, unset_unsafe, (HT_BASETYPE *ht, HT_TYPE(const_key) key))
	attr_nonnull(1);
#endif // HT_THREAD_SAFE

/*
 * void ht_XXX_unset_list(ht_XXX_t *ht, const ht_XXX_key_list_t *keylist);
 *
 * Functionally equality to calling ht_XXX_unset() for every key in keylist, but
 * more efficient.
 */
HT_DECLARE_FUNC(void, unset_list, (HT_BASETYPE *ht, const HT_TYPE(key_list) *key_list))
	attr_nonnull(1);

/*
 * void ht_XXX_unset_all(ht_XXX_t *ht);
 *
 * Empty the hashtable completely.
 * Functionally equivalent to calling ht_XXX_unset() for every key in the table,
 * but more efficient.
 */
HT_DECLARE_FUNC(void, unset_all, (HT_BASETYPE *ht))
	attr_nonnull(1);

/*
 * void* ht_XXX_foreach(ht_XXX_t *ht, ht_XXX_callback_t callback, void *arg);
 *
 * Call callback(key, value, arg) for each key-value pair in the hashtable.
 *
 * If the callback returns anything other than a NULL pointer, the loop is broken
 * early, and this function returns whatever the callback returned.
 *
 * Otherwise, this function returns NULL once every pair has been processed.
 *
 * WARNING: Do not try to modify the hashtable from inside the callback.
 */
HT_DECLARE_FUNC(void*, foreach, (HT_BASETYPE *ht, HT_TYPE(foreach_callback) callback, void *arg))
	attr_nonnull(1, 2);

/*
 * void ht_XXX_iter_begin(ht_XXX_t *ht, ht_XXX_iter_t *iter);
 * void ht_XXX_iter_next(ht_XXX_iter_t *iter);
 * void ht_XXX_iter_end(ht_XXX_iter_t *iter);
 *
 * These functions are used to iterate over the key-value pairs stored in the
 * hashtable without having to provide a callback function.
 *
 * Declare a ht_XXX_iter_t structure and use ht_XXX_iter_begin() to initialize it.
 * Then, if iter->has_data is true, iter->key and iter->value are set to the key
 * and value of the first pair in the hashtable, respectively.
 *
 * While iter->has_data is true, you can call ht_XXX_iter_next() to advance the
 * iterator to the next pair. If there are no more pairs, ht_XXX_iter_next() sets
 * iter->has_data to false. Otherwise, iter->key and iter->data are updated.
 * Calling ht_XXX_iter_next() has no effect if iter->has_data is already false.
 *
 * You must call ht_XXX_iter_end() when you are done iterating.
 *
 * Example:
 *
 * 		ht_XXX_iter_t iter;
 * 		ht_XXX_iter_begin(&my_hashtable, &iter);
 *
 * 		while(iter.has_data) {
 * 			do_something_with(iter.key, iter.value);
 *
 * 			if(some_condition) {
 * 				break;
 * 			}
 *
 * 			ht_XXX_iter_next(&iter);
 * 		}
 *
 * 		ht_XXX_iter_end(&iter);
 *
 * WARNING: Do not try to modify the hashtable while iterating over its contents.
 *
 * If HT_THREAD_SAFE is defined, ht_XXX_iter_begin() acquires a read lock, and
 * ht_XXX_iter_end() releases it. Thus, the hashtable is guarateed to stay
 * unmodified while any threads have an iterator active.
 */
HT_DECLARE_FUNC(void, iter_begin, (HT_BASETYPE *ht, HT_TYPE(iter) *iter))
	attr_nonnull(1, 2);

HT_DECLARE_FUNC(void, iter_next, (HT_TYPE(iter) *iter))
	attr_nonnull(1);

HT_DECLARE_FUNC(void, iter_end, (HT_TYPE(iter) *iter))
	attr_nonnull(1);

/*
 * hash_t ht_XXX_hash(ht_XXX_const_key_t key);
 *
 * Compute the key's hash, suitable to pass to _prehashed functions.
 */
INLINE HT_DECLARE_FUNC(hash_t, hash, (HT_TYPE(const_key) key)) {
	return HT_FUNC_HASH_KEY(key);
}

#endif // HT_DECL

/*******************\
 * Implementations *
\*******************/

// #define HT_IMPL
#ifdef HT_IMPL

struct HT_TYPE(element) {
	HT_TYPE(value) value;
	HT_TYPE(key) key;
	hash_t hash;
};

inline
HT_DECLARE_PRIV_FUNC(ht_size_t, get_psl, (ht_size_t zero_idx, ht_size_t actual_idx, ht_size_t num_allocated)) {
	// returns the probe sequence length from zero_idx to actual_idx

	if(actual_idx < zero_idx) {
		return num_allocated - zero_idx + actual_idx;
	}

	return actual_idx - zero_idx;
}

HT_DECLARE_PRIV_FUNC(ht_size_t, get_element_psl, (HT_BASETYPE *ht, HT_TYPE(element) *e)) {
	return HT_PRIV_FUNC(get_psl)(e->hash & ht->hash_mask, e - ht->elements, ht->num_elements_allocated);
}

HT_DECLARE_PRIV_FUNC(void, dump, (HT_BASETYPE *ht)) {
#if 0
	log_debug(" -- begin dump of hashtable %p --", (void*)ht);
	for(ht_size_t i = 0; i < ht->num_elements_allocated; ++i) {
		HT_TYPE(element) *e = ht->elements + i;

		if(e->hash & HT_HASH_LIVE_BIT) {
			ht_size_t psl = HT_PRIV_FUNC(get_element_psl)(ht, e);
			log_debug("%.4i. 0x%08x    [%"HT_KEY_FMT"]  ==>  [%"HT_VALUE_FMT"]    PSL: %u", i, e->hash, HT_KEY_PRINTABLE(e->key), HT_VALUE_PRINTABLE(e->value), psl);
			assert(psl <= ht->max_psl);
		} else {
			log_debug("%.4i. 0x%08x    -- empty --", i, e->hash);
		}

	}
	log_debug("Max PSL: %u", ht->max_psl);
	log_debug(" -- end dump of hashtable %p --", (void*)ht);
#endif
}

HT_DECLARE_PRIV_FUNC(void, begin_write, (HT_BASETYPE *ht)) {
	#ifdef HT_THREAD_SAFE
	SDL_LockMutex(ht->sync.mutex);

	while(ht->sync.writing || ht->sync.readers) {
		SDL_CondWait(ht->sync.cond, ht->sync.mutex);
	}

	ht->sync.writing = true;
	SDL_UnlockMutex(ht->sync.mutex);
	#endif
}

HT_DECLARE_PRIV_FUNC(void, end_write, (HT_BASETYPE *ht)) {
	#ifdef HT_THREAD_SAFE
	SDL_LockMutex(ht->sync.mutex);
	ht->sync.writing = false;
	SDL_CondBroadcast(ht->sync.cond);
	SDL_UnlockMutex(ht->sync.mutex);
	#endif
}

HT_DECLARE_PRIV_FUNC(void, begin_read, (HT_BASETYPE *ht)) {
	#ifdef HT_THREAD_SAFE
	SDL_LockMutex(ht->sync.mutex);

	while(ht->sync.writing) {
		SDL_CondWait(ht->sync.cond, ht->sync.mutex);
	}

	++ht->sync.readers;
	SDL_UnlockMutex(ht->sync.mutex);
	#endif
}

HT_DECLARE_PRIV_FUNC(void, end_read, (HT_BASETYPE *ht)) {
	#ifdef HT_THREAD_SAFE
	SDL_LockMutex(ht->sync.mutex);

	if(!--ht->sync.readers) {
		SDL_CondBroadcast(ht->sync.cond);
	}

	SDL_UnlockMutex(ht->sync.mutex);
	#endif
}

#ifdef HT_THREAD_SAFE
HT_DECLARE_FUNC(void, lock, (HT_BASETYPE *ht)) {
	HT_PRIV_FUNC(begin_read)(ht);
}

HT_DECLARE_FUNC(void, unlock, (HT_BASETYPE *ht)) {
	HT_PRIV_FUNC(end_read)(ht);
}

HT_DECLARE_FUNC(void, write_lock, (HT_BASETYPE *ht)) {
	HT_PRIV_FUNC(begin_write)(ht);
}

HT_DECLARE_FUNC(void, write_unlock, (HT_BASETYPE *ht)) {
	HT_PRIV_FUNC(end_write)(ht);
}
#endif // HT_THREAD_SAFE

HT_DECLARE_FUNC(void, create, (HT_BASETYPE *ht)) {
	ht_size_t size = HT_MIN_SIZE;

	ht->elements = ALLOC_ARRAY(size, typeof(*ht->elements));
	ht->num_elements_allocated = size;
	ht->num_elements_occupied = 0;
	ht->hash_mask = size - 1;

	#ifdef HT_THREAD_SAFE
	ht->sync.writing = false;
	ht->sync.readers = 0;
	ht->sync.mutex = SDL_CreateMutex();
	ht->sync.cond = SDL_CreateCond();
	#endif
}

HT_DECLARE_FUNC(void, destroy, (HT_BASETYPE *ht)) {
	HT_FUNC(unset_all)(ht);
	#ifdef HT_THREAD_SAFE
	SDL_DestroyCond(ht->sync.cond);
	SDL_DestroyMutex(ht->sync.mutex);
	#endif
	mem_free(ht->elements);
}

HT_DECLARE_PRIV_FUNC(HT_TYPE(element)*, find_element, (HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash)) {
	hash_t hash_mask = ht->hash_mask;
	ht_size_t i = hash & hash_mask;
	ht_size_t attr_unused zero_idx = i;
	ht_size_t probe_len = 0;
	ht_size_t max_probe_len = ht->max_psl;
	hash |= HT_HASH_LIVE_BIT;

	HT_TYPE(element) *elements = ht->elements;

	// log_debug("%p %08x [%"HT_KEY_FMT"]", (void*)ht, hash, HT_KEY_PRINTABLE(key));

	for(;;) {
		HT_TYPE(element) *e = elements + i;
		hash_t e_hash = e->hash;
		// log_debug("i=%u :: %08x", i, e_hash);

		if(e_hash == hash && HT_FUNC_KEYS_EQUAL(key, e->key)) {
			// log_debug("found at %u (probe_len = %u)", i, probe_len);
			assert(probe_len == HT_PRIV_FUNC(get_element_psl)(ht, e));
			return e;
		}

		if(!(e_hash & HT_HASH_LIVE_BIT)) {
			// log_debug("not found (probe_len = %u)", probe_len);
			return NULL;
		}

		ht_size_t e_probe_len = HT_PRIV_FUNC(get_element_psl)(ht, e);
		assert(probe_len == HT_PRIV_FUNC(get_psl)(zero_idx, i, ht->num_elements_allocated));

		if(probe_len > e_probe_len) {
			// log_debug("[%"HT_KEY_FMT"] probe len at %u lower than current (%u < %u), bailing", HT_KEY_PRINTABLE(key), i, e_probe_len, probe_len);
			return NULL;
		}

		if(++probe_len > max_probe_len) {
			// log_debug("max PSL reached, bailing (%u)", max_probe_len);
			return NULL;
		}

		i = (i + 1) & hash_mask;
	}
}

HT_DECLARE_FUNC(HT_TYPE(value), get_prehashed, (HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash, HT_TYPE(value) fallback)) {
	assert(hash == HT_FUNC_HASH_KEY(key));
	HT_TYPE(value) value;

	HT_PRIV_FUNC(begin_read)(ht);
	HT_TYPE(element) *e = HT_PRIV_FUNC(find_element)(ht, key, hash);
	value = e ? e->value : fallback;
	HT_PRIV_FUNC(end_read)(ht);

	return value;
}

#ifdef HT_THREAD_SAFE
HT_DECLARE_FUNC(HT_TYPE(value), get_unsafe_prehashed, (HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash, HT_TYPE(value) fallback)) {
	assert(hash == HT_FUNC_HASH_KEY(key));
	HT_TYPE(element) *e = HT_PRIV_FUNC(find_element)(ht, key, hash);
	return e ? e->value : fallback;
}
#endif // HT_THREAD_SAFE

HT_DECLARE_FUNC(bool, lookup_prehashed, (HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash, HT_TYPE(value) *out_value)) {
	assert(hash == HT_FUNC_HASH_KEY(key));
	bool found = false;

	HT_PRIV_FUNC(begin_read)(ht);
	HT_TYPE(element) *e = HT_PRIV_FUNC(find_element)(ht, key, hash);

	if(e != NULL) {
		if(out_value != NULL) {
			*out_value = e->value;
		}

		found = true;
	}

	HT_PRIV_FUNC(end_read)(ht);

	return found;
}

#ifdef HT_THREAD_SAFE
HT_DECLARE_FUNC(bool, lookup_unsafe_prehashed, (HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash, HT_TYPE(value) *out_value)) {
	assert(hash == HT_FUNC_HASH_KEY(key));
	HT_TYPE(element) *e = HT_PRIV_FUNC(find_element)(ht, key, hash);

	if(e != NULL) {
		if(out_value != NULL) {
			*out_value = e->value;
		}

		return true;
	}

	return false;
}
#endif // HT_THREAD_SAFE

HT_DECLARE_PRIV_FUNC(void, unset_all, (HT_BASETYPE *ht)) {
	for(ht_size_t i = 0; i < ht->num_elements_allocated; ++i) {
		HT_TYPE(element) *e = ht->elements + i;
		if(e->hash & HT_HASH_LIVE_BIT) {
			HT_FUNC_FREE_KEY(e->key);
			e->hash = 0;

			if(--ht->num_elements_occupied == 0) {
				break;
			}
		}
	}
}

HT_DECLARE_FUNC(void, unset_all, (HT_BASETYPE *ht)) {
	HT_PRIV_FUNC(begin_write)(ht);
	HT_PRIV_FUNC(unset_all)(ht);
	HT_PRIV_FUNC(end_write)(ht);
}

HT_DECLARE_PRIV_FUNC(void, unset_with_backshift, (HT_BASETYPE *ht, HT_TYPE(element) *e)) {
	HT_TYPE(element) *elements = ht->elements;
	hash_t hash_mask = ht->hash_mask;

	HT_FUNC_FREE_KEY(e->key);
	--ht->num_elements_occupied;

	ht_size_t idx = e - elements;
	for(;;) {
		idx = (idx + 1) & hash_mask;
		HT_TYPE(element) *next_e = elements + idx;

		if(HT_PRIV_FUNC(get_element_psl)(ht, next_e) < 1) {
			e->hash = 0;
			return;
		}

		*e = *next_e;
		e = next_e;
	}
}

HT_DECLARE_FUNC(bool, unset, (HT_BASETYPE *ht, HT_TYPE(const_key) key)) {
	hash_t hash = HT_FUNC_HASH_KEY(key);
	bool success = false;

	HT_PRIV_FUNC(begin_write)(ht);
	HT_TYPE(element) *e = HT_PRIV_FUNC(find_element)(ht, key, hash);
	if(e != NULL) {
		HT_PRIV_FUNC(unset_with_backshift)(ht, e);
		success = true;
	}
	HT_PRIV_FUNC(end_write)(ht);

	return success;
}

#ifdef HT_THREAD_SAFE

HT_DECLARE_FUNC(bool, unset_unsafe, (HT_BASETYPE *ht, HT_TYPE(const_key) key)) {
	hash_t hash = HT_FUNC_HASH_KEY(key);
	bool success = false;

	HT_TYPE(element) *e = HT_PRIV_FUNC(find_element)(ht, key, hash);
	if(e != NULL) {
		HT_PRIV_FUNC(unset_with_backshift)(ht, e);
		success = true;
	}

	return success;
}

#endif

HT_DECLARE_FUNC(void, unset_list, (HT_BASETYPE *ht, const HT_TYPE(key_list) *key_list)) {
	HT_PRIV_FUNC(begin_write)(ht);

	for(const HT_TYPE(key_list) *i = key_list; i; i = i->next) {
		hash_t hash = HT_FUNC_HASH_KEY(i->key);
		HT_TYPE(element) *e = HT_PRIV_FUNC(find_element)(ht, i->key, hash);

		if(e != NULL) {
			HT_PRIV_FUNC(unset_with_backshift)(ht, e);
		}
	}

	HT_PRIV_FUNC(end_write)(ht);
}

HT_DECLARE_PRIV_FUNC(HT_TYPE(element)*, insert, (
	HT_TYPE(element) *insertion_elem,
	HT_TYPE(element) *elements,
	hash_t hash_mask,
	ht_size_t *p_max_psl
)) {
	HT_TYPE(element) *e, *target = NULL, temp_elem;
	ht_size_t idx = insertion_elem->hash & hash_mask;

	for(;;) {
		e = elements + idx;

		if(!(e->hash & HT_HASH_LIVE_BIT)) {
			*e = *insertion_elem;
			if(target == NULL) {
				target = e;
			}

			ht_size_t psl = HT_PRIV_FUNC(get_psl)(e->hash & hash_mask, idx, hash_mask + 1);
			if(*p_max_psl < psl) {
				*p_max_psl = psl;
			}

			break;
		}

		ht_size_t e_probe_len = HT_PRIV_FUNC(get_psl)(e->hash & hash_mask, idx, hash_mask + 1);
		ht_size_t i_probe_len = HT_PRIV_FUNC(get_psl)(insertion_elem->hash & hash_mask, idx, hash_mask + 1);

		if(e_probe_len < i_probe_len) {
			// log_debug("SWAP %u (%u < %u)", idx, e_probe_len, i_probe_len);
			temp_elem = *e;
			*e = *insertion_elem;
			if(target == NULL) {
				target = e;
			}
			*insertion_elem = temp_elem;

			ht_size_t psl = HT_PRIV_FUNC(get_psl)(e->hash & hash_mask, idx, hash_mask + 1);
			if(*p_max_psl < psl) {
				*p_max_psl = psl;
			}
		}

		idx = (idx + 1) & hash_mask;
	}

	return target;
}

HT_DECLARE_PRIV_FUNC(bool, set, (
	HT_BASETYPE *ht,
	hash_t hash,
	HT_TYPE(const_key) key,
	HT_TYPE(value) value,
	HT_TYPE(value) (*transform_value)(HT_TYPE(value)),
	bool allow_overwrite,
	HT_TYPE(value) *out_value
)) {
	HT_TYPE(element) *e = HT_PRIV_FUNC(find_element)(ht, key, hash);
	if(e) {
		if(!allow_overwrite) {
			if(out_value != NULL) {
				*out_value = e->value;
			}

			return false;
		}

		if(transform_value != NULL) {
			value = transform_value(value);
		}

		if(out_value != NULL) {
			*out_value = value;
		}

		e->value = value;

		// log_debug("[%"HT_KEY_FMT"] ==> [%"HT_VALUE_FMT"] (replace)", HT_KEY_PRINTABLE(key), HT_VALUE_PRINTABLE(value));
		return true;
	}

	// log_debug("[%"HT_KEY_FMT"] ==> [%"HT_VALUE_FMT"]", HT_KEY_PRINTABLE(key), HT_VALUE_PRINTABLE(value));

	// log_debug(" *** BEFORE ***");
	HT_PRIV_FUNC(dump)(ht);

	if(transform_value != NULL) {
		value = transform_value(value);
	}

	if(out_value != NULL) {
		*out_value = value;
	}

	HT_TYPE(element) insertion_elem;
	insertion_elem.value = value;
	HT_FUNC_COPY_KEY(&insertion_elem.key, key);
	insertion_elem.hash = hash | HT_HASH_LIVE_BIT;
	e = HT_PRIV_FUNC(insert)(&insertion_elem, ht->elements, ht->hash_mask, &ht->max_psl);
	assume(e != NULL);

	++ht->num_elements_occupied;

	// log_debug(" *** AFTER ***");
	HT_PRIV_FUNC(dump)(ht);
	// log_debug(" *** END SET ***");

	assert(HT_PRIV_FUNC(find_element)(ht, key, hash) == e);
	assert(!memcmp(&e->value, &value, sizeof(value)));
	return true;
}

HT_DECLARE_PRIV_FUNC(void, check_elem_count, (HT_BASETYPE *ht)) {
	#ifdef DEBUG
	attr_unused ht_size_t num_elements = 0;
	for(ht_size_t i = 0; i < ht->num_elements_allocated; ++i) {
		if(ht->elements[i].hash & HT_HASH_LIVE_BIT) {
			++num_elements;
		}
	}
	assert(num_elements == ht->num_elements_occupied);
	#endif // DEBUG
}

HT_DECLARE_PRIV_FUNC(void, resize, (HT_BASETYPE *ht, size_t new_size)) {
	assert(new_size != ht->num_elements_allocated);

	HT_TYPE(element) *old_elements = ht->elements;
	ht_size_t old_size = ht->num_elements_allocated;
	HT_PRIV_FUNC(check_elem_count)(ht);

	auto new_elements = ALLOC_ARRAY(new_size, typeof(*ht->elements));
	ht->max_psl = 0;

	for(ht_size_t i = 0; i < old_size; ++i) {
		HT_TYPE(element) *e = old_elements + i;
		if(e->hash & HT_HASH_LIVE_BIT) {
			HT_PRIV_FUNC(insert)(e, new_elements, new_size - 1, &ht->max_psl);
		}
	}

	ht->elements = new_elements;
	ht->num_elements_allocated = new_size;
	ht->hash_mask = new_size - 1;

	mem_free(old_elements);

	/*
	log_debug(
		"Resized hashtable at %p: %"PRIuMAX" -> %"PRIuMAX"",
		(void*)ht, (uintmax_t)old_size, (uintmax_t)new_size
	);
	*/

	HT_PRIV_FUNC(dump)(ht);
	HT_PRIV_FUNC(check_elem_count)(ht);
}

HT_DECLARE_PRIV_FUNC(bool, maybe_resize, (HT_BASETYPE *ht)) {
	if(
		ht->num_elements_occupied == ht->num_elements_allocated || ht->max_psl > 5
	) {
		HT_PRIV_FUNC(resize)(ht, ht->num_elements_allocated * 2);
		return true;
	}

	return false;
}

HT_DECLARE_FUNC(bool, set, (HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) value)) {
	hash_t hash = HT_FUNC_HASH_KEY(key);

	HT_PRIV_FUNC(begin_write)(ht);
	bool result = HT_PRIV_FUNC(set)(ht, hash, key, value, NULL, true, NULL);
	HT_PRIV_FUNC(maybe_resize)(ht);
	HT_PRIV_FUNC(end_write)(ht);

	return result;
}

HT_DECLARE_FUNC(bool, try_set_prehashed, (HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash, HT_TYPE(value) value, HT_TYPE(value) (*value_transform)(HT_TYPE(value)), HT_TYPE(value) *out_value)) {
	assert(hash == HT_FUNC_HASH_KEY(key));

	HT_PRIV_FUNC(begin_write)(ht);
	bool result = HT_PRIV_FUNC(set)(ht, hash, key, value, value_transform, false, out_value);
	HT_PRIV_FUNC(maybe_resize)(ht);
	HT_PRIV_FUNC(end_write)(ht);
	return result;
}

HT_DECLARE_PRIV_FUNC(bool, get_ptr_unsafe_prehashed_noassert, (
	HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash, HT_TYPE(value) **outp, bool create)
) {
	HT_TYPE(element) *e = HT_PRIV_FUNC(find_element)(ht, key, hash);

	if(e) {
		*outp = &e->value;
		return true;
	}

	if(!create) {
		return false;
	}

	HT_TYPE(element) insertion_elem;
	HT_FUNC_COPY_KEY(&insertion_elem.key, key);
	insertion_elem.hash = hash | HT_HASH_LIVE_BIT;
	e = NOT_NULL(HT_PRIV_FUNC(insert)(&insertion_elem, ht->elements, ht->hash_mask, &ht->max_psl));
	++ht->num_elements_occupied;

	if(HT_PRIV_FUNC(maybe_resize)(ht)) {
		// element pointer invalidated
		e = NOT_NULL(HT_PRIV_FUNC(find_element)(ht, key, hash));
	}

	*outp = &e->value;
	return false;
}

HT_DECLARE_FUNC(bool, get_ptr_unsafe_prehashed, (
	HT_BASETYPE *ht, HT_TYPE(const_key) key, hash_t hash, HT_TYPE(value) **outp, bool create)
) {
	assert(hash == HT_FUNC_HASH_KEY(key));
	return HT_PRIV_FUNC(get_ptr_unsafe_prehashed_noassert)(ht, key, hash, outp, create);
}

HT_DECLARE_FUNC(bool, get_ptr_unsafe, (
	HT_BASETYPE *ht, HT_TYPE(const_key) key, HT_TYPE(value) **outp, bool create)
) {
	hash_t hash = HT_FUNC_HASH_KEY(key);
	return HT_PRIV_FUNC(get_ptr_unsafe_prehashed_noassert)(ht, key, hash, outp, create);
}

HT_DECLARE_FUNC(void*, foreach, (HT_BASETYPE *ht, HT_TYPE(foreach_callback) callback, void *arg)) {
	void *ret = NULL;

	HT_PRIV_FUNC(begin_read)(ht);

	for(ht_size_t i = 0, remaining = ht->num_elements_occupied; remaining; ++i) {
		HT_TYPE(element) *e = ht->elements + i;
		if(e->hash & HT_HASH_LIVE_BIT) {
			if((ret = callback(e->key, e->value, arg))) {
				break;
			}
			--remaining;
		}
	}

	HT_PRIV_FUNC(end_read)(ht);
	return ret;
}

HT_DECLARE_FUNC(void, iter_begin, (HT_BASETYPE *ht, HT_TYPE(iter) *iter)) {
	HT_PRIV_FUNC(begin_read)(ht);
	memset(iter, 0, sizeof(*iter));
	iter->hashtable = ht;
	iter->private.remaining = ht->num_elements_occupied;
	iter->has_data = iter->private.remaining;
	HT_FUNC(iter_next)(iter);
}

HT_DECLARE_FUNC(void, iter_next, (HT_TYPE(iter) *iter)) {
	HT_BASETYPE *ht = iter->hashtable;

	if(!iter->private.remaining) {
		iter->has_data = false;
		return;
	}

	for(;;) {
		ht_size_t i = iter->private.i++;
		assert(i < ht->num_elements_allocated);
		HT_TYPE(element) *e = ht->elements + i;

		if(e->hash & HT_HASH_LIVE_BIT) {
			--iter->private.remaining;
			iter->key = e->key;
			iter->value = e->value;
			return;
		}
	}
}

HT_DECLARE_FUNC(void, iter_end, (HT_TYPE(iter) *iter)) {
	HT_PRIV_FUNC(end_read)(iter->hashtable);
}

#endif // HT_IMPL

/***********\
 * Cleanup *
\***********/

// Generated with:
// $ sed -e 's/.*#define\s*//' -e 's/[^A-Z0-9_].*//' -e '/^$/d' -e 's/^/#undef /' hashtable.inc.h | sort -u
// I'm sure this can be done much more efficiently, but I don't care to figure it out.

// It is intentional that it also catches the examples in comments.
// The user-supplied macros have to be #undef'd too.

#undef HT_BASETYPE
#undef HT_DECL
#undef HT_DECLARE_FUNC
#undef HT_DECLARE_PRIV_FUNC
#undef HT_FUNC
#undef HT_FUNC_COPY_KEY
#undef HT_FUNC_FREE_KEY
#undef HT_FUNC_HASH_KEY
#undef HT_FUNC_KEYS_EQUAL
#undef HT_IMPL
#undef HT_KEY_CONST
#undef HT_KEY_TYPE
#undef HT_MIN_SIZE
#undef HT_NAME
#undef HT_PRIV_FUNC
#undef HT_PRIV_NAME
#undef HT_SUFFIX
#undef HT_THREAD_SAFE
#undef HT_TYPE
#undef HT_VALUE_CONST
#undef HT_VALUE_TYPE
#undef _HT_NAME_INNER1
#undef _HT_NAME_INNER2
#undef _HT_PRIV_NAME_INNER1
#undef _HT_PRIV_NAME_INNER2
#undef HT_KEY_FMT
#undef HT_KEY_PRINTABLE
#undef HT_VALUE_FMT
#undef HT_VALUE_PRINTABLE
