8 n__hash_8c
gullradriel edited this page 2026-10-29 20:54:40 +01:00

src/n_hash.c

Hash functions and table. More...

Functions

Name
uint64_t getblock64(const uint64_t * p, const size_t i)
Block read - modified from murmur's author, ajusted byte endianess.
uint32_t getblock32(const uint32_t * p, const size_t i)
Block read - modified from murmur's author, ajusted byte endianess.
uint64_t fmix64(uint64_t k)
Finalization mix - force all bits of a hash block to avalanche (from murmur's author).
uint32_t fmix32(uint32_t h)
Finalization mix - force all bits of a hash block to avalanche (from murmur's author).
void _ht_search_trie_helper(LIST * results, HASH_NODE * node, int(*)(HASH_NODE *node) node_is_matching)
Recursive function to search tree's keys and apply a matching func to put results in the list.
LIST * _ht_search_trie(HASH_TABLE * table, int(*)(HASH_NODE *node) node_is_matching)
Search tree's keys and apply a matching func to put results in the list.
LIST * _ht_search(HASH_TABLE * table, int(*)(HASH_NODE *node) node_is_matching)
Search hash table's keys and apply a matching func to put results in the list.
int _ht_remove_trie(HASH_TABLE * table, const char * key)
Remove a key from a trie table and destroy the node.
int _ht_remove(HASH_TABLE * table, const char * key)
Remove a key from a hash table.
int _ht_put_string_trie(HASH_TABLE * table, const char * key, char * string)
put a duplicate of the string value with given key in the targeted hash table [TRIE HASH TABLE)
int _ht_put_string_ptr_trie(HASH_TABLE * table, const char * key, char * string)
put a pointer to the string value with given key in the targeted hash table [TRIE HASH TABLE)
int _ht_put_string_ptr(HASH_TABLE * table, const char * key, char * string)
put a null terminated char *string with given key in the targeted hash table (pointer)
int _ht_put_string(HASH_TABLE * table, const char * key, char * string)
put a null terminated char *string with given key in the targeted hash table (copy of string)
int _ht_put_ptr_trie(HASH_TABLE * table, const char * key, void * ptr, void(*)(void *ptr) destructor, void ()(void *ptr) duplicator)
put a pointer to the string value with given key in the targeted hash table [TRIE HASH TABLE)
int _ht_put_ptr(HASH_TABLE * table, const char * key, void * ptr, void(*)(void *ptr) destructor, void ()(void *ptr) duplicator)
put a pointer value with given key in the targeted hash table
int _ht_put_int_trie(HASH_TABLE * table, const char * key, int64_t value)
put an integral value with given key in the targeted hash table [TRIE HASH TABLE)
int _ht_put_int(HASH_TABLE * table, const char * key, int64_t value)
put an integral value with given key in the targeted hash table [CLASSIC HASH TABLE)
int _ht_put_double_trie(HASH_TABLE * table, const char * key, double value)
put a double value with given key in the targeted hash table [TRIE HASH TABLE)
int _ht_put_double(HASH_TABLE * table, const char * key, double value)
put a double value with given key in the targeted hash table
void _ht_print_trie_helper(HASH_TABLE * table, HASH_NODE * node)
Recursive function to print trie tree's keys and values.
void _ht_print_trie(HASH_TABLE * table)
Generic print func call for trie trees.
void _ht_print(HASH_TABLE * table)
Generic print func call for classic hash tables.
void _ht_node_destroy(void * node)
destroy a HASH_NODE by first calling the HASH_NODE destructor
HASH_NODE * _ht_new_string_ptr_node(HASH_TABLE * table, const char * key, char * value)
node creation, HASH_CLASSIC mode, pointer to string value
HASH_NODE * _ht_new_string_node(HASH_TABLE * table, const char * key, const char * value)
node creation, HASH_CLASSIC mode, strdup of value
size_t _ht_new_seed(void )
HASH_NODE * _ht_new_ptr_node(HASH_TABLE * table, const char * key, void * value, void(*)(void *ptr) destructor, void ()(void *ptr) duplicator)
node creation, HASH_CLASSIC mode, pointer to string value
HASH_NODE * _ht_new_node_trie(HASH_TABLE * table, const char key)
node creation, HASH_CLASSIC mode
HASH_NODE * _ht_new_node(const HASH_TABLE * table, const char * key)
node creation, HASH_CLASSIC mode
HASH_NODE * _ht_new_int_node(HASH_TABLE * table, const char * key, int64_t value)
node creation, HASH_CLASSIC mode
HASH_NODE * _ht_new_double_node(HASH_TABLE * table, const char * key, double value)
node creation, HASH_CLASSIC mode
uint64_t _ht_mix(uint64_t x)
void _ht_key_init(void )
int _ht_is_leaf_node_trie(HASH_TABLE * table, const char * key)
Search a key and tell if it's holding a value (leaf).
HASH_VALUE _ht_hash_value(const HASH_TABLE * table, const char * key, size_t key_len)
compute a key's hash value with a correctly sized output buffer
int _ht_get_string_trie(HASH_TABLE * table, const char * key, char ** val)
Retrieve an char *string value in the hash table, at the given key.
int _ht_get_string(HASH_TABLE * table, const char * key, char ** val)
Retrieve a char *string value in the hash table, at the given key.
int _ht_get_ptr_trie(HASH_TABLE * table, const char * key, void ** val)
Retrieve a pointer value in the hash table, at the given key.
int _ht_get_ptr(HASH_TABLE * table, const char * key, void ** val)
Retrieve a pointer value in the hash table, at the given key.
HASH_NODE * _ht_get_node_trie(HASH_TABLE * table, const char * key)
retrieve a HASH_NODE at key from table
HASH_NODE * _ht_get_node(HASH_TABLE * table, const char * key)
return the associated key's node inside the hash_table
int _ht_get_int_trie(HASH_TABLE * table, const char * key, int64_t * val)
Retrieve an integral value in the hash table, at the given key.
int _ht_get_int(HASH_TABLE * table, const char * key, int64_t * val)
Retrieve an integral value in the hash table, at the given key.
int _ht_get_double_trie(HASH_TABLE * table, const char * key, double * val)
Retrieve an double value in the hash table, at the given key.
int _ht_get_double(HASH_TABLE * table, const char * key, double * val)
Retrieve a double value in the hash table, at the given key.
char * _ht_find_longest_prefix_trie(HASH_TABLE * table, const char * key)
find the longest prefix string that is not the current key
int _ht_depth_first_search(HASH_NODE * node, LIST * results)
recursive, helper for ht_get_completion_list, get the list of leaf starting from node
size_t _ht_check_trie_divergence(HASH_TABLE * table, const char * key)
check and return branching index in key if any
int _empty_ht_trie(HASH_TABLE * table)
Empty a TRIE hash table.
int _empty_ht(HASH_TABLE * table)
Empty a hash table (CLASSIC mode).
int _destroy_ht_trie(HASH_TABLE ** table)
Free and set the table to NULL (TRIE mode).
int _destroy_ht(HASH_TABLE ** table)
Free and set the table to NULL.

Attributes

Name
uint64_t _ht_table_counter
uint64_t _ht_process_key
pthread_once_t _ht_key_once
pthread_mutex_t _ht_counter_lock

Defines

Name
ROTL64(x, r)
64 bit rotate left
ROTL32(x, r)
32 bit rotate left
BYTESWAP64(x)
32 bits bytes swap
BYTESWAP32(x)
32 bits bytes swap
BIG_CONSTANT(x)
max unsigned long long

Detailed Description

Hash functions and table.

Author: Castagnier Mickael

Version: 2.0

Date: 16/03/2015

Functions Documentation

function getblock64

uint64_t getblock64(
    const uint64_t * p,
    const size_t i
)

Block read - modified from murmur's author, ajusted byte endianess.

Parameters:

  • p value
  • i position

Return: value at position inside block

function getblock32

uint32_t getblock32(
    const uint32_t * p,
    const size_t i
)

Block read - modified from murmur's author, ajusted byte endianess.

Parameters:

  • p value
  • i position

Return: value at position inside block

function fmix64

uint64_t fmix64(
    uint64_t k
)

Finalization mix - force all bits of a hash block to avalanche (from murmur's author).

Parameters:

  • k value

Return: mixed value

function fmix32

uint32_t fmix32(
    uint32_t h
)

Finalization mix - force all bits of a hash block to avalanche (from murmur's author).

Parameters:

  • h value

Return: mixed value

function _ht_search_trie_helper

void _ht_search_trie_helper(
    LIST * results,
    HASH_NODE * node,
    int(*)(HASH_NODE *node) node_is_matching
)

Recursive function to search tree's keys and apply a matching func to put results in the list.

Parameters:

  • results targeted and initialized LIST in which the matching nodes will be put
  • node targeted starting trie node
  • node_is_matching pointer to a matching function to use

function _ht_search_trie

LIST * _ht_search_trie(
    HASH_TABLE * table,
    int(*)(HASH_NODE *node) node_is_matching
)

Search tree's keys and apply a matching func to put results in the list.

Parameters:

  • table targeted table
  • node_is_matching pointer to a matching function to use

Return: NULL or a LIST *list of HASH_NODE *elements

LIST * _ht_search(
    HASH_TABLE * table,
    int(*)(HASH_NODE *node) node_is_matching
)

Search hash table's keys and apply a matching func to put results in the list.

Parameters:

  • table targeted table
  • node_is_matching pointer to a matching function to use

Return: NULL or a LIST *list of HASH_NODE *elements

function _ht_remove_trie

int _ht_remove_trie(
    HASH_TABLE * table,
    const char * key
)

Remove a key from a trie table and destroy the node.

Parameters:

  • table targeted tabled
  • key the node key to kill

Return: TRUE or FALSE

function _ht_remove

int _ht_remove(
    HASH_TABLE * table,
    const char * key
)

Remove a key from a hash table.

Parameters:

  • table targeted hash table
  • key Key to remove

Return: TRUE or FALSE.

function _ht_put_string_trie

int _ht_put_string_trie(
    HASH_TABLE * table,
    const char * key,
    char * string
)

put a duplicate of the string value with given key in the targeted hash table [TRIE HASH TABLE)

Parameters:

  • table targeted hash table
  • key associated value's key
  • string string value to put (copy)

Return: TRUE or FALSE

function _ht_put_string_ptr_trie

int _ht_put_string_ptr_trie(
    HASH_TABLE * table,
    const char * key,
    char * string
)

put a pointer to the string value with given key in the targeted hash table [TRIE HASH TABLE)

Parameters:

  • table targeted hash table
  • key associated value's key
  • string string value to put (pointer)

Return: TRUE or FALSE

function _ht_put_string_ptr

int _ht_put_string_ptr(
    HASH_TABLE * table,
    const char * key,
    char * string
)

put a null terminated char *string with given key in the targeted hash table (pointer)

Parameters:

  • table targeted hash table
  • key Associated value's key
  • string The string to put

Return: TRUE or FALSE

function _ht_put_string

int _ht_put_string(
    HASH_TABLE * table,
    const char * key,
    char * string
)

put a null terminated char *string with given key in the targeted hash table (copy of string)

Parameters:

  • table targeted hash table
  • key Associated value's key
  • string string value to put (will be strdup'ed)

Return: TRUE or FALSE

function _ht_put_ptr_trie

int _ht_put_ptr_trie(
    HASH_TABLE * table,
    const char * key,
    void * ptr,
    void(*)(void *ptr) destructor,
    void *(*)(void *ptr) duplicator
)

put a pointer to the string value with given key in the targeted hash table [TRIE HASH TABLE)

Parameters:

  • table targeted hash table
  • key associated value's key
  • ptr pointer value to put
  • destructor the destructor func for ptr or NULL
  • duplicator the duplicate func for ptr or NULL

Return: TRUE or FALSE

function _ht_put_ptr

int _ht_put_ptr(
    HASH_TABLE * table,
    const char * key,
    void * ptr,
    void(*)(void *ptr) destructor,
    void *(*)(void *ptr) duplicator
)

put a pointer value with given key in the targeted hash table

Parameters:

  • table targeted hash table
  • key Associated value's key
  • ptr pointer value to put
  • destructor Pointer to the ptr type destructor function. Leave to NULL if there isn't
  • duplicator Pointer to the ptr type duplicator function. Leave to NULL if there isn't

Return: TRUE or FALSE

function _ht_put_int_trie

int _ht_put_int_trie(
    HASH_TABLE * table,
    const char * key,
    int64_t value
)

put an integral value with given key in the targeted hash table [TRIE HASH TABLE)

Parameters:

  • table targeted hash table
  • key associated value's key
  • value integral value to put

Return: TRUE or FALSE

function _ht_put_int

int _ht_put_int(
    HASH_TABLE * table,
    const char * key,
    int64_t value
)

put an integral value with given key in the targeted hash table [CLASSIC HASH TABLE)

Parameters:

  • table targeted table
  • key associated value's key
  • value integral value to put

Return: TRUE or FALSE

function _ht_put_double_trie

int _ht_put_double_trie(
    HASH_TABLE * table,
    const char * key,
    double value
)

put a double value with given key in the targeted hash table [TRIE HASH TABLE)

Parameters:

  • table targeted hash table
  • key associated value's key
  • value double value to put

Return: TRUE or FALSE

function _ht_put_double

int _ht_put_double(
    HASH_TABLE * table,
    const char * key,
    double value
)

put a double value with given key in the targeted hash table

Parameters:

  • table targeted hash table
  • key associated value's key
  • value double value to put

Return: TRUE or FALSE

function _ht_print_trie_helper

void _ht_print_trie_helper(
    HASH_TABLE * table,
    HASH_NODE * node
)

Recursive function to print trie tree's keys and values.

Parameters:

  • table targeted hash table
  • node current node

function _ht_print_trie

void _ht_print_trie(
    HASH_TABLE * table
)

Generic print func call for trie trees.

Parameters:

  • table targeted hash table

function _ht_print

void _ht_print(
    HASH_TABLE * table
)

Generic print func call for classic hash tables.

Parameters:

  • table targeted hash table

function _ht_node_destroy

void _ht_node_destroy(
    void * node
)

destroy a HASH_NODE by first calling the HASH_NODE destructor

Parameters:

  • node The node to kill

function _ht_new_string_ptr_node

HASH_NODE * _ht_new_string_ptr_node(
    HASH_TABLE * table,
    const char * key,
    char * value
)

node creation, HASH_CLASSIC mode, pointer to string value

Parameters:

  • table targeted table
  • key key of new node
  • value char *value of key

Return: NULL or a new HASH_NODE *

function _ht_new_string_node

HASH_NODE * _ht_new_string_node(
    HASH_TABLE * table,
    const char * key,
    const char * value
)

node creation, HASH_CLASSIC mode, strdup of value

Parameters:

  • table targeted table
  • key key of new node
  • value char *value of key

Return: NULL or a new HASH_NODE *

function _ht_new_seed

static size_t _ht_new_seed(
    void 
)

function _ht_new_ptr_node

HASH_NODE * _ht_new_ptr_node(
    HASH_TABLE * table,
    const char * key,
    void * value,
    void(*)(void *ptr) destructor,
    void *(*)(void *ptr) duplicator
)

node creation, HASH_CLASSIC mode, pointer to string value

Parameters:

  • table targeted table
  • key key of new node
  • value pointer data of key
  • destructor function pointer to a destructor of value type
  • duplicator function pointer to a duplicator of value type (or NULL)

Return: NULL or a new HASH_NODE *

function _ht_new_node_trie

HASH_NODE * _ht_new_node_trie(
    HASH_TABLE * table,
    const char key
)

node creation, HASH_CLASSIC mode

Parameters:

  • table targeted table
  • key key of new node

Return: NULL or a new HASH_NODE *

function _ht_new_node

HASH_NODE * _ht_new_node(
    const HASH_TABLE * table,
    const char * key
)

node creation, HASH_CLASSIC mode

Parameters:

  • table targeted table
  • key key of new node

Return: NULL or a new HASH_NODE *

function _ht_new_int_node

HASH_NODE * _ht_new_int_node(
    HASH_TABLE * table,
    const char * key,
    int64_t value
)

node creation, HASH_CLASSIC mode

Parameters:

  • table targeted table
  • key key of new node
  • value int value of key

Return: NULL or a new HASH_NODE *

function _ht_new_double_node

HASH_NODE * _ht_new_double_node(
    HASH_TABLE * table,
    const char * key,
    double value
)

node creation, HASH_CLASSIC mode

Parameters:

  • table targeted table
  • key key of new node
  • value double value of key

Return: NULL or a new HASH_NODE *

function _ht_mix

static uint64_t _ht_mix(
    uint64_t x
)

function _ht_key_init

static void _ht_key_init(
    void 
)

function _ht_is_leaf_node_trie

int _ht_is_leaf_node_trie(
    HASH_TABLE * table,
    const char * key
)

Search a key and tell if it's holding a value (leaf).

Parameters:

  • table targeted hash table
  • key key verify

Return: 1 or 0

function _ht_hash_value

static HASH_VALUE _ht_hash_value(
    const HASH_TABLE * table,
    const char * key,
    size_t key_len
)

compute a key's hash value with a correctly sized output buffer

Parameters:

  • table targeted table, for its seed
  • key the key to hash
  • key_len length of key

Return: the key's hash value

Both MurmurHash 128-bit variants write exactly 16 bytes, whatever HASH_VALUE happens to measure:

64-bit: MurmurHash3_x64_128 -> 2 x uint64_t, and HASH_VALUE[2] is 16 bytes 32-bit: MurmurHash3_x86_128 -> 4 x uint32_t, but HASH_VALUE is size_t, so a HASH_VALUE[2] is only 8 bytes

Passing a HASH_VALUE[2] therefore overflowed the stack by 8 bytes on every hash insert, lookup and remove of every 32-bit build, while 64-bit builds fitted exactly and never showed it. Found as a SIGSEGV at startup in an Android armeabi-v7a build of DarkChan (_ht_new_node <- _ht_put_ptr <- n_gui_add_label), 2026-08-19.

Owning the buffer in one place is the point: the callers only ever wanted the first HASH_VALUE of the digest, and none of them can now get the size wrong. memcpy rather than a cast keeps it free of aliasing assumptions, and takes the same leading bytes the callers used before, so hash values are unchanged on 64-bit.

function _ht_get_string_trie

int _ht_get_string_trie(
    HASH_TABLE * table,
    const char * key,
    char ** val
)

Retrieve an char *string value in the hash table, at the given key.

Parameters:

  • table targeted hash table
  • key associated value's key
  • val a pointer to a destination char *string

Return: TRUE or FALSE.

Leave val untouched if key is not found.

function _ht_get_string

int _ht_get_string(
    HASH_TABLE * table,
    const char * key,
    char ** val
)

Retrieve a char *string value in the hash table, at the given key.

Parameters:

  • table targeted hash table
  • key associated value's key
  • val A pointer to an empty destination char *string

Return: TRUE or FALSE.

Leave val untouched if key is not found.

function _ht_get_ptr_trie

int _ht_get_ptr_trie(
    HASH_TABLE * table,
    const char * key,
    void ** val
)

Retrieve a pointer value in the hash table, at the given key.

Parameters:

  • table targeted hash table
  • key associated value's key
  • val a pointer to a destination pointer

Return: TRUE or FALSE.

Leave val untouched if key is not found.

function _ht_get_ptr

int _ht_get_ptr(
    HASH_TABLE * table,
    const char * key,
    void ** val
)

Retrieve a pointer value in the hash table, at the given key.

Parameters:

  • table targeted hash table
  • key associated value's key
  • val A pointer to an empty pointer store

Return: TRUE or FALSE.

Leave val untouched if key is not found.

function _ht_get_node_trie

HASH_NODE * _ht_get_node_trie(
    HASH_TABLE * table,
    const char * key
)

retrieve a HASH_NODE at key from table

Parameters:

  • table targeted hash table
  • key associated value's key

Return: a HASH_NODE *node or NULL

function _ht_get_node

HASH_NODE * _ht_get_node(
    HASH_TABLE * table,
    const char * key
)

return the associated key's node inside the hash_table

Parameters:

  • table targeted table
  • key Associated value's key

Return: The found node, or NULL

function _ht_get_int_trie

int _ht_get_int_trie(
    HASH_TABLE * table,
    const char * key,
    int64_t * val
)

Retrieve an integral value in the hash table, at the given key.

Parameters:

  • table targeted hash table
  • key associated value's key
  • val a pointer to a destination integer

Return: TRUE or FALSE.

Leave val untouched if key is not found.

function _ht_get_int

int _ht_get_int(
    HASH_TABLE * table,
    const char * key,
    int64_t * val
)

Retrieve an integral value in the hash table, at the given key.

Parameters:

  • table targeted hash table
  • key associated value's key
  • val A pointer to a destination integer

Return: TRUE or FALSE.

Leave val untouched if key is not found.

function _ht_get_double_trie

int _ht_get_double_trie(
    HASH_TABLE * table,
    const char * key,
    double * val
)

Retrieve an double value in the hash table, at the given key.

Parameters:

  • table targeted hash table
  • key associated value's key
  • val a pointer to a destination double

Return: TRUE or FALSE.

Leave val untouched if key is not found.

function _ht_get_double

int _ht_get_double(
    HASH_TABLE * table,
    const char * key,
    double * val
)

Retrieve a double value in the hash table, at the given key.

Parameters:

  • table targeted hash table
  • key associated value's key
  • val A pointer to a destination double

Return: TRUE or FALSE.

Leave val untouched if key is not found.

function _ht_find_longest_prefix_trie

char * _ht_find_longest_prefix_trie(
    HASH_TABLE * table,
    const char * key
)

find the longest prefix string that is not the current key

Parameters:

  • table targeted hash table
  • key key to search prefix for

Return: TRUE or FALSE

int _ht_depth_first_search(
    HASH_NODE * node,
    LIST * results
)

recursive, helper for ht_get_completion_list, get the list of leaf starting from node

Parameters:

  • node starting node
  • results initialized LIST * for the matching NODES

Return: TRUE or FALSE

function _ht_check_trie_divergence

size_t _ht_check_trie_divergence(
    HASH_TABLE * table,
    const char * key
)

check and return branching index in key if any

Parameters:

  • table targeted hash table
  • key associated value's key

Return: index of the divergence or SIZE_MAX on error

function _empty_ht_trie

int _empty_ht_trie(
    HASH_TABLE * table
)

Empty a TRIE hash table.

Parameters:

  • table targeted hash table

Return: TRUE or FALSE.

function _empty_ht

int _empty_ht(
    HASH_TABLE * table
)

Empty a hash table (CLASSIC mode).

Parameters:

  • table targeted hash table

Return: TRUE or FALSE.

function _destroy_ht_trie

int _destroy_ht_trie(
    HASH_TABLE ** table
)

Free and set the table to NULL (TRIE mode).

Parameters:

  • table targeted hash table

Return: TRUE or FALSE.

function _destroy_ht

int _destroy_ht(
    HASH_TABLE ** table
)

Free and set the table to NULL.

Parameters:

  • table targeted hash table

Return: TRUE or FALSE.

Attributes Documentation

variable _ht_table_counter

static uint64_t _ht_table_counter = 0;

variable _ht_process_key

static uint64_t _ht_process_key = 0;

variable _ht_key_once

static pthread_once_t _ht_key_once = PTHREAD_ONCE_INIT;

variable _ht_counter_lock

static pthread_mutex_t _ht_counter_lock = PTHREAD_MUTEX_INITIALIZER;

Macros Documentation

define ROTL64

#define ROTL64(
    x,
    r
)
(((x) << (r)) | ((x) >> (64 - (r))))

64 bit rotate left

define ROTL32

#define ROTL32(
    x,
    r
)
(((x) << (r)) | ((x) >> (32 - (r))))

32 bit rotate left

define BYTESWAP64

#define BYTESWAP64(
    x
)
(((uint64_t)(x) << 56) |                           \
 (((uint64_t)(x) << 40) & 0X00FF000000000000ULL) | \
 (((uint64_t)(x) << 24) & 0X0000FF0000000000ULL) | \
 (((uint64_t)(x) << 8) & 0X000000FF00000000ULL) |  \
 (((uint64_t)(x) >> 8) & 0X00000000FF000000ULL) |  \
 (((uint64_t)(x) >> 24) & 0X0000000000FF0000ULL) | \
 (((uint64_t)(x) >> 40) & 0X000000000000FF00ULL) | \
 ((uint64_t)(x) >> 56))

32 bits bytes swap

define BYTESWAP32

#define BYTESWAP32(
    x
)
((((x) & 0xFF) << 24) | (((x) >> 24) & 0xFF) | (((x) & 0x0000FF00) << 8) | (((x) & 0x00FF0000) >> 8))

32 bits bytes swap

define BIG_CONSTANT

#define BIG_CONSTANT(
    x
)
(x##LLU)

max unsigned long long

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#include "nilorea/n_common.h"
#include "nilorea/n_log.h"
#include "nilorea/n_hash.h"
#include "nilorea/n_str.h"

#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#if defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))
#include <sys/random.h>
#define N_HASH_HAVE_GETRANDOM 1
#endif

#ifdef __windows__
/* n_windows.h brings winsock2 (htonl) and must come first: bcrypt.h uses LONG/ULONG without defining them;
 * link with -lbcrypt (in the Windows CLIBS) */
#include "nilorea/n_windows.h"
#include <bcrypt.h>
#else
#include <arpa/inet.h>
#endif

/* Trie tree tables */

HASH_NODE* _ht_new_node_trie(HASH_TABLE* table, const char key) {
    __n_assert(table, return NULL);

    HASH_NODE* new_hash_node = NULL;
    Malloc(new_hash_node, HASH_NODE, 1);
    __n_assert(new_hash_node, n_log(LOG_ERR, "Could not allocate new_hash_node"); return NULL);
    new_hash_node->key = NULL;
    new_hash_node->hash_value = 0;
    new_hash_node->data.ptr = NULL;
    new_hash_node->destroy_func = NULL;
    new_hash_node->children = NULL;
    new_hash_node->is_leaf = 0;
    new_hash_node->need_rehash = 0;
    new_hash_node->alphabet_length = table->alphabet_length;

    Malloc(new_hash_node->children, HASH_NODE*, table->alphabet_length);
    __n_assert(new_hash_node->children, n_log(LOG_ERR, "Could not allocate new_hash_node"); Free(new_hash_node); return NULL);

    /* n_log( LOG_DEBUG , "node: %d %c table: alpha: %d / offset %d" , key , key , table -> alphabet_length , table -> alphabet_offset ); */

    for (size_t it = 0; it < table->alphabet_length; it++) {
        new_hash_node->children[it] = NULL;
    }
    new_hash_node->is_leaf = 0;
    new_hash_node->key_id = key;
    return new_hash_node;
} /* _ht_new_node_trie(...) */

int _ht_is_leaf_node_trie(HASH_TABLE* table, const char* key) {
    __n_assert(table, return -1);

    /* checks if the prefix match of key and root is a leaf node */
    HASH_NODE* node = table->root;
    for (size_t it = 0; key[it]; it++) {
        size_t index = (size_t)key[it] - table->alphabet_offset;
        if (index >= table->alphabet_length) {
            n_log(LOG_ERR, "Invalid value %zu for charater at position %zu of %s, set to 0", index, it, key);
            index = 0;
        }
        if (node->children[index]) {
            node = node->children[index];
        } else {
            return 0;
        }
    }
    return node->is_leaf;
} /* _ht_is_leaf_node_trie(...) */

void _ht_node_destroy(void* node) {
    HASH_NODE* node_ptr = (HASH_NODE*)node;
    __n_assert(node_ptr, return);
    if (node_ptr->type == HASH_STRING) {
        Free(node_ptr->data.string);
    }
    if (node_ptr->type == HASH_PTR) {
        if (node_ptr->destroy_func && node_ptr->data.ptr) {
            node_ptr->destroy_func(node_ptr->data.ptr);
        }
        /* No free by default. must be passed as a destroy_func
           else
           {
           Free( node_ptr  -> data . ptr );
           }
           */
    }
    FreeNoLog(node_ptr->key);
    if (node_ptr->alphabet_length > 0) {
        for (size_t it = 0; it < node_ptr->alphabet_length; it++) {
            if (node_ptr->children[it]) {
                _ht_node_destroy(node_ptr->children[it]);
            }
        }
        Free(node_ptr->children);
    }
    Free(node_ptr);
} /* _ht_node_destroy */

size_t _ht_check_trie_divergence(HASH_TABLE* table, const char* key) {
    __n_assert(table, return SIZE_MAX);
    __n_assert(key, return SIZE_MAX);
    __n_assert(table->root, return SIZE_MAX);

    HASH_NODE* node = table->root;

    size_t last_index = 0;
    /* `node` is non-null on entry (table->root checked above) and only ever
     * gets reassigned from `node->children[index]` after that slot was
     * verified non-null. We still gate the loop on `node` so the static
     * analyzer narrows `node` to non-null at every dereference inside the
     * body, older clang static analyzers (the one in Ubuntu's clang-tools
     * package, used by CI) don't track that constraint through the
     * children[] reassignment, even though it holds. */
    for (size_t it = 0; node && key[it] != '\0'; it++) {
        size_t index = (size_t)key[it] - table->alphabet_offset;
        if (index >= table->alphabet_length) {
            n_log(LOG_ERR, "Invalid value %zu for charater at position %zu of %s, set to 0", index, it, key);
            index = 0;
        }
        HASH_NODE* child = node->children[index];
        if (child) {
            /* child check */
            for (size_t it2 = 0; it2 < table->alphabet_length; it2++) {
                if (it2 != (unsigned)index && node->children[it2]) {
                    /* child found, update branch index */
                    last_index = it + 1;
                    break;
                }
            }
            /* Go to the next child in the sequence */
            node = child;
        }
    }
    return last_index;
} /* _ht_check_trie_divergence(...) */

char* _ht_find_longest_prefix_trie(HASH_TABLE* table, const char* key) {
    __n_assert(table, return NULL);
    __n_assert(key, return NULL);

    size_t len = strlen(key);

    /* start with full key and backtrack to search for divergences */
    char* longest_prefix = NULL;
    Malloc(longest_prefix, char, len + 1);
    __n_assert(longest_prefix, return NULL);
    memcpy(longest_prefix, key, len);
    /* memcpy does not add a null terminator; set it explicitly */
    longest_prefix[len] = '\0';

    /* check branching from the root */
    size_t branch_index = _ht_check_trie_divergence(table, longest_prefix);
    if (branch_index > 0 && branch_index != SIZE_MAX) {
        /* truncate to the branch point */
        longest_prefix[branch_index - 1] = '\0';
        /* shrink the allocation; Realloc preserves the original pointer on
           failure so the caller still gets a valid terminated string */
        Realloc(longest_prefix, char, branch_index + 1);
    }
    return longest_prefix;
} /* _ht_find_longest_prefix_trie(...) */

int _ht_remove_trie(HASH_TABLE* table, const char* key) {
    __n_assert(table, return FALSE);
    __n_assert(table->root, return FALSE);
    __n_assert(key, return FALSE);

    /* stop if matching node not a leaf node */
    if (!_ht_is_leaf_node_trie(table, key)) {
        return FALSE;
    }

    HASH_NODE* node = table->root;
    /* find the longest prefix string that is not the current key */
    char* longest_prefix = _ht_find_longest_prefix_trie(table, key);
    if (!longest_prefix) {
        n_log(LOG_ERR, "couldn't find longest prefix for key %s", key);
        return FALSE;
    }
    if (longest_prefix[0] == '\0') {
        Free(longest_prefix);
        return FALSE;
    }
    /* keep track of position in the tree */
    size_t it = 0;
    for (it = 0; longest_prefix[it] != '\0'; it++) {
        size_t index = (size_t)longest_prefix[it] - table->alphabet_offset;
        if (index >= table->alphabet_length) {
            n_log(LOG_ERR, "Invalid value %zu for charater at position %zu of %s, set to 0", index, it, key);
            index = 0;
        }
        if (node->children[index] != NULL) {
            /* common prefix, keep moving */
            node = node->children[index];
        } else {
            /* not found */
            Free(longest_prefix);
            return FALSE;
        }
    }
    /* deepest common node between the two strings */
    /* deleting the sequence corresponding to key */
    for (; key[it] != '\0'; it++) {
        size_t index = (size_t)key[it] - table->alphabet_offset;
        if (index >= table->alphabet_length) {
            n_log(LOG_ERR, "Invalid value %zu for charater at position %zu of %s, set to 0", index, it, key);
            index = 0;
        }
        if (node->children[index]) {
            /* delete the remaining sequence */
            HASH_NODE* node_to_kill = node->children[index];
            node->children[index] = NULL;
            _ht_node_destroy(node_to_kill);
        }
    }
    Free(longest_prefix);

    table->nb_keys--;

    return TRUE;
} /* _ht_remove_trie(...) */

int _ht_put_int_trie(HASH_TABLE* table, const char* key, HASH_INT_TYPE value) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);

    HASH_NODE* node = table->root;

    for (size_t it = 0; key[it] != '\0'; it++) {
        size_t index = (size_t)key[it] - table->alphabet_offset;
        if (index >= table->alphabet_length) {
            n_log(LOG_ERR, "Invalid value %zu for charater at position %zu of %s, set to 0", index, it, key);
            index = 0;
        }
        /* n_log( LOG_DEBUG , "index:%d" , index ); */
        if (node->children[index] == NULL) {
            /* create a node */
            node->children[index] = _ht_new_node_trie(table, key[it]);
            __n_assert(node->children[index], return FALSE);
        } else {
            /* nothing to do since node is existing */
        }
        /* go down a level, to the child referenced by index */
        node = node->children[index];
    }
    /* At the end of the key, mark this node as the leaf node */
    int was_leaf = node->is_leaf;
    if (was_leaf) {
        FreeNoLog(node->key);
    }
    node->is_leaf = 1;
    /* Put the key */
    node->key = strdup(key);
    if (!node->key) {
        n_log(LOG_ERR, "strdup failure for key [%s]", key);
        table->nb_keys++; /* compensate for upcoming remove */
        _ht_remove_trie(table, key);
        return FALSE;
    }
    /* Put the value */
    node->data.ival = value;
    node->type = HASH_INT;

    if (!was_leaf) {
        table->nb_keys++;
    }

    return TRUE;
} /* _ht_put_int_trie(...) */

