#include "map.h"

API void Map_Reserve(Map *map, int size) {
    Map old_map = *map;

    map->len = 0;
    map->cap = size;
    if (map->cap < 32) {
        map->cap = 32;
        IO_Assert(map->allocator.p);
    }

    size_t byte_size = sizeof(Map_Entry) * map->cap;
    map->entries = (Map_Entry *)M_Alloc(map->allocator, byte_size);
    MA_MemoryZero(map->entries, byte_size);

    if (old_map.entries) {
        for (int i = 0; i < old_map.cap; i += 1) {
            Map_Entry *it = old_map.entries + i;
            if (it->key) Map_InsertEntry(map, it->key, it->value);
        }
        M_Dealloc(map->allocator, old_map.entries);
    }
}

API Map_Entry *Map_GetEntryBase(Map *map, uint64_t key) {
    IO_Assert(key);
    if (map->len * 2 >= map->cap) Map_Reserve(map, map->cap * 2);

    uint64_t hash = HashBytes(&key, sizeof(key));
    if (hash == 0) hash += 1;
    uint64_t index = WRAP_AROUND_POWER_OF_2(hash, map->cap);
    uint64_t i = index;
    for (;;) {
        Map_Entry *it = map->entries + i;
        if (it->key == key || it->key == 0) {
            return it;
        }

        i = WRAP_AROUND_POWER_OF_2(i + 1, map->cap);
        if (i == index) return NULL;
    }
    IO_InvalidCodepath();
}

API bool Map_InsertWithoutReplace(Map *map, void *key, void *value) {
    Map_Entry *entry = Map_GetEntryBase(map, (uint64_t)key);
    if (entry->key != 0) return false;

    map->len += 1;
    entry->key = (uint64_t)key;
    entry->value = (uint64_t)value;
    return true;
}

API Map_Entry *Map_InsertEntry(Map *map, uint64_t key, uint64_t value) {
    Map_Entry *entry = Map_GetEntryBase(map, key);
    if (entry->key == key) {
        entry->value = value;
    }
    if (entry->key == 0) {
        entry->key = key;
        entry->value = value;
        map->len += 1;
    }
    return entry;
}

API Map_Entry *Map_GetEntry(Map *map, uint64_t key) {
    Map_Entry *entry = Map_GetEntryBase(map, key);
    if (entry && entry->key == key) {
        return entry;
    }
    return NULL;
}

API void Map_Insert(Map *map, S8_String keystr, void *value) {
    uint64_t key = HashBytes(keystr.str, keystr.len);
    Map_InsertEntry(map, key, (uint64_t)value);
}

API void *Map_Get(Map *map, S8_String keystr) {
    uint64_t key = HashBytes(keystr.str, keystr.len);
    Map_Entry *r = Map_GetEntry(map, key);
    return r ? (void *)r->value : 0;
}

API void Map_InsertU64(Map *map, uint64_t key, void *value) {
    Map_InsertEntry(map, key, (uint64_t)value);
}

API void *Map_GetU64(Map *map, uint64_t key) {
    Map_Entry *r = Map_GetEntry(map, key);
    return r ? (void *)r->value : 0;
}

API void *Map_GetP(Map *map, void *key) {
    return Map_GetU64(map, (uint64_t)key);
}

API void Map_InsertP(Map *map, void *key, void *value) {
    Map_InsertEntry(map, (uint64_t)key, (uint64_t)value);
}
