// Kyler Olsen
// YREA SLS
// Hash Table
// November 2025

#include <stdlib.h>

#include "sls/hash_table.h"
#include "sls/string.h"

static size_t next_power_of_two(size_t n) {
    if (n < 2) return 2;
    n--;
    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8;
    n |= n >> 16;
    #if SIZE_MAX > UINT32_MAX
        n |= n >> 32;
    #endif
    n++;
    return n;
}

HashTable *init_hash_table(size_t min_buckets_count) {
    size_t buckets_count = next_power_of_two(min_buckets_count);
    size_t size = sizeof(HashTable) + buckets_count * sizeof(Bucket);
    HashTable *ht = (HashTable *)malloc(size);
    if (!ht) return NULL;
    ht->buckets_count = buckets_count;
    for (size_t i = 0; i < buckets_count; i++)
        ht->buckets[i] = NULL;
    return ht;
}

void del_hash_table(HashTable *ht) {
    for (size_t i = 0; i < ht->buckets_count; i++) {
        Bucket *head = ht->buckets[i];
        while (head) {
            Bucket *next = head->next;
            // TODO: Does item need to be freed?
            free(head);
            head = next;
        }
    }
    free(ht);
}

static inline uint32_t sls_hash_a(SlsStr key) {
    // FNV-1a
    uint32_t hash = 2166136261u;
    const uint8_t *p = (const uint8_t *)key.str;
    for (size_t i = 0; i < key.len; i++) {
        hash ^= p[i];
        hash *= 16777619u;
    }
    return hash;
}

static inline uint32_t sls_hash_b(SlsStr key) {
    // Murmur2A
    const uint32_t m = 0x5bd1e995;
    uint32_t hash = 0;

    while (key.len >= 4) {
        uint32_t k = (uint32_t)key.str[0]
            | (uint32_t)key.str[1] << 8
            | (uint32_t)key.str[2] << 16
            | (uint32_t)key.str[3] << 24;
        k *= m;
        k ^= k >> 24;
        k *= m;

        hash *= m;
        hash ^= k;

        key.str += 4;
        key.len -= 4;
    }

    if (key.len >= 3)
        hash ^= key.str[2] << 16;
    if (key.len >= 2)
        hash ^= key.str[1] << 8;
    if (key.len >= 1)
        hash ^= key.str[0];
    hash *= m;

    hash ^= hash >> 13;
    hash *= m;
    hash ^= hash >> 15;

    return hash;
}

Boolean hash_table_put(HashTable *ht, SlsStr key, void *item) {
    uint32_t hash_a = sls_hash_a(key);
    uint32_t hash_b = sls_hash_b(key);
    size_t bucket_index = hash_a & (ht->buckets_count - 1);

    for (Bucket *node = ht->buckets[bucket_index]; node; node = node->next) {
        if (node->hash_a == hash_a && node->hash_b == hash_b) {
            // TODO: Does item need to be freed before being replaced?
            node->item = item;
            return TRUE;
        }
    }

    Bucket *bucket = (Bucket *)malloc(sizeof(Bucket));
    if (!bucket)
        return FALSE;
    bucket->hash_a = hash_a;
    bucket->hash_b = hash_b;
    bucket->item = item;
    bucket->next = ht->buckets[bucket_index];
    ht->buckets[bucket_index] = bucket;

    return TRUE;
}

void *hash_table_get(const HashTable *ht, SlsStr key, void *default_item) {
    uint32_t hash_a = sls_hash_a(key);
    uint32_t hash_b = sls_hash_b(key);
    size_t bucket_index = hash_a & (ht->buckets_count - 1);

    for (Bucket *node = ht->buckets[bucket_index]; node; node = node->next)
        if (node->hash_a == hash_a && node->hash_b == hash_b)
            return node->item;

    return default_item;
}

Boolean hash_table_del(HashTable *ht, SlsStr key) {
    uint32_t hash_a = sls_hash_a(key);
    uint32_t hash_b = sls_hash_b(key);
    size_t bucket_index = hash_a & (ht->buckets_count - 1);

    Bucket *prev = NULL;

    for (Bucket *node = ht->buckets[bucket_index]; node; node = node->next) {
        if (node->hash_a == hash_a && node->hash_b == hash_b) {
            if (prev == NULL)
                ht->buckets[bucket_index] = node->next;
            else
                prev->next = node->next;
            // TODO: Does item need to be freed?
            free(node);
            return TRUE;
        }
        prev = node;
    }

    return FALSE;
}