int _ht_put_double_trie(HASH_TABLE* table, const char* key, double value) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);

    HASH_NODE* node = table->root;

    for (size_t it = 0; key[it] != '\0'; it++) {
        size_t index = (size_t)key[it] - table->alphabet_offset;
        if (index >= table->alphabet_length) {
            n_log(LOG_ERR, "Invalid value %zu for charater at position %zu of %s, set to 0", index, it, key);
            index = 0;
        }
        if (node->children[index] == NULL) {
            /* create a node */
            node->children[index] = _ht_new_node_trie(table, key[it]);
            __n_assert(node->children[index], return FALSE);
        } else {
            /* nothing to do since node is existing */
        }
        /* go down a level, to the child referenced by index */
        node = node->children[index];
    }
    /* At the end of the key, mark this node as the leaf node */
    int was_leaf = node->is_leaf;
    if (was_leaf) {
        FreeNoLog(node->key);
    }
    node->is_leaf = 1;
    /* Put the key */
    node->key = strdup(key);
    if (!node->key) {
        n_log(LOG_ERR, "strdup failure for key [%s]", key);
        table->nb_keys++; /* compensate for upcoming remove */
        _ht_remove_trie(table, key);
        return FALSE;
    }
    /* Put the value */
    node->data.fval = value;
    node->type = HASH_DOUBLE;

    if (!was_leaf) {
        table->nb_keys++;
    }

    return TRUE;
} /* _ht_put_double_trie(...) */

