
#pragma once

#ifndef hash_h_
#define hash_h_

#include "libdivide.h"

#ifdef __cplusplus
extern "C" {
#endif

// Hash functions
#define DJB_MAGIC 5381u

typedef struct hashitem  // size is 12/24 bytes.
{
    char *string;
    intptr_t key;
    struct hashitem *next;
} hashitem_t;

typedef struct
{
    uint32_t v;
    libdivide::libdivide_u32_t d;
} hashprime_t;

typedef struct
{
    union
    {
        uint32_t    size;
        hashprime_t prime;
    };
    hashitem_t **items;
} hashtable_t;

// djb3 algorithm
static inline uint32_t hash_getcode(const char *s)
{
    uint32_t h = DJB_MAGIC;
    char ch;

    while ((ch = Btolower(*s++)) != '\0')
        h = ((h << 5) + h) ^ ch;

    return h;
}

static FORCE_INLINE uint32_t hash_getbucket(hashtable_t const *t, const char *s)
{
    uint32_t const h = hash_getcode(s);
    return h - libdivide::libdivide_u32_do(h, &t->prime.d) * t->prime.v;
}

void hash_init(hashtable_t *t);
void hash_loop(hashtable_t *t, void (*func)(const char *, intptr_t));
void hash_free(hashtable_t *t);
void hash_add(hashtable_t *t, const char *s, intptr_t key, int32_t replace);
void hash_delete(hashtable_t *t, const char *s);

intptr_t hash_findcase(hashtable_t const *t, char const *s);
intptr_t hash_find(hashtable_t const *t, char const *s);

// Hash functions
// modified for raw binary keys and one big allocation, and maximum find() performance

typedef struct inthashitem
{
    intptr_t key;
    intptr_t value;
    struct inthashitem *collision; // use NULL to signify empty and pointer identity to signify end of linked list
} inthashitem_t;

typedef struct
{
    inthashitem_t *items;
    union
    {
        uint32_t    count;
        hashprime_t prime;
    };
} inthashtable_t;

// djb3 algorithm
static inline uint32_t inthash_getcode(intptr_t key)
{
    uint32_t h = DJB_MAGIC;

    for (auto keybuf = (uint8_t const *) &key, keybuf_end = keybuf + sizeof(key); keybuf < keybuf_end; ++keybuf)
        h = ((h << 5) + h) ^ (uint32_t) *keybuf;

    return h;
}

static FORCE_INLINE uint32_t inthash_getbucket(inthashtable_t const *t, intptr_t key)
{
    uint32_t const h = inthash_getcode(key);
    return h - libdivide::libdivide_u32_do(h, &t->prime.d) * t->prime.v;
}

void inthash_init(inthashtable_t *t);
void inthash_loop(inthashtable_t const *t, void (*func)(intptr_t, intptr_t));
void inthash_free(inthashtable_t *t);
void inthash_add(inthashtable_t *t, intptr_t key, intptr_t value, int32_t replace);
void inthash_delete(inthashtable_t *t, intptr_t key);

intptr_t inthash_find(inthashtable_t const *t, intptr_t key);

// keep the load factor below 0.75 and make sure the size is odd
// ideally we would find the next largest prime number
#define INTHASH_SIZE(size) ((size * 4u / 3u) | 1u)

#ifdef __cplusplus
}
#endif

#endif // hash_h_
