/*
 * Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#include <HashMap.h>

#include <stddef.h>
#include <stdlib.h>

typedef struct HashMapBucket
{
    unsigned long hash;
    char *key;
    void *value;
} HashMapBucket;

struct HashMap
{
    size_t count;
    size_t capacity;
    HashMapBucket **entries;

    float maxLoad;
    size_t iterator;
};

static unsigned long
HashMapHashKey(const char *key)
{
    unsigned long hash = 2166136261u;
    size_t i = 0;

    while (key[i])
    {
        hash ^= (unsigned char) key[i];
        hash *= 16777619;

        i++;
    }

    return hash;
}


static int
HashMapGrow(HashMap * map)
{
    size_t oldCapacity;
    size_t i;
    HashMapBucket **newEntries;

    if (!map)
    {
        return 0;
    }

    oldCapacity = map->capacity;
    map->capacity *= 2;

    newEntries = calloc(map->capacity, sizeof(HashMapBucket *));
    if (!newEntries)
    {
        return 0;
    }

    for (i = 0; i < oldCapacity; i++)
    {
        /* If there is a value here, and it isn't a tombstone */
        if (map->entries[i] && map->entries[i]->hash)
        {
            /* Copy it to the new entries array */
            size_t index = map->entries[i]->hash % map->capacity;

            for (;;)
            {
                if (newEntries[index])
                {
                    if (!newEntries[index]->hash)
                    {
                        free(newEntries[index]);
                        newEntries[index] = map->entries[i];
                        break;
                    }
                }
                else
                {
                    newEntries[index] = map->entries[i];
                    break;
                }

                index = (index + 1) % map->capacity;
            }
        }
        else
        {
            /* Either NULL or a tombstone */
            free(map->entries[i]);
        }
    }

    free(map->entries);
    map->entries = newEntries;
    return 1;
}

HashMap *
HashMapCreate(void)
{
    HashMap *map = malloc(sizeof(HashMap));

    if (!map)
    {
        return NULL;
    }

    map->maxLoad = 0.75;
    map->count = 0;
    map->capacity = 16;
    map->iterator = 0;

    map->entries = calloc(map->capacity, sizeof(HashMapBucket *));
    if (!map->entries)
    {
        free(map);
        return NULL;
    }

    return map;
}

void *
HashMapDelete(HashMap * map, const char *key)
{
    unsigned long hash;
    size_t index;

    if (!map || !key)
    {
        return NULL;
    }

    hash = HashMapHashKey(key);
    index = hash % map->capacity;

    for (;;)
    {
        HashMapBucket *bucket = map->entries[index];

        if (!bucket)
        {
            break;
        }

        if (bucket->hash == hash)
        {
            bucket->hash = 0;
            return bucket->value;
        }

        index = (index + 1) % map->capacity;
    }

    return NULL;
}

void
HashMapFree(HashMap * map)
{
    if (map)
    {
        size_t i;

        for (i = 0; i < map->capacity; i++)
        {
            if (map->entries[i])
            {
                free(map->entries[i]);
            }
        }
        free(map->entries);
        free(map);
    }
}

void *
HashMapGet(HashMap * map, const char *key)
{
    unsigned long hash;
    size_t index;

    if (!map || !key)
    {
        return NULL;
    }

    hash = HashMapHashKey(key);
    index = hash % map->capacity;

    for (;;)
    {
        HashMapBucket *bucket = map->entries[index];

        if (!bucket)
        {
            break;
        }

        if (bucket->hash == hash)
        {
            return bucket->value;
        }

        index = (index + 1) % map->capacity;
    }

    return NULL;
}

int
HashMapIterate(HashMap * map, char **key, void **value)
{
    if (!map)
    {
        return 0;
    }

    if (map->iterator >= map->capacity)
    {
        map->iterator = 0;
        *key = NULL;
        *value = NULL;
        return 0;
    }

    while (map->iterator < map->capacity)
    {
        HashMapBucket *bucket = map->entries[map->iterator];

        map->iterator++;

        if (bucket)
        {
            *key = bucket->key;
            *value = bucket->value;
            return 1;
        }
    }

    map->iterator = 0;
    return 0;
}

void
HashMapMaxLoadSet(HashMap * map, float load)
{
    if (!map || (load > 1.0 || load <= 0))
    {
        return;
    }

    map->maxLoad = load;
}


void *
HashMapSet(HashMap * map, char *key, void *value)
{
    unsigned long hash;
    size_t index;

    if (!map || !key || !value)
    {
        return NULL;
    }

    if (map->count + 1 > map->capacity * map->maxLoad)
    {
        HashMapGrow(map);
    }

    hash = HashMapHashKey(key);
    index = hash % map->capacity;

    for (;;)
    {
        HashMapBucket *bucket = map->entries[index];

        if (!bucket)
        {
            bucket = malloc(sizeof(HashMapBucket));
            if (!bucket)
            {
                break;
            }

            bucket->hash = hash;
            bucket->key = key;
            bucket->value = value;
            map->entries[index] = bucket;
            map->count++;
            break;
        }

        if (!bucket->hash)
        {
            bucket->hash = hash;
            bucket->key = key;
            bucket->value = value;
            break;
        }

        if (bucket->hash == hash)
        {
            void *oldValue = bucket->value;

            bucket->value = value;
            return oldValue;
        }

        index = (index + 1) % map->capacity;
    }

    return NULL;
}

void
HashMapIterateFree(char *key, void *value)
{
    if (key)
    {
        free(key);
    }

    if (value)
    {
        free(value);
    }
}