// cppcheck-suppress constParameterCallback ; callback signature must match typedef
int _ht_put_string_trie(HASH_TABLE* table, const char* key, char* string) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);

    HASH_NODE* node = table->root;

    for (size_t it = 0; key[it] != '\0'; it++) {
        size_t index = (size_t)key[it] - table->alphabet_offset;
        if (index >= table->alphabet_length) {
            n_log(LOG_ERR, "Invalid value %zu for charater at position %zu of %s, set to 0", index, it, key);
            index = 0;
        }
        if (node->children[index] == NULL) {
            /* create a node */
            node->children[index] = _ht_new_node_trie(table, key[it]);
            __n_assert(node->children[index], return FALSE);
        } else {
            /* nothing to do since node is existing */
        }
        /* go down a level, to the child referenced by index */
        node = node->children[index];
    }
    /* At the end of the key, mark this node as the leaf node */
    int was_leaf = node->is_leaf;
    if (was_leaf) {
        FreeNoLog(node->key);
        FreeNoLog(node->data.string);
    }
    node->is_leaf = 1;
    /* Put the key */
    node->key = strdup(key);
    if (!node->key) {
        n_log(LOG_ERR, "strdup failure for key [%s]", key);
        table->nb_keys++; /* compensate for upcoming remove */
        _ht_remove_trie(table, key);
        return FALSE;
    }
    /* Put the value */
    if (string) {
        node->data.string = strdup(string);
        if (!node->data.string) {
            n_log(LOG_ERR, "strdup failure for value at key [%s]", key);
            Free(node->key);
            table->nb_keys++; /* compensate for upcoming remove */
            _ht_remove_trie(table, key);
            return FALSE;
        }
    } else {
        node->data.string = NULL;
    }

    node->type = HASH_STRING;

    if (!was_leaf) {
        table->nb_keys++;
    }

    return TRUE;
} /* _ht_put_string_trie(...) */

