#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

struct pair_list
{
    const char *key;
    char *value;
    struct pair_list *next;
};

struct hash_map
{
    struct pair_list **data;
    size_t size;
};

size_t hash(const char *key)
{
    if (!key)
        return 0;

    uint32_t hash = 2166136261;
    uint32_t prime = 16777619;

    while (*key)
    {
        hash ^= *key;
        hash *= prime;
        key++;
    }

    return hash;
}

struct hash_map *hash_map_init(size_t size)
{
    struct hash_map *res = malloc(sizeof(struct hash_map));
    if (!res)
        return NULL;
    res->data = calloc(size, sizeof(struct pair_list *));
    if (!res->data)
    {
        free(res);
        return NULL;
    }
    res->size = size;
    return res;
}

bool hash_map_insert(struct hash_map *hash_map, const char *key, char *value,
                     bool *updated)
{
    if (!hash_map || hash_map->size == 0 || !key)
        return false;

    size_t hashed_val = hash(key) % hash_map->size;
    struct pair_list *elt = hash_map->data[hashed_val];
    while (elt)
    {
        if (strcmp(key, elt->key) == 0)
            break;
        elt = elt->next;
    }
    if (!elt)
    {
        struct pair_list *new = calloc(1, sizeof(struct pair_list));
        if (!new)
            return false;
        new->key = key;
        new->value = value;
        struct pair_list *res = hash_map->data[hashed_val];
        new->next = res;
        hash_map->data[hashed_val] = new;
        if (updated)
            *updated = false;
        return true;
    }
    elt->value = value;
    if (updated)
        *updated = true;
    return true;
}

void hash_map_free(struct hash_map *hash_map)
{
    if (!hash_map)
        return;
    for (size_t i = 0; i < hash_map->size; i++)
    {
        struct pair_list *elt = hash_map->data[i];
        while (elt)
        {
            struct pair_list *n = elt->next;
            free(elt);
            elt = n;
        }
    }
    free(hash_map->data);
    free(hash_map);
    //hash_map->data = NULL;
}

void hash_map_dump(struct hash_map *hash_map)
{
    if (!hash_map)
        return;
    for (size_t i = 0; i < hash_map->size; i++)
    {
        struct pair_list *elt = hash_map->data[i];
        while (elt)
        {
            printf("%s: %s", elt->key, elt->value);
            elt = elt->next;
            if (elt)
                printf(", ");
            else
                putchar('\n');
        }
    }
}

const char *hash_map_get(const struct hash_map *hash_map, const char *key)
{
    if (!hash_map || hash_map->size == 0)
        return NULL;
    size_t hashed_val = hash(key) % hash_map->size;
    struct pair_list *elt = hash_map->data[hashed_val];
    while (elt)
    {
        if (strcmp(key, elt->key) == 0)
            break;
        elt = elt->next;
    }
    if (elt)
        return elt->value;
    return NULL;
}

bool hash_map_remove(struct hash_map *hash_map, const char *key)
{
    if (!hash_map || hash_map->size == 0)
        return false;
    size_t hashed_val = hash(key) % hash_map->size;
    struct pair_list *elt = hash_map->data[hashed_val];
    struct pair_list *prev = NULL;
    while (elt)
    {
        if (strcmp(key, elt->key) == 0)
            break;
        prev = elt;
        elt = elt->next;
    }
    if (!elt)
        return false;
    if (prev)
        prev->next = elt->next;
    else
        hash_map->data[hashed_val] = elt->next;
    //free(elt);
    printf("removed %s", elt->value);
    free(elt);
    return true;
}

int main(void)
{
    struct hash_map *map = hash_map_init(10);
    if (!map)
        return 1;

    bool updated;
    hash_map_insert(map, "key1", "value1", &updated);
    hash_map_insert(map, "key2", "value2", &updated);
    hash_map_insert(map, "key1", "new_value1", &updated);

    hash_map_dump(map);

    const char *val = hash_map_get(map, "key1");
    if (val)
        printf("Retrieved key1: %s\n", val);

    hash_map_remove(map, "key2");
    hash_map_dump(map);

    hash_map_free(map);

    printf("Everything is ok!\n");
    return 0;
}
