#ifndef HSHTB_H_
#define HSHTB_H_

#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include "hal/hal.h"
#include "util/util.h"

#define HSHTB_FNV32_OFF 0x811c9dc5u
#define HSHTB_FNV32_PRIME 0x01000193u

static inline uint32_t hshtb_fnv32(const void *data, size_t len) {
  const unsigned char *p = (const unsigned char *)data;
  uint32_t h = HSHTB_FNV32_OFF;
  for (size_t i = 0; i < len; i++) {
    h ^= p[i];
    h *= HSHTB_FNV32_PRIME;
  }
  return h;
}

enum {
  HSHTB_EMPTY = 0,
  HSHTB_TAKEN = 1,
  HSHTB_TOMB = 2,
};

#define __HSHTB_ARRAY_LEN(tb) \
 ((void)sizeof(struct { int _[!!(sizeof(tb) % sizeof((tb)[0]) == 0)]; }) , (sizeof(tb) / sizeof((tb)[0])))

#define HSHTB_ALLOC(tb, keyfield, k, out) \
  do { \
    size_t __len = __HSHTB_ARRAY_LEN(tb); \
    uint32_t __h = hshtb_fnv32((k), hal_strlen((k))); \
    size_t __idx = __h % __len; \
    size_t __start = __idx; \
    typeof(&(tb)[0]) __tomb = NULL; \
    (out) = NULL; \
    do { \
      if ((tb)[__idx]._hshtbstate == HSHTB_EMPTY) { \
        typeof(&(tb)[0]) __slot = __tomb ? __tomb : &(tb)[__idx]; \
        hal_memset(__slot, 0, sizeof(*__slot)); \
        __slot->_hshtbstate = HSHTB_TAKEN; \
        hal_memcpy(__slot->keyfield, (k), MIN(sizeof(__slot->keyfield) - 1, hal_strlen((k)))); \
        (out) = __slot; \
        break; \
      } \
      if ((tb)[__idx]._hshtbstate == HSHTB_TAKEN && hal_strcmp((tb)[__idx].keyfield, (k)) == 0) { \
        (out) = &(tb)[__idx]; \
        break; \
      } \
      if ((tb)[__idx]._hshtbstate == HSHTB_TOMB && !__tomb) { \
        __tomb = &(tb)[__idx]; \
      } \
      __idx = (__idx + 1) % __len; \
    } while(__idx != __start); \
  } while(0)

#define HSHTB_GET(tb, keyfield, k, out) \
  do { \
    size_t __len = __HSHTB_ARRAY_LEN(tb); \
    uint32_t __h = hshtb_fnv32((k), hal_strlen((k))); \
    size_t __idx = __h % __len; \
    size_t __start = __idx; \
    (out) = NULL; \
    do { \
      if ((tb)[__idx]._hshtbstate == HSHTB_EMPTY) { \
        break; \
      } \
      if ((tb)[__idx]._hshtbstate == HSHTB_TAKEN && hal_strcmp((tb)[__idx].keyfield, (k)) == 0) { \
        (out) = &(tb)[__idx]; \
        break; \
      } \
      __idx = (__idx + 1) % __len; \
    } while(__idx != __start); \
  } while(0)

#define HSHTB_DELETE(tb, keyfield, k) \
  do { \
    typeof(&(tb)[0]) __e; \
    HSHTB_GET((tb), keyfield, (k), __e); \
    if (__e) { \
      __e->_hshtbstate = HSHTB_TOMB; \
    } \
  } while(0)

#endif // HSHTB_H_