int _ht_put_string_ptr_trie(HASH_TABLE* table, const char* key, char* string) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);

    HASH_NODE* node = table->root;

    for (size_t it = 0; key[it] != '\0'; it++) {
        size_t index = (size_t)key[it] - table->alphabet_offset;
        if (index >= table->alphabet_length) {
            n_log(LOG_ERR, "Invalid value %zu for charater at position %zu of %s, set to 0", index, it, key);
            index = 0;
        }
        if (node->children[index] == NULL) {
            /* create a node */
            node->children[index] = _ht_new_node_trie(table, key[it]);
            __n_assert(node->children[index], return FALSE);
        } else {
            /* nothing to do since node is existing */
        }
        /* go down a level, to the child referenced by index */
        node = node->children[index];
    }
    /* At the end of the key, mark this node as the leaf node */
    int was_leaf = node->is_leaf;
    if (was_leaf) {
        FreeNoLog(node->key);
        FreeNoLog(node->data.string);
    }
    node->is_leaf = 1;
    /* Put the key - string pointer variant does not strdup the value, but key must be duplicated */
    node->key = strdup(key);
    if (!node->key) {
        n_log(LOG_ERR, "strdup failure for key [%s]", key);
        table->nb_keys++; /* compensate for upcoming remove */
        _ht_remove_trie(table, key);
        return FALSE;
    }
    /* Put the string pointer (not a copy - caller owns the string) */
    node->data.string = string;
    node->type = HASH_STRING;

    if (!was_leaf) {
        table->nb_keys++;
    }

    return TRUE;
} /* _ht_put_string_ptr_trie(...) */

int _ht_put_ptr_trie(HASH_TABLE* table, const char* key, void* ptr, void (*destructor)(void* ptr), void* (*duplicator)(void* ptr)) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);

    HASH_NODE* node = table->root;

    for (size_t it = 0; key[it] != '\0'; it++) {
        size_t index = (size_t)key[it] - table->alphabet_offset;
        if (index >= table->alphabet_length) {
            n_log(LOG_ERR, "Invalid value %zu for character at position %zu of %s, set to 0", index, it, key);
            index = 0;
        }
        if (node->children[index] == NULL) {
            /* create a node */
            node->children[index] = _ht_new_node_trie(table, key[it]);
            __n_assert(node->children[index], return FALSE);
        } else {
            /* nothing to do since node is existing */
        }
        /* go down a level, to the child referenced by index */
        node = node->children[index];
    }
    /* At the end of the key, mark this node as the leaf node */
    int was_leaf = node->is_leaf;
    if (was_leaf) {
        FreeNoLog(node->key);
        /* free old value if a destructor was set */
        if (node->destroy_func && node->data.ptr) {
            node->destroy_func(node->data.ptr);
        }
    }
    node->is_leaf = 1;
    /* Put the key */
    node->key = strdup(key);
    if (!node->key) {
        n_log(LOG_ERR, "strdup failure for key [%s]", key);
        table->nb_keys++; /* compensate for upcoming remove */
        _ht_remove_trie(table, key);
        return FALSE;
    }
    /* Put the value */
    node->data.ptr = ptr;
    node->destroy_func = destructor;
    node->duplicate_func = duplicator;
    node->type = HASH_PTR;

    if (!was_leaf) {
        table->nb_keys++;
    }

    return TRUE;
} /* _ht_put_ptr_trie(...) */

HASH_NODE* _ht_get_node_trie(HASH_TABLE* table, const char* key) {
    __n_assert(table, return NULL);
    __n_assert(key, return NULL);

    HASH_NODE* node = NULL;

    if (key[0] != '\0') {
        node = table->root;
        for (size_t it = 0; key[it] != '\0'; it++) {
            size_t index = (size_t)key[it] - table->alphabet_offset;
            if (index >= table->alphabet_length) {
                n_log(LOG_DEBUG, "Invalid value %zu for charater at index %zu of %s, set to 0", index, it, key);
                return NULL;
            }
            if (node->children[index] == NULL) {
                /* not found */
                return NULL;
            }
            node = node->children[index];
        }
    } else {
        node = NULL;
    }
    return node;
} /* _ht_get_node_trie(...) */

int _ht_get_int_trie(HASH_TABLE* table, const char* key, HASH_INT_TYPE* val) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    if (strlen(key) == 0)
        return FALSE;

    HASH_NODE* node = _ht_get_node_trie(table, key);

    if (!node || !node->is_leaf)
        return FALSE;

    if (node->type != HASH_INT) {
        n_log(LOG_ERR, "Can't get key[\"%s\"] of type HASH_INT, key is type %s", key, ht_node_type(node));
        return FALSE;
    }

    (*val) = node->data.ival;

    return TRUE;
} /* _ht_get_int_trie() */

int _ht_get_double_trie(HASH_TABLE* table, const char* key, double* val) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    if (strlen(key) == 0)
        return FALSE;

    HASH_NODE* node = _ht_get_node_trie(table, key);

    if (!node || !node->is_leaf)
        return FALSE;

    if (node->type != HASH_DOUBLE) {
        n_log(LOG_ERR, "Can't get key[\"%s\"] of type HASH_DOUBLE, key is type %s", key, ht_node_type(node));
        return FALSE;
    }

    (*val) = node->data.fval;

    return TRUE;
} /* _ht_get_double_trie() */

int _ht_get_string_trie(HASH_TABLE* table, const char* key, char** val) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    if (strlen(key) == 0)
        return FALSE;

    HASH_NODE* node = _ht_get_node_trie(table, key);

    if (!node || !node->is_leaf)
        return FALSE;

    if (node->type != HASH_STRING) {
        n_log(LOG_ERR, "Can't get key[\"%s\"] of type HASH_STRING, key is type %s", key, ht_node_type(node));
        return FALSE;
    }

    (*val) = node->data.string;

    return TRUE;
} /* _ht_get_string_trie() */

int _ht_get_ptr_trie(HASH_TABLE* table, const char* key, void** val) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    if (strlen(key) == 0)
        return FALSE;

    HASH_NODE* node = _ht_get_node_trie(table, key);

    if (!node || !node->is_leaf)
        return FALSE;

    if (node->type != HASH_PTR) {
        n_log(LOG_ERR, "Can't get key[\"%s\"] of type HASH_PTR , key is type %s", key, ht_node_type(node));
        return FALSE;
    }

    (*val) = node->data.ptr;

    return TRUE;
} /* _ht_get_ptr_trie() */

int _empty_ht_trie(HASH_TABLE* table) {
    __n_assert(table, return FALSE);

    _ht_node_destroy(table->root);

    table->root = _ht_new_node_trie(table, '\0');

    table->nb_keys = 0;
    return TRUE;
} /* _empty_ht_trie */

int _destroy_ht_trie(HASH_TABLE** table) {
    __n_assert(table && (*table), n_log(LOG_ERR, "Can't destroy table: already NULL"); return FALSE);

    _ht_node_destroy((*table)->root);

    Free((*table));

    return TRUE;
} /* _destroy_ht_trie */

void _ht_print_trie_helper(HASH_TABLE* table, HASH_NODE* node) {
    if (!node)
        return;

    if (node->is_leaf) {
        printf("key: %s, val: ", node->key);
        switch (node->type) {
            case HASH_INT:
                printf("int: %ld", (long)node->data.ival);
                break;
            case HASH_DOUBLE:
                printf("double: %f", node->data.fval);
                break;
            case HASH_PTR:
                printf("ptr: %p", node->data.ptr);
                break;
            case HASH_STRING:
                printf("%s", node->data.string);
                break;
            default:
                printf("unknwow type %d", node->type);
                break;
        }
        printf("\n");
    }
    for (size_t it = 0; it < table->alphabet_length; it++) {
        _ht_print_trie_helper(table, node->children[it]);
    }
} /* _ht_print_trie_helper(...) */

void _ht_print_trie(HASH_TABLE* table) {
    __n_assert(table, return);
    __n_assert(table->root, return);

    HASH_NODE* node = table->root;

    _ht_print_trie_helper(table, node);

    return;
} /* _ht_print_trie(...) */

void _ht_search_trie_helper(LIST* results, HASH_NODE* node, int (*node_is_matching)(HASH_NODE* node)) {
    if (!node)
        return;

    if (node->is_leaf) {
        if (node_is_matching(node) == TRUE) {
            list_push(results, strdup(node->key), &free);
        }
    }
    for (size_t it = 0; it < node->alphabet_length; it++) {
        _ht_search_trie_helper(results, node->children[it], node_is_matching);
    }
}

LIST* _ht_search_trie(HASH_TABLE* table, int (*node_is_matching)(HASH_NODE* node)) {
    __n_assert(table, return NULL);

    LIST* results = new_generic_list(MAX_LIST_ITEMS);
    __n_assert(results, return NULL);

    _ht_search_trie_helper(results, table->root, node_is_matching);

    if (results->nb_items < 1)
        list_destroy(&results);

    return results;
} /* _ht_search_trie(...) */

int _ht_depth_first_search(HASH_NODE* node, LIST* results) {
    __n_assert(results, return FALSE);

    if (!node)
        return FALSE;

    for (size_t it = 0; it < node->alphabet_length; it++) {
        _ht_depth_first_search(node->children[it], results);
    }
    if (node->is_leaf) {
        if (results->nb_items < results->nb_max_items) {
            return list_push(results, strdup(node->key), &free);
        }
        return TRUE;
    }
    return TRUE;
} /* _ht_depth_first_search(...) */

/* Classic hash table */
#define ROTL64(x, r) (((x) << (r)) | ((x) >> (64 - (r))))
#define ROTL32(x, r) (((x) << (r)) | ((x) >> (32 - (r))))
#define BIG_CONSTANT(x) (x##LLU)

/* NO-OP for little-endian platforms */
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__)
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define BYTESWAP32(x) (x)
#define BYTESWAP64(x) (x)
#endif
/* if __BYTE_ORDER__ is not predefined (like FreeBSD), use arch */
#elif defined(__i386) || defined(__x86_64) || defined(__alpha) || defined(__vax)

#define BYTESWAP32(x) (x)
#define BYTESWAP64(x) (x)
/* use __builtin_bswap32 if available */
#elif defined(__GNUC__) || defined(__clang__)
#ifdef __has_builtin
#if __has_builtin(__builtin_bswap32)
#define BYTESWAP32(x) __builtin_bswap32(x)
#endif  // __has_builtin(__builtin_bswap32)
#if __has_builtin(__builtin_bswap64)
#define BYTESWAP64(x) __builtin_bswap64(x)
#endif  // __has_builtin(__builtin_bswap64)
#endif  // __has_builtin
#endif  // defined(__GNUC__) || defined(__clang__)
/* last resort (big-endian w/o __builtin_bswap) */
#ifndef BYTESWAP32
#define BYTESWAP32(x) ((((x) & 0xFF) << 24) | (((x) >> 24) & 0xFF) | (((x) & 0x0000FF00) << 8) | (((x) & 0x00FF0000) >> 8))
#endif
#ifndef BYTESWAP64
#define BYTESWAP64(x)                                  \
    (((uint64_t)(x) << 56) |                           \
     (((uint64_t)(x) << 40) & 0X00FF000000000000ULL) | \
     (((uint64_t)(x) << 24) & 0X0000FF0000000000ULL) | \
     (((uint64_t)(x) << 8) & 0X000000FF00000000ULL) |  \
     (((uint64_t)(x) >> 8) & 0X00000000FF000000ULL) |  \
     (((uint64_t)(x) >> 24) & 0X0000000000FF0000ULL) | \
     (((uint64_t)(x) >> 40) & 0X000000000000FF00ULL) | \
     ((uint64_t)(x) >> 56))
#endif

FORCE_INLINE uint32_t getblock32(const uint32_t* p, const size_t i) {
    uint32_t result;
    memcpy(&result, (const uint8_t*)p + i * 4, sizeof(result));
    return BYTESWAP32(result);
}

FORCE_INLINE uint64_t getblock64(const uint64_t* p, const size_t i) {
    uint64_t result;
    memcpy(&result, (const uint8_t*)p + i * 8, sizeof(result));
    return BYTESWAP64(result);
}

FORCE_INLINE uint32_t fmix32(uint32_t h) {
    h ^= h >> 16;
    h *= 0x85ebca6b;
    h ^= h >> 13;
    h *= 0xc2b2ae35;
    h ^= h >> 16;

    return h;
} /* fmix32(...) */

FORCE_INLINE uint64_t fmix64(uint64_t k) {
    k ^= k >> 33;
    k *= BIG_CONSTANT(0xff51afd7ed558ccd);
    k ^= k >> 33;
    k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53);
    k ^= k >> 33;

    return k;
}

