#include <stdlib.h>
#include <inttypes.h>

#include "hashtable.h"

struct _htbl {
    int (*eq)(const void *, const void *);
    uint32_t (*hash)(const void *);
    struct _any **buckets;
    size_t buckcnt;
    size_t size;
    size_t keyoff;
    size_t chainoff;
    float ratio;
};

#define KEYP(tbl, o) ((void *)(o) + (tbl)->keyoff)
#define CHAINP(tbl, o) *(struct _any **)((void *)(o) + (tbl)->chainoff)

struct _htbl *
_htbl_create(float ratio, int (*eq)(const void *, const void *),
    uint32_t (*hash)(const void *), size_t keyoff, size_t chainoff)
{
    struct _htbl *tbl = calloc(1, sizeof(*tbl));
    if (tbl == NULL)
        return NULL;
    tbl->size = 0;
    tbl->buckcnt = 1;
    tbl->ratio = ratio;
    tbl->keyoff = keyoff;
    tbl->chainoff = chainoff;
    tbl->hash = hash;
    tbl->eq = eq;
    tbl->buckets = calloc(tbl->buckcnt, sizeof(*tbl->buckets));
    if (tbl->buckets == NULL) {
        free(tbl);
        return NULL;
    }
    return tbl;
}

void
_htbl_free(struct _htbl *tbl)
{
    free(tbl->buckets);
    free(tbl);
}

static struct _any *
bucket_rev(struct _htbl *tbl, struct _any *p, struct _any *n)
{
    while (n != NULL) {
        struct _any *s = CHAINP(tbl, n);
        CHAINP(tbl, n) = p;
        p = n;
        n = s;
    }
    return p;
}

static void
bucket_insert(struct _htbl *tbl, struct _any *o)
{
    size_t bi = tbl->hash(KEYP(tbl, o)) % tbl->buckcnt;
    CHAINP(tbl, o) = tbl->buckets[bi];
    tbl->buckets[bi] = o;
}

static void
_htbl_grow(struct _htbl *tbl)
{
    size_t ncnt = 2 * tbl->buckcnt + 1;
    size_t ocnt = tbl->buckcnt;
    struct _any **obuckets = tbl->buckets;
    struct _any **nbuckets = calloc(ncnt, sizeof(*nbuckets));
    if (nbuckets == NULL)
        return;

    tbl->buckcnt = ncnt;
    tbl->buckets = nbuckets;

    for (size_t i = 0; i < ocnt; i++) {
        struct _any *o = bucket_rev(tbl, NULL, obuckets[i]);
        while (o != NULL) {
            struct _any *s = CHAINP(tbl, o);
            bucket_insert(tbl, o);
            o = s;
        }
    }

    free(obuckets);
}

void
_htbl_insert(struct _htbl *tbl, struct _any *o)
{
    bucket_insert(tbl, o);
    tbl->size++;
    if (tbl->size > tbl->buckcnt * tbl->ratio)
        _htbl_grow(tbl);
}

struct _any *
_htbl_find(struct _htbl *tbl, const void *key)
{
    struct _any *ret;
    size_t bi = tbl->hash(key) % tbl->buckcnt;
    for (ret = tbl->buckets[bi]; ret != NULL; ret = CHAINP(tbl, ret))
        if (tbl->eq(KEYP(tbl, ret), key))
            return ret;
    return NULL;
}

struct _any *
_htbl_remove(struct _htbl *tbl, const void *key)
{
    size_t bi = tbl->hash(key) % tbl->buckcnt;
    struct _any **p = &tbl->buckets[bi], *o = tbl->buckets[bi];
    while (o != NULL && !tbl->eq(KEYP(tbl, o), key)) {
        p = &CHAINP(tbl, o);
        o = *p;
    }
    if (o != NULL) {
        *p = CHAINP(tbl, o);
        tbl->size--;
    }
    return o;
}

void
_htbl_fillv(struct _htbl *tbl, struct _any **v)
{
    size_t vi = 0;
    size_t bi = 0;
    struct _any *o = tbl->buckets[bi];
    while (vi < tbl->size) {
        while (o == NULL) {
            bi++;
            o = tbl->buckets[bi];
        }
        v[vi] = o;
        vi++;
        o = CHAINP(tbl, o);
    }
}

struct _any **
_htbl_tov(struct _htbl *tbl)
{
    struct _any **v = malloc(sizeof(*v));
    if (v != NULL)
        _htbl_fillv(tbl, v);
    return v;
}

size_t
_htbl_size(struct _htbl *tbl)
{
    return tbl->size;
}

static void
iter_next_bucket(struct htbl_iter *it)
{
    while (it->tbl->buckets[it->bi] == NULL)
        it->bi++;
    it->obj = it->tbl->buckets[it->bi];
    it->ptr = &it->tbl->buckets[it->bi];
}

struct _any *
_htbl_iter_first(struct _htbl *tbl, struct htbl_iter *it)
{
    if (tbl->size == 0)
        return NULL;

    it->tbl = tbl;
    it->cnt = 1;
    it->bi = 0;
    iter_next_bucket(it);
    return it->obj;
}

struct _any *
_htbl_iter_next(struct htbl_iter *it)
{
    struct _any *tmp;
    if (it->cnt == it->tbl->size)
        return NULL;

    it->cnt++;
    if ((tmp = CHAINP(it->tbl, it->obj)) != NULL) {
        it->ptr = &CHAINP(it->tbl, it->obj);
        it->obj = tmp;
    } else {
        it->bi++;
        iter_next_bucket(it);
    }
    return it->obj;
}

#include <stdio.h>

struct _any *
_htbl_iter_del(struct htbl_iter *it)
{
    struct _any *tmp;

    it->tbl->size--;
    if (it->cnt > it->tbl->size) {
        *it->ptr = NULL;
        return NULL;
    }

    if ((tmp = CHAINP(it->tbl, it->obj)) != NULL) {
        *it->ptr = tmp;
        it->obj = tmp;
    } else {
        *it->ptr = NULL;
        it->bi++;
        iter_next_bucket(it);
    }
    return it->obj;
}