// Safe forward-indexing version
void MurmurHash3_x86_32(const void* key, const size_t len, const uint32_t seed, void* out) {
    const uint8_t* data = (const uint8_t*)key;
    const size_t nblocks = len / 4;

    uint32_t h1 = seed;
    const uint32_t c1 = 0xcc9e2d51;
    const uint32_t c2 = 0x1b873593;

    const uint32_t* blocks = (const uint32_t*)data;

    for (size_t i = 0; i < nblocks; i++) {
        uint32_t k1 = getblock32(blocks, i);
        k1 *= c1;
        k1 = ROTL32(k1, 15);
        k1 *= c2;

        h1 ^= k1;
        h1 = ROTL32(h1, 13);
        h1 = h1 * 5 + 0xe6546b64;
    }

    const uint8_t* tail = data + nblocks * 4;
    uint32_t k1 = 0;
    switch (len & 3) {
        case 3:
            k1 ^= (uint32_t)tail[2] << 16; /* fall through */
            FALL_THROUGH;
        case 2:
            k1 ^= (uint32_t)tail[1] << 8; /* fall through */
            FALL_THROUGH;
        case 1:
            k1 ^= (uint32_t)tail[0];
            k1 *= c1;
            k1 = ROTL32(k1, 15);
            k1 *= c2;
            h1 ^= k1;
            break;
        default:
            break;
    }

    h1 ^= (uint32_t)len;
    h1 = fmix32(h1);
    *(uint32_t*)out = h1;
} /* MurmurHash3_x86_32 */

void MurmurHash3_x86_128(const void* key, const size_t len, const uint32_t seed, void* out) {
    const uint8_t* data = (const uint8_t*)key;
    const size_t nblocks = len / 16;

    uint32_t h1 = seed;
    uint32_t h2 = seed;
    uint32_t h3 = seed;
    uint32_t h4 = seed;

    const uint32_t c1 = 0x239b961b;
    const uint32_t c2 = 0xab0e9789;
    const uint32_t c3 = 0x38b34ae5;
    const uint32_t c4 = 0xa1e38b93;

    const uint32_t* blocks = (const uint32_t*)data;

    for (size_t i = 0; i < nblocks; i++) {
        uint32_t k1 = getblock32(blocks, i * 4 + 0);
        uint32_t k2 = getblock32(blocks, i * 4 + 1);
        uint32_t k3 = getblock32(blocks, i * 4 + 2);
        uint32_t k4 = getblock32(blocks, i * 4 + 3);

        k1 *= c1;
        k1 = ROTL32(k1, 15);
        k1 *= c2;
        h1 ^= k1;
        h1 = ROTL32(h1, 19);
        h1 += h2;
        h1 = h1 * 5 + 0x561ccd1b;

        k2 *= c2;
        k2 = ROTL32(k2, 16);
        k2 *= c3;
        h2 ^= k2;
        h2 = ROTL32(h2, 17);
        h2 += h3;
        h2 = h2 * 5 + 0x0bcaa747;

        k3 *= c3;
        k3 = ROTL32(k3, 17);
        k3 *= c4;
        h3 ^= k3;
        h3 = ROTL32(h3, 15);
        h3 += h4;
        h3 = h3 * 5 + 0x96cd1c35;

        k4 *= c4;
        k4 = ROTL32(k4, 18);
        k4 *= c1;
        h4 ^= k4;
        h4 = ROTL32(h4, 13);
        h4 += h1;
        h4 = h4 * 5 + 0x32ac3b17;
    }

    // tail
    const uint8_t* tail = data + nblocks * 16;

    uint32_t k1 = 0, k2 = 0, k3 = 0, k4 = 0;

    switch (len & 15) {
        case 15:
            k4 ^= (uint32_t)tail[14] << 16; /* fall through */
            FALL_THROUGH;
        case 14:
            k4 ^= (uint32_t)tail[13] << 8; /* fall through */
            FALL_THROUGH;
        case 13:
            k4 ^= (uint32_t)tail[12];
            k4 *= c4;
            k4 = ROTL32(k4, 18);
            k4 *= c1;
            h4 ^= k4;
            /* fall through */
            FALL_THROUGH;
        case 12:
            k3 ^= (uint32_t)tail[11] << 24; /* fall through */
            FALL_THROUGH;
        case 11:
            k3 ^= (uint32_t)tail[10] << 16; /* fall through */
            FALL_THROUGH;
        case 10:
            k3 ^= (uint32_t)tail[9] << 8; /* fall through */
            FALL_THROUGH;
        case 9:
            k3 ^= (uint32_t)tail[8];
            k3 *= c3;
            k3 = ROTL32(k3, 17);
            k3 *= c4;
            h3 ^= k3;
            /* fall through */
            FALL_THROUGH;
        case 8:
            k2 ^= (uint32_t)tail[7] << 24; /* fall through */
            FALL_THROUGH;
        case 7:
            k2 ^= (uint32_t)tail[6] << 16; /* fall through */
            FALL_THROUGH;
        case 6:
            k2 ^= (uint32_t)tail[5] << 8; /* fall through */
            FALL_THROUGH;
        case 5:
            k2 ^= (uint32_t)tail[4];
            k2 *= c2;
            k2 = ROTL32(k2, 16);
            k2 *= c3;
            h2 ^= k2;
            /* fall through */
            FALL_THROUGH;
        case 4:
            k1 ^= (uint32_t)tail[3] << 24; /* fall through */
            FALL_THROUGH;
        case 3:
            k1 ^= (uint32_t)tail[2] << 16; /* fall through */
            FALL_THROUGH;
        case 2:
            k1 ^= (uint32_t)tail[1] << 8; /* fall through */
            FALL_THROUGH;
        case 1:
            k1 ^= (uint32_t)tail[0];
            k1 *= c1;
            k1 = ROTL32(k1, 15);
            k1 *= c2;
            h1 ^= k1;
            break;
        default:
            break;
    }

    // finalization
    h1 ^= (uint32_t)len;
    h2 ^= (uint32_t)len;
    h3 ^= (uint32_t)len;
    h4 ^= (uint32_t)len;

    h1 += h2 + h3 + h4;
    h2 += h1;
    h3 += h1;
    h4 += h1;

    h1 = fmix32(h1);
    h2 = fmix32(h2);
    h3 = fmix32(h3);
    h4 = fmix32(h4);

    h1 += h2 + h3 + h4;
    h2 += h1;
    h3 += h1;
    h4 += h1;

    ((uint32_t*)out)[0] = h1;
    ((uint32_t*)out)[1] = h2;
    ((uint32_t*)out)[2] = h3;
    ((uint32_t*)out)[3] = h4;
} /* MurmurHash3_x86_128(...) */

void MurmurHash3_x64_128(const void* key, const size_t len, const uint64_t seed, void* out) {
    const uint8_t* data = (const uint8_t*)key;
    const size_t nblocks = len / 16;

    uint64_t h1 = seed;
    uint64_t h2 = seed;

    const uint64_t c1 = 0x87c37b91114253d5ULL;
    const uint64_t c2 = 0x4cf5ad432745937fULL;

    // Body
    const uint64_t* blocks = (const uint64_t*)data;
    for (size_t i = 0; i < nblocks; i++) {
        uint64_t k1 = getblock64(blocks, i * 2 + 0);
        uint64_t k2 = getblock64(blocks, i * 2 + 1);

        k1 *= c1;
        k1 = ROTL64(k1, 31);
        k1 *= c2;
        h1 ^= k1;

        h1 = ROTL64(h1, 27);
        h1 += h2;
        h1 = h1 * 5 + 0x52dce729;

        k2 *= c2;
        k2 = ROTL64(k2, 33);
        k2 *= c1;
        h2 ^= k2;

        h2 = ROTL64(h2, 31);
        h2 += h1;
        h2 = h2 * 5 + 0x38495ab5;
    }

    // Tail
    const uint8_t* tail = data + nblocks * 16;

    uint64_t k1 = 0;
    uint64_t k2 = 0;

    switch (len & 15) {
        case 15:
            k2 ^= ((uint64_t)tail[14]) << 48; /* fall through */
            FALL_THROUGH;
        case 14:
            k2 ^= ((uint64_t)tail[13]) << 40; /* fall through */
            FALL_THROUGH;
        case 13:
            k2 ^= ((uint64_t)tail[12]) << 32; /* fall through */
            FALL_THROUGH;
        case 12:
            k2 ^= ((uint64_t)tail[11]) << 24; /* fall through */
            FALL_THROUGH;
        case 11:
            k2 ^= ((uint64_t)tail[10]) << 16; /* fall through */
            FALL_THROUGH;
        case 10:
            k2 ^= ((uint64_t)tail[9]) << 8; /* fall through */
            FALL_THROUGH;
        case 9:
            k2 ^= ((uint64_t)tail[8]);
            k2 *= c2;
            k2 = ROTL64(k2, 33);
            k2 *= c1;
            h2 ^= k2;
            /* fall through */
            FALL_THROUGH;
        case 8:
            k1 ^= ((uint64_t)tail[7]) << 56; /* fall through */
            FALL_THROUGH;
        case 7:
            k1 ^= ((uint64_t)tail[6]) << 48; /* fall through */
            FALL_THROUGH;
        case 6:
            k1 ^= ((uint64_t)tail[5]) << 40; /* fall through */
            FALL_THROUGH;
        case 5:
            k1 ^= ((uint64_t)tail[4]) << 32; /* fall through */
            FALL_THROUGH;
        case 4:
            k1 ^= ((uint64_t)tail[3]) << 24; /* fall through */
            FALL_THROUGH;
        case 3:
            k1 ^= ((uint64_t)tail[2]) << 16; /* fall through */
            FALL_THROUGH;
        case 2:
            k1 ^= ((uint64_t)tail[1]) << 8; /* fall through */
            FALL_THROUGH;
        case 1:
            k1 ^= ((uint64_t)tail[0]);
            k1 *= c1;
            k1 = ROTL64(k1, 31);
            k1 *= c2;
            h1 ^= k1;
            break;
        default:
            break;
    }

    // Finalization
    h1 ^= len;
    h2 ^= len;

    h1 += h2;
    h2 += h1;

    h1 = fmix64(h1);
    h2 = fmix64(h2);

    h1 += h2;
    h2 += h1;

    ((uint64_t*)out)[0] = h1;
    ((uint64_t*)out)[1] = h2;
} /* MurmurHash3_x64_128()*/

char* ht_node_type(const HASH_NODE* node) {
    __n_assert(node, return "NULL_NODE");

    switch (node->type) {
        case HASH_INT:
            return "HASH_INT";
        case HASH_DOUBLE:
            return "HASH_DOUBLE";
        case HASH_STRING:
            return "HASH_STRING";
        case HASH_PTR:
            return "HASH_PTR";
        default:
            return "HASH_UNKNOWN";
    }
} /* ht_node_type(...) */

static HASH_VALUE _ht_hash_value(const HASH_TABLE* table, const char* key, size_t key_len) {
    uint8_t digest[16] = {0};
    HASH_VALUE value = 0;

    MurmurHash(key, key_len, table->seed, digest);
    memcpy(&value, digest, sizeof(value));

    return value;
} /* _ht_hash_value */

HASH_NODE* _ht_get_node(HASH_TABLE* table, const char* key) {
    HASH_VALUE hash_value = 0;
    size_t index = 0;

    __n_assert(table, return NULL);
    __n_assert(key, return NULL);

    if (key[0] == '\0')
        return NULL;

    hash_value = _ht_hash_value(table, key, strlen(key));
    index = (hash_value) % (table->size);

    if (!table->hash_table[index]->start)
        return NULL;

    list_foreach(list_node, table->hash_table[index]) {
        HASH_NODE* node_ptr = (HASH_NODE*)list_node->ptr;
        if (!strcmp(key, node_ptr->key)) {
            return node_ptr;
        }
    }
    return NULL;
} /* _ht_get_node() */

HASH_NODE* _ht_new_node(const HASH_TABLE* table, const char* key) {
    __n_assert(table, return NULL);
    __n_assert(key, return NULL);

    HASH_NODE* new_hash_node = NULL;

    HASH_VALUE hash_value = 0;

    if (strlen(key) == 0)
        return NULL;

    hash_value = _ht_hash_value(table, key, strlen(key));

    Malloc(new_hash_node, HASH_NODE, 1);
    __n_assert(new_hash_node, n_log(LOG_ERR, "Could not allocate new_hash_node"); return NULL);
    new_hash_node->key = strdup(key);
    new_hash_node->key_id = '\0';
    __n_assert(new_hash_node->key, n_log(LOG_ERR, "Could not allocate new_hash_node->key"); Free(new_hash_node); return NULL);
    new_hash_node->hash_value = hash_value;
    new_hash_node->data.ptr = NULL;
    new_hash_node->destroy_func = NULL;
    new_hash_node->children = NULL;
    new_hash_node->is_leaf = 0;
    new_hash_node->need_rehash = 0;
    new_hash_node->alphabet_length = 0;

    return new_hash_node;
} /* _ht_new_node */

HASH_NODE* _ht_new_int_node(HASH_TABLE* table, const char* key, HASH_INT_TYPE value) {
    __n_assert(table, return NULL);
    __n_assert(key, return NULL);

    HASH_NODE* new_hash_node = NULL;
    new_hash_node = _ht_new_node(table, key);
    if (new_hash_node) {
        new_hash_node->data.ival = value;
        new_hash_node->type = HASH_INT;
    } else {
        n_log(LOG_ERR, "Could not get a new node in table %p with key %s", table, key);
    }
    return new_hash_node;
} /* _ht_new_int_node */

HASH_NODE* _ht_new_double_node(HASH_TABLE* table, const char* key, double value) {
    __n_assert(table, return NULL);
    __n_assert(key, return NULL);

    HASH_NODE* new_hash_node = NULL;
    new_hash_node = _ht_new_node(table, key);
    if (new_hash_node) {
        new_hash_node->data.fval = value;
        new_hash_node->type = HASH_DOUBLE;
    } else {
        n_log(LOG_ERR, "Could not get a new node in table %p with key %s", table, key);
    }
    return new_hash_node;
} /* _ht_new_double_node */

HASH_NODE* _ht_new_string_node(HASH_TABLE* table, const char* key, const char* value) {
    __n_assert(table, return NULL);
    __n_assert(key, return NULL);

    HASH_NODE* new_hash_node = NULL;
    new_hash_node = _ht_new_node(table, key);
    if (new_hash_node) {
        if (value)
            new_hash_node->data.string = strdup(value);
        else
            new_hash_node->data.string = NULL;
        new_hash_node->type = HASH_STRING;
    } else {
        n_log(LOG_ERR, "Could not get a new node in table %p with key %s", table, key);
    }
    return new_hash_node;
}

HASH_NODE* _ht_new_string_ptr_node(HASH_TABLE* table, const char* key, char* value) {
    __n_assert(table, return NULL);
    __n_assert(key, return NULL);

    HASH_NODE* new_hash_node = NULL;
    new_hash_node = _ht_new_node(table, key);
    if (new_hash_node) {
        new_hash_node->data.string = value;
        new_hash_node->type = HASH_STRING;
    } else {
        n_log(LOG_ERR, "Could not get a new node in table %p with key %s", table, key);
    }
    return new_hash_node;
}

HASH_NODE* _ht_new_ptr_node(HASH_TABLE* table, const char* key, void* value, void (*destructor)(void* ptr), void* (*duplicator)(void* ptr)) {
    __n_assert(table, return NULL);
    __n_assert(key, return NULL);

    HASH_NODE* new_hash_node = NULL;
    new_hash_node = _ht_new_node(table, key);
    if (new_hash_node) {
        new_hash_node->data.ptr = value;
        new_hash_node->destroy_func = destructor;
        new_hash_node->duplicate_func = duplicator;
        new_hash_node->type = HASH_PTR;
    } else {
        n_log(LOG_ERR, "Could not get a new node in table %p with key %s", table, key);
    }
    return new_hash_node;
}

int _ht_put_int(HASH_TABLE* table, const char* key, HASH_INT_TYPE value) {
    HASH_NODE* node_ptr = NULL;

    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);

    if ((node_ptr = _ht_get_node(table, key))) {
        if (node_ptr->type == HASH_INT) {
            node_ptr->data.ival = value;
            return TRUE;
        }
        n_log(LOG_ERR, "Can't add key[\"%s\"] with type HASH_INT, key already exist with type %s", node_ptr->key, ht_node_type(node_ptr));
        return FALSE; /* key registered with another data type */
    }

    int retcode = FALSE;
    node_ptr = _ht_new_int_node(table, key, value);
    if (node_ptr) {
        size_t index = (node_ptr->hash_value) % (table->size);
        retcode = list_push(table->hash_table[index], node_ptr, &_ht_node_destroy);
        if (retcode == TRUE) {
            table->nb_keys++;
        }
    }
    return retcode;
} /*_ht_put_int() */

int _ht_put_double(HASH_TABLE* table, const char* key, double value) {
    HASH_NODE* node_ptr = NULL;

    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);

    if ((node_ptr = _ht_get_node(table, key))) {
        if (node_ptr->type == HASH_DOUBLE) {
            node_ptr->data.fval = value;
            return TRUE;
        }
        n_log(LOG_ERR, "Can't add key[\"%s\"] with type HASH_DOUBLE, key already exist with type %s", node_ptr->key, ht_node_type(node_ptr));
        return FALSE; /* key registered with another data type */
    }

    int retcode = FALSE;
    node_ptr = _ht_new_double_node(table, key, value);
    if (node_ptr) {
        HASH_VALUE index = (node_ptr->hash_value) % (table->size);
        retcode = list_push(table->hash_table[index], node_ptr, &_ht_node_destroy);
        if (retcode == TRUE) {
            table->nb_keys++;
        }
    }
    return retcode;
} /*_ht_put_double()*/

int _ht_put_ptr(HASH_TABLE* table, const char* key, void* ptr, void (*destructor)(void* ptr), void* (*duplicator)(void* ptr)) {
    HASH_NODE* node_ptr = NULL;

    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);

    if ((node_ptr = _ht_get_node(table, key))) {
        /* let's check the key isn't already assigned with another data type */
        if (node_ptr->type == HASH_PTR) {
            /* free the old value if a destructor is set */
            if (node_ptr->destroy_func && node_ptr->data.ptr) {
                node_ptr->destroy_func(node_ptr->data.ptr);
            }
            /* always update the pointer and function pointers */
            node_ptr->data.ptr = ptr;
            node_ptr->destroy_func = destructor;
            node_ptr->duplicate_func = duplicator;
            return TRUE;
        }
        n_log(LOG_ERR, "Can't add key[\"%s\"] with type HASH_PTR , key already exist with type %s", node_ptr->key, ht_node_type(node_ptr));
        return FALSE; /* key registered with another data type */
    }

    int retcode = FALSE;
    node_ptr = _ht_new_ptr_node(table, key, ptr, destructor, duplicator);
    if (node_ptr) {
        HASH_VALUE index = (node_ptr->hash_value) % (table->size);
        retcode = list_push(table->hash_table[index], node_ptr, &_ht_node_destroy);
        if (retcode == TRUE) {
            table->nb_keys++;
        }
    }
    return retcode;
} /* _ht_put_ptr() */

int _ht_put_string(HASH_TABLE* table, const char* key, char* string) {
    HASH_NODE* node_ptr = NULL;

    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);

    if ((node_ptr = _ht_get_node(table, key))) {
        /* let's check the key isn't already assigned with another data type */
        if (node_ptr->type == HASH_STRING) {
            char* new_str = NULL;
            if (string) {
                new_str = strdup(string);
                if (!new_str) {
                    n_log(LOG_ERR, "could not strdup char *string at %p, didn't overwrite %s", string, key);
                    return FALSE;
                }
            }
            Free(node_ptr->data.string);
            node_ptr->data.string = new_str;
            return TRUE;
        }
        n_log(LOG_ERR, "Can't add key[\"%s\"] with type HASH_STRING , key already exist with type %s", node_ptr->key, ht_node_type(node_ptr));
        return FALSE; /* key registered with another data type */
    }

    int retcode = FALSE;
    node_ptr = _ht_new_string_node(table, key, string);
    if (node_ptr) {
        HASH_VALUE index = (node_ptr->hash_value) % (table->size);
        retcode = list_push(table->hash_table[index], node_ptr, &_ht_node_destroy);
        if (retcode == TRUE) {
            table->nb_keys++;
        }
    }
    return retcode;
} /*_ht_put_string */

int _ht_put_string_ptr(HASH_TABLE* table, const char* key, char* string) {
    HASH_NODE* node_ptr = NULL;

    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);

    if ((node_ptr = _ht_get_node(table, key))) {
        /* let's check the key isn't already assigned with another data type */
        if (node_ptr->type == HASH_STRING) {
            Free(node_ptr->data.string);
            node_ptr->data.string = string;
            return TRUE;
        }
        n_log(LOG_ERR, "Can't add key[\"%s\"] with type HASH_STRING , key already exist with type %s", node_ptr->key, ht_node_type(node_ptr));
        return FALSE; /* key registered with another data type */
    }

    int retcode = FALSE;
    node_ptr = _ht_new_string_ptr_node(table, key, string);
    if (node_ptr) {
        HASH_VALUE index = (node_ptr->hash_value) % (table->size);
        retcode = list_push(table->hash_table[index], node_ptr, &_ht_node_destroy);
        if (retcode == TRUE) {
            table->nb_keys++;
        }
    }
    return retcode;
} /*_ht_put_string_ptr */

int _ht_get_int(HASH_TABLE* table, const char* key, HASH_INT_TYPE* val) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    if (strlen(key) == 0)
        return FALSE;

    HASH_NODE* node = _ht_get_node(table, key);

    if (!node)
        return FALSE;

    if (node->type != HASH_INT) {
        n_log(LOG_ERR, "Can't get key[\"%s\"] of type HASH_INT, key is type %s", key, ht_node_type(node));
        return FALSE;
    }

    (*val) = node->data.ival;

    return TRUE;
} /* _ht_get_int() */

int _ht_get_double(HASH_TABLE* table, const char* key, double* val) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);

    if (strlen(key) == 0)
        return FALSE;

    HASH_NODE* node = _ht_get_node(table, key);

    if (!node)
        return FALSE;

    if (node->type != HASH_DOUBLE) {
        n_log(LOG_ERR, "Can't get key[\"%s\"] of type HASH_DOUBLE, key is type %s", key, ht_node_type(node));
        return FALSE;
    }

    (*val) = node->data.fval;

    return TRUE;
} /* _ht_get_double()*/

int _ht_get_ptr(HASH_TABLE* table, const char* key, void** val) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    if (strlen(key) == 0)
        return FALSE;

    HASH_NODE* node = _ht_get_node(table, key);
    if (!node)
        return FALSE;

    if (node->type != HASH_PTR) {
        n_log(LOG_ERR, "Can't get key[\"%s\"] of type HASH_PTR, key is type %s", key, ht_node_type(node));
        return FALSE;
    }

    (*val) = node->data.ptr;

    return TRUE;
} /* _ht_get_ptr() */

int _ht_get_string(HASH_TABLE* table, const char* key, char** val) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    if (strlen(key) == 0)
        return FALSE;

    HASH_NODE* node = _ht_get_node(table, key);
    if (!node)
        return FALSE;

    if (node->type != HASH_STRING) {
        n_log(LOG_ERR, "Can't get key[\"%s\"] of type HASH_STRING, key is type %s", key, ht_node_type(node));
        return FALSE;
    }

    (*val) = node->data.string;

    return TRUE;
} /* _ht_get_string() */

int _ht_remove(HASH_TABLE* table, const char* key) {
    HASH_VALUE hash_value = 0;
    size_t index = 0;

    HASH_NODE* node_ptr = NULL;
    LIST_NODE* node_to_kill = NULL;

    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    if (strlen(key) == 0)
        return FALSE;

    hash_value = _ht_hash_value(table, key, strlen(key));
    index = (hash_value) % (table->size);

    if (!table->hash_table[index]->start) {
        n_log(LOG_ERR, "Can't remove key[\"%s\"], table is empty", key);
        return FALSE;
    }

    list_foreach(list_node, table->hash_table[index]) {
        node_ptr = (HASH_NODE*)list_node->ptr;
        /* if we found the same */
        if (!strcmp(key, node_ptr->key)) {
            node_to_kill = list_node;
            break;
        }
    }
    if (node_to_kill) {
        node_ptr = remove_list_node(table->hash_table[index], node_to_kill, HASH_NODE);
        _ht_node_destroy(node_ptr);

        table->nb_keys--;

        return TRUE;
    }
    n_log(LOG_ERR, "Can't delete key[\"%s\"]: inexisting key", key);
    return FALSE;
} /* ht_remove() */

int _empty_ht(HASH_TABLE* table) {
    __n_assert(table, return FALSE);

    HASH_VALUE index = 0;
    for (index = 0; index < table->size; index++) {
        while (table->hash_table[index] && table->hash_table[index]->start) {
            HASH_NODE* hash_node = remove_list_node(table->hash_table[index], table->hash_table[index]->start, HASH_NODE);
            _ht_node_destroy(hash_node);
        }
    }
    table->nb_keys = 0;
    return TRUE;
} /* empty_ht */

int _destroy_ht(HASH_TABLE** table) {
    __n_assert(table && (*table), n_log(LOG_ERR, "Can't destroy table: already NULL"); return FALSE);

    if ((*table)->hash_table) {
        // empty_ht( (*table) );

        HASH_VALUE it = 0;
        for (it = 0; it < (*table)->size; it++) {
            if ((*table)->hash_table[it])
                list_destroy(&(*table)->hash_table[it]);
        }
        Free((*table)->hash_table);
    }
    Free((*table));
    return TRUE;
} /* _destroy_ht */

void _ht_print(HASH_TABLE* table) {
    __n_assert(table, return);
    __n_assert(table->hash_table, return);
    // cppcheck-suppress constVariablePointer ; node is generated by ht_foreach macro
    ht_foreach(node, table) {
        const HASH_NODE* ht_node = (const HASH_NODE*)node->ptr;
        printf("key:%s node:%s\n", ht_node->key, ht_node->key);
    }

    return;
} /* _ht_print(...) */

LIST* _ht_search(HASH_TABLE* table, int (*node_is_matching)(HASH_NODE* node)) {
    __n_assert(table, return NULL);

    LIST* results = new_generic_list(MAX_LIST_ITEMS);
    __n_assert(results, return NULL);

    ht_foreach(node, table) {
        HASH_NODE* hnode = (HASH_NODE*)node->ptr;
        if (node_is_matching(hnode) == TRUE) {
            list_push(results, strdup(hnode->key), &free);
        }
    }

    if (results->nb_items < 1)
        list_destroy(&results);

    return results;
} /* _ht_search(...) */

/* Hash tables function pointers and common table type functions */

HASH_TABLE* new_ht_trie(size_t alphabet_length, size_t alphabet_offset) {
    HASH_TABLE* table = NULL;

    Malloc(table, HASH_TABLE, 1);
    __n_assert(table, n_log(LOG_ERR, "Error allocating HASH_TABLE *table"); return NULL);

    table->size = 0;
    table->seed = 0;
    table->nb_keys = 0;
    errno = 0;
    table->hash_table = NULL;

    table->ht_put_int = _ht_put_int_trie;
    table->ht_put_double = _ht_put_double_trie;
    table->ht_put_ptr = _ht_put_ptr_trie;
    table->ht_put_string = _ht_put_string_trie;
    table->ht_put_string_ptr = _ht_put_string_ptr_trie;
    table->ht_get_int = _ht_get_int_trie;
    table->ht_get_double = _ht_get_double_trie;
    table->ht_get_string = _ht_get_string_trie;
    table->ht_get_ptr = _ht_get_ptr_trie;
    table->ht_remove = _ht_remove_trie;
    table->ht_search = _ht_search_trie;
    table->empty_ht = _empty_ht_trie;
    table->destroy_ht = _destroy_ht_trie;
    table->ht_print = _ht_print_trie;

    table->alphabet_length = alphabet_length;
    table->alphabet_offset = alphabet_offset;

    table->root = _ht_new_node_trie(table, '\0');
    if (!table->root) {
        n_log(LOG_ERR, "Couldn't allocate new_ht_trie with alphabet_length of %zu and alphabet offset of %zu", alphabet_length, alphabet_offset);
        Free(table);
        return NULL;
    }
    table->mode = HASH_TRIE;

    return table;
} /* new_ht_trie */

/* Per-process random key for the table seeds, read once from the OS: a
 * hash table fed attacker-chosen keys (form fields, headers, cookies) must
 * not use a seed the attacker can predict, or colliding keys can be
 * precomputed to turn every lookup into a list walk (hash flooding). The
 * old seed was rand() % 100000 with rand() never seeded: the same for
 * every process. */
static uint64_t _ht_process_key = 0;
static uint64_t _ht_table_counter = 0;
static pthread_once_t _ht_key_once = PTHREAD_ONCE_INIT;
static pthread_mutex_t _ht_counter_lock = PTHREAD_MUTEX_INITIALIZER;

static void _ht_key_init(void) {
    uint64_t k = 0;
    int got = 0;
#if defined(__windows__)
    got = BCryptGenRandom(NULL, (PUCHAR)&k, (ULONG)sizeof(k), BCRYPT_USE_SYSTEM_PREFERRED_RNG) == 0;
#else
#ifdef N_HASH_HAVE_GETRANDOM
    got = getrandom(&k, sizeof(k), GRND_NONBLOCK) == (ssize_t)sizeof(k);
#endif
    /* cppcheck does not define __GLIBC__, so it never sees the getrandom()
     * branch above that can set got */
    // cppcheck-suppress knownConditionTrueFalse ; got is set by getrandom() on glibc
    if (!got) {
        int fd = open("/dev/urandom", O_RDONLY);
        if (fd >= 0) {
            got = read(fd, &k, sizeof(k)) == (ssize_t)sizeof(k);
            close(fd);
        }
    }
#endif
    if (!got) {
        /* last resort: time and addresses, at least different per run */
        uint64_t t = (uint64_t)time(NULL), c = (uint64_t)clock();
        k = t * 0x9E3779B97F4A7C15ULL ^ (c << 21) ^ (uint64_t)(uintptr_t)&k ^ (uint64_t)(uintptr_t)&_ht_process_key;
    }
    _ht_process_key = k;
}

/* splitmix64: spread a counter into well-mixed 64-bit values. */
static uint64_t _ht_mix(uint64_t x) {
    x += 0x9E3779B97F4A7C15ULL;
    x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9ULL;
    x = (x ^ (x >> 27)) * 0x94D049BB133111EBULL;
    return x ^ (x >> 31);
}

/* A fresh, unpredictable seed for one table. */
static size_t _ht_new_seed(void) {
    uint64_t n;
    pthread_once(&_ht_key_once, _ht_key_init);
    pthread_mutex_lock(&_ht_counter_lock);
    n = ++_ht_table_counter;
    pthread_mutex_unlock(&_ht_counter_lock);
    return (size_t)_ht_mix(_ht_process_key ^ _ht_mix(n));
}

HASH_TABLE* new_ht(size_t size) {
    HASH_TABLE* table = NULL;

    if (size < 1) {
        n_log(LOG_ERR, "Invalid size %zu for new_ht()", size);
        return NULL;
    }
    Malloc(table, HASH_TABLE, 1);
    __n_assert(table, n_log(LOG_ERR, "Error allocating HASH_TABLE *table"); return NULL);

    table->size = size;
    table->seed = _ht_new_seed();
    table->nb_keys = 0;
    errno = 0;
    Malloc(table->hash_table, LIST*, size);
    // table -> hash_table = (LIST **)calloc( size, sizeof( LIST *) );
    __n_assert(table->hash_table, n_log(LOG_ERR, "Can't allocate table -> hash_table with size %zu !", size); Free(table); return NULL);

    size_t it = 0;
    for (it = 0; it < size; it++) {
        table->hash_table[it] = new_generic_list(MAX_LIST_ITEMS);
        // if no valid table then unroll previsouly created slots
        if (!table->hash_table[it]) {
            n_log(LOG_ERR, "Can't allocate table -> hash_table[ %zu ] !", it);
            size_t it_delete = 0;
            for (it_delete = 0; it_delete < it; it_delete++) {
                list_destroy(&table->hash_table[it_delete]);
            }
            Free(table->hash_table);
            Free(table);
            return NULL;
        }
    }
    table->mode = HASH_CLASSIC;

    table->ht_put_int = _ht_put_int;
    table->ht_put_double = _ht_put_double;
    table->ht_put_ptr = _ht_put_ptr;
    table->ht_put_string = _ht_put_string;
    table->ht_put_string_ptr = _ht_put_string_ptr;
    table->ht_get_int = _ht_get_int;
    table->ht_get_double = _ht_get_double;
    table->ht_get_string = _ht_get_string;
    table->ht_get_ptr = _ht_get_ptr;
    table->ht_get_node = _ht_get_node;
    table->ht_remove = _ht_remove;
    table->ht_search = _ht_search;
    table->empty_ht = _empty_ht;
    table->destroy_ht = _destroy_ht;
    table->ht_print = _ht_print;

    return table;
} /* new_ht(...) */

HASH_NODE* ht_get_node(HASH_TABLE* table, const char* key) {
    __n_assert(table, return NULL);
    __n_assert(key, return NULL);
    return table->ht_get_node(table, key);
} /*ht_get_node(...) */

int ht_get_double(HASH_TABLE* table, const char* key, double* val) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    return table->ht_get_double(table, key, val);
} /* ht_get_double(...) */

int ht_get_int(HASH_TABLE* table, const char* key, HASH_INT_TYPE* val) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    return table->ht_get_int(table, key, val);
} /* ht_get_int(...) */

int ht_get_ptr(HASH_TABLE* table, const char* key, void** val) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    return table->ht_get_ptr(table, key, val);
} /* ht_get_ptr(...) */

int ht_get_string(HASH_TABLE* table, const char* key, char** val) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    return table->ht_get_string(table, key, val);
} /* ht_get_string(...) */

int ht_put_double(HASH_TABLE* table, const char* key, double value) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    return table->ht_put_double(table, key, value);
} /* ht_put_double(...) */

int ht_put_int(HASH_TABLE* table, const char* key, HASH_INT_TYPE value) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    return table->ht_put_int(table, key, value);
} /* ht_put_int(...) */

int ht_put_ptr(HASH_TABLE* table, const char* key, void* ptr, void (*destructor)(void* ptr), void* (*duplicator)(void* ptr)) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    return table->ht_put_ptr(table, key, ptr, destructor, duplicator);
} /* ht_put_ptr(...) */

int ht_put_string(HASH_TABLE* table, const char* key, char* string) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    return table->ht_put_string(table, key, string);
} /* ht_put_string(...) */

int ht_put_string_ptr(HASH_TABLE* table, const char* key, char* string) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    return table->ht_put_string_ptr(table, key, string);
} /* ht_put_string_ptr(...) */

int ht_remove(HASH_TABLE* table, const char* key) {
    __n_assert(table, return FALSE);
    __n_assert(key, return FALSE);
    return table->ht_remove(table, key);
} /* ht_remove(...) */

void ht_print(HASH_TABLE* table) {
    __n_assert(table, return);
    table->ht_print(table);
    return;
} /* ht_print(...) */

LIST* ht_search(HASH_TABLE* table, int (*node_is_matching)(HASH_NODE* node)) {
    __n_assert(table, return NULL);
    return table->ht_search(table, node_is_matching);
} /* ht_search(...) */

int empty_ht(HASH_TABLE* table) {
    __n_assert(table, return FALSE);
    return table->empty_ht(table);
} /* empty_ht(...) */

int destroy_ht(HASH_TABLE** table) {
    __n_assert((*table), return FALSE);
    return (*table)->destroy_ht(table);
} /* destroy_ht(...) */

HASH_NODE* ht_get_node_ex(HASH_TABLE* table, HASH_VALUE hash_value) {
    __n_assert(table, return NULL);
    __n_assert(table->mode == HASH_CLASSIC, return NULL);

    size_t index = (hash_value) % (table->size);
    if (!table->hash_table[index]->start) {
        return NULL;
    }

    list_foreach(list_node, table->hash_table[index]) {
        HASH_NODE* node_ptr = (HASH_NODE*)list_node->ptr;
        if (hash_value == node_ptr->hash_value) {
            return node_ptr;
        }
    }
    return NULL;
} /* ht_get_node_ex() */

int ht_get_ptr_ex(HASH_TABLE* table, HASH_VALUE hash_value, void** val) {
    __n_assert(table, return FALSE);
    __n_assert(table->mode == HASH_CLASSIC, return FALSE);

    HASH_NODE* node = ht_get_node_ex(table, hash_value);
    if (!node)
        return FALSE;

    if (node->type != HASH_PTR) {
        n_log(LOG_ERR, "Can't get key[\"%zu\"] of type HASH_PTR, key is type %s", hash_value, ht_node_type(node));
        return FALSE;
    }

    (*val) = node->data.ptr;

    return TRUE;
} /* ht_get_ptr_ex() */

int ht_put_ptr_ex(HASH_TABLE* table, HASH_VALUE hash_value, void* val, void (*destructor)(void* ptr), void* (*duplicator)(void* ptr)) {
    __n_assert(table, return FALSE);
    __n_assert(table->mode == HASH_CLASSIC, return FALSE);

    size_t index = 0;
    HASH_NODE* new_hash_node = NULL;
    HASH_NODE* node_ptr = NULL;

    index = (hash_value) % (table->size);

    /* we have some nodes here. Let's check if the key already exists */
    list_foreach(list_node, table->hash_table[index]) {
        node_ptr = (HASH_NODE*)list_node->ptr;
        /* if we found the same key we just replace the value and return */
        if (hash_value == node_ptr->hash_value) {
            /* let's check the key isn't already assigned with another data type */
            if (node_ptr->type == HASH_PTR) {
                if (node_ptr->destroy_func && node_ptr->data.ptr) {
                    node_ptr->destroy_func(node_ptr->data.ptr);
                } else if (!node_ptr->destroy_func && node_ptr->data.ptr) {
                    n_log(LOG_ERR, "Can't free previous key[\"%s\"] with type HASH_PTR , no hash node destroy func", node_ptr->key);
                }
                node_ptr->destroy_func = destructor;
                node_ptr->duplicate_func = duplicator;
                node_ptr->data.ptr = val;
                return TRUE;
            }
            n_log(LOG_ERR, "Can't add key[\"%s\"] with type HASH_PTR , key already exist with type %s", node_ptr->key, ht_node_type(node_ptr));
            return FALSE; /* key registered with another data type */
        }
    }

    Malloc(new_hash_node, HASH_NODE, 1);
    __n_assert(new_hash_node, n_log(LOG_ERR, "Could not allocate new_hash_node"); return FALSE);

    new_hash_node->key = NULL;
    new_hash_node->hash_value = hash_value;
    new_hash_node->data.ptr = val;
    new_hash_node->type = HASH_PTR;
    new_hash_node->destroy_func = destructor;
    new_hash_node->duplicate_func = duplicator;

    table->nb_keys++;

    return list_push(table->hash_table[index], new_hash_node, &_ht_node_destroy);
} /* ht_put_ptr_ex() */

int ht_remove_ex(HASH_TABLE* table, HASH_VALUE hash_value) {
    __n_assert(table, return FALSE);
    __n_assert(table->mode == HASH_CLASSIC, return FALSE);

    size_t index = 0;
    HASH_NODE* node_ptr = NULL;
    LIST_NODE* node_to_kill = NULL;

    index = (hash_value) % (table->size);
    if (!table->hash_table[index]->start) {
        n_log(LOG_ERR, "Can't remove key[\"%zu\"], table is empty", hash_value);
        return FALSE;
    }

    list_foreach(list_node, table->hash_table[index]) {
        node_ptr = (HASH_NODE*)list_node->ptr;
        /* if we found the same */
        if (hash_value == node_ptr->hash_value) {
            node_to_kill = list_node;
            break;
        }
    }
    if (node_to_kill) {
        node_ptr = remove_list_node(table->hash_table[index], node_to_kill, HASH_NODE);
        _ht_node_destroy(node_ptr);

        table->nb_keys--;

        return TRUE;
    }
    n_log(LOG_ERR, "Can't delete key[\"%zu\"]: inexisting key", hash_value);
    return FALSE;
} /* ht_remove_ex() */

LIST* ht_get_completion_list(HASH_TABLE* table, const char* keybud, size_t max_results) {
    __n_assert(table, return NULL);
    __n_assert(keybud, return NULL);

    LIST* results = new_generic_list(max_results);
    if (table->mode == HASH_TRIE) {
        HASH_NODE* node = _ht_get_node_trie(table, keybud);
        if (node) {
            if (list_push(results, strdup(keybud), &free) == TRUE) {
                _ht_depth_first_search(node, results);
            }
        } else {
            node = table->root;
            for (size_t it = 0; it < table->alphabet_length; it++) {
                if (node->children[it]) {
                    char new_keybud[3] = "";
                    new_keybud[0] = (char)(it + table->alphabet_offset);
                    list_push(results, strdup(new_keybud), &free);
                }
            }
        }
    } else if (table->mode == HASH_CLASSIC) {
        ht_foreach(node, table) {
            HASH_NODE* hnode = (HASH_NODE*)node->ptr;
            if (strncasecmp(keybud, hnode->key, strlen(keybud)) == 0) {
                char* key = strdup(hnode->key);
                if (list_push(results, key, &free) == FALSE) {
                    n_log(LOG_ERR, "not enough space in list or memory error, key %s not pushed !", key);
                    Free(key);
                }
            }
        }
    } else {
        n_log(LOG_ERR, "unsupported mode %d", table->mode);
        list_destroy(&results);
        return NULL;
    }
    if (results && results->nb_items < 1)
        list_destroy(&results);
    return results;
} /* ht_get_completion_list(...) */

int is_prime(size_t nb) {
    /* quick test for first primes */
    if (nb <= 1) return FALSE;
    if (nb <= 3) return TRUE;

    /* skip middle five numbers in below loop */
    if ((nb % 2 == 0) || (nb % 3 == 0))
        return FALSE;

    /* looping */
    for (size_t it = 5; it * it <= nb; it = it + 6) {
        if ((nb % it == 0) || (nb % (it + 2) == 0))
            return FALSE;
    }
    return TRUE;
} /*  is_prime() */

size_t next_prime(size_t nb) {
    if (nb <= 1)
        return 2;

    size_t next_prime = nb;
    do {
        next_prime++;
    } while (is_prime(next_prime) == FALSE);

    return next_prime;
} /* next_prime() */

int ht_get_table_collision_percentage(HASH_TABLE* table) {
    __n_assert(table, return FALSE);
    if (table->mode != HASH_CLASSIC) {
        n_log(LOG_ERR, "unsupported table->mode (%d instead or %d)", table->mode, HASH_CLASSIC);
        return FALSE;
    }
    if (table->size == 0) return FALSE;

    size_t nb_collisionned_lists = 0;

    for (size_t hash_it = 0; hash_it < table->size; hash_it++) {
        if (table->hash_table[hash_it] && table->hash_table[hash_it]->nb_items > 1) {
            nb_collisionned_lists++;
        }
    }
    size_t collision_percentage = (100 * nb_collisionned_lists) / table->size;
    return (int)collision_percentage;
} /* ht_get_table_collision_percentage() */

size_t ht_get_optimal_size(HASH_TABLE* table) {
    __n_assert(table, return 0);
    if (table->mode != HASH_CLASSIC) {
        n_log(LOG_ERR, "unsupported table->mode (%d instead or %d)", table->mode, HASH_CLASSIC);
        return FALSE;
    }

    size_t optimum_size = (size_t)((double)table->nb_keys * 1.3);
    if (is_prime(optimum_size) != TRUE)
        optimum_size = next_prime(optimum_size);
    return optimum_size;
} /* ht_get_optimal_size() */

int ht_resize(HASH_TABLE** table, size_t size) {
    __n_assert((*table), return FALSE);
    if ((*table)->mode != HASH_CLASSIC) {
        n_log(LOG_ERR, "unsupported table->mode (%d instead or %d)", (*table)->mode, HASH_CLASSIC);
        return FALSE;
    }
    if (size < 1) {
        n_log(LOG_ERR, "invalid size %zu for hash table %p", size, (void*)(*table));
        return FALSE;
    }
    /* mark every existing entry for rehash before resizing.
     * Using ht_foreach (plain for-loop macro) instead of HT_FOREACH
     * because the body needs no break/continue, and HT_FOREACH's
     * GCC nested-function expansion is rejected by clang's parser. */
    ht_foreach(it_node, (*table)) {
        HASH_NODE* node = (HASH_NODE*)it_node->ptr;
        node->need_rehash = 1;
    }

    if (size > (*table)->size) {
        if (Realloc((*table)->hash_table, LIST*, size) == FALSE) {
            return FALSE;
        }
        for (size_t it = (*table)->size; it < size; it++) {
            (*table)->hash_table[it] = new_generic_list(MAX_LIST_ITEMS);
            if (!(*table)->hash_table[it]) {
                n_log(LOG_ERR, "Can't allocate table -> hash_table[ %zu ] !", it);
                /* destroy newly added slots and shrink back */
                for (size_t it_delete = (*table)->size; it_delete < it; it_delete++) {
                    list_destroy(&(*table)->hash_table[it_delete]);
                }
                /* best-effort rollback: shrink back to original size.
                   Realloc preserves the pointer on failure, which is fine here. */
                Realloc((*table)->hash_table, LIST*, (*table)->size);
                return FALSE;
            }
        }
        /* rehash all marked nodes into their new positions */
        for (size_t it = 0; it < size; it++) {
            if ((*table)->hash_table[it]) {
                while ((*table)->hash_table[it]->start) {
                    HASH_NODE* hash_node = (HASH_NODE*)(*table)->hash_table[it]->start->ptr;
                    if (hash_node->need_rehash == 0)
                        break;
                    hash_node->need_rehash = 0;
                    LIST_NODE* node = list_node_shift((*table)->hash_table[it]);
                    node->next = node->prev = NULL;
                    size_t index = (hash_node->hash_value) % (size);
                    list_node_push((*table)->hash_table[index], node);
                }
            }
        }
    } else {
        /* rehash nodes that need it into the smaller index space */
        for (size_t it = 0; it < (*table)->size; it++) {
            if ((*table)->hash_table[it]) {
                while ((*table)->hash_table[it]->start) {
                    HASH_NODE* hash_node = (HASH_NODE*)(*table)->hash_table[it]->start->ptr;
                    if (hash_node->need_rehash == 0)
                        break;
                    hash_node->need_rehash = 0;
                    LIST_NODE* node = list_node_shift((*table)->hash_table[it]);
                    node->next = node->prev = NULL;
                    size_t index = (hash_node->hash_value) % (size);
                    list_node_push((*table)->hash_table[index], node);
                }
            }
        }
        /* destroy trailing slots that are now unused */
        for (size_t it = size; it < (*table)->size; it++) {
            list_destroy(&(*table)->hash_table[it]);
        }
        /* shrink the backing array; Realloc preserves the pointer on failure.
           Trailing slots have already been list_destroy'd so they are NULL.
           (*table)->size is updated below regardless, keeping the table
           consistent even if the allocator cannot shrink in-place. */
        Realloc((*table)->hash_table, LIST*, size);
    }
    (*table)->size = size;

    return TRUE;
} /* ht_resize() */

int ht_optimize(HASH_TABLE** table) {
    __n_assert((*table), return FALSE);
    if ((*table)->mode != HASH_CLASSIC) {
        n_log(LOG_ERR, "unsupported table->mode (%d instead or %d)", (*table)->mode, HASH_CLASSIC);
        return FALSE;
    }

    size_t optimal_size = ht_get_optimal_size((*table));
    if (optimal_size == FALSE) {
        return FALSE;
    }

    int collision_percentage = ht_get_table_collision_percentage((*table));
    if (collision_percentage == FALSE)
        return FALSE;

    int resize_result = ht_resize(table, optimal_size);
    if (resize_result == FALSE) {
        return FALSE;
    }

    collision_percentage = ht_get_table_collision_percentage((*table));
    if (collision_percentage == FALSE) {
        return FALSE;
    }

    return TRUE;
} /* ht_optimize() */

HASH_TABLE* ht_duplicate(HASH_TABLE* table) {
    __n_assert(table, return NULL);
    HASH_TABLE* duplicated_table = NULL;

    if (table->mode != HASH_CLASSIC) {
        n_log(LOG_ERR, "unsupported mode %d for table %p", table->mode, table);
        return NULL;
    }

    duplicated_table = new_ht(table->size);
    if (!duplicated_table) {
        n_log(LOG_ERR, "couldn't allocate duplicated table of %zu elements", table->size);
        return NULL;
    }

    ht_foreach(node, table) {
        HASH_NODE* hash_node = (HASH_NODE*)node->ptr;
        int has_succeeded = TRUE;
        switch (hash_node->type) {
            case HASH_INT:
                has_succeeded = ht_put_int(duplicated_table, hash_node->key, hash_node->data.ival);
                break;
            case HASH_DOUBLE:
                has_succeeded = ht_put_double(duplicated_table, hash_node->key, hash_node->data.fval);
                break;
            case HASH_PTR: {
                if (hash_node->duplicate_func && hash_node->data.ptr) {
                    /* deep copy: new entry takes ownership with the same funcs */
                    void* duplicated_ptr = hash_node->duplicate_func(hash_node->data.ptr);
                    if (!duplicated_ptr) {
                        n_log(LOG_ERR, "duplicate_func returned NULL for key [%s]", hash_node->key);
                        has_succeeded = FALSE;
                        break;
                    }
                    has_succeeded = ht_put_ptr(duplicated_table, hash_node->key, duplicated_ptr, hash_node->destroy_func, hash_node->duplicate_func);
                } else {
                    /* shallow copy: pass NULL destroy/duplicate so the duplicate table
                       does not take ownership; only the source table will free the pointer */
                    has_succeeded = ht_put_ptr(duplicated_table, hash_node->key, hash_node->data.ptr, NULL, NULL);
                }
            } break;
            case HASH_STRING:
                has_succeeded = ht_put_string(duplicated_table, hash_node->key, hash_node->data.string);
                break;
            default:
                n_log(LOG_ERR, "unknown node type %d for key [%s], skipping", hash_node->type, hash_node->key);
                break;
        }

        if (has_succeeded == FALSE) {
            n_log(LOG_ERR, "problem when trying to duplicate value in %p, duplication cancelled", table);
            destroy_ht(&duplicated_table);
            return NULL;
        }
    }

    return duplicated_table;
}