#include "stringmap.h"
#include "common/math.h"
#include "common/stringmap.h"
#include <stdint.h>
#include <stdlib.h>

StringMap* stringmap_new(void)
{
	StringMap* map = malloc(sizeof(StringMap));
	stringmap_create(map);
	return map;
}

void stringmap_delete(StringMap* map) { free(map); }

size_t* stringmap_get(const StringMap* map, const char* key, size_t key_length)
{
	size_t key_hash
		= common_string_hash_djb2((const unsigned char*)key, key_length);
	for (size_t i = 0; i < map->length; ++i)
		if (map->data[i].key_hash == key_hash && !map->data[i].deleted)
			return &map->data[i].value;
	return NULL;
}

bool stringmap_has(const StringMap* map, const char* key, size_t key_length)
{
	size_t key_hash
		= common_string_hash_djb2((const unsigned char*)key, key_length);
	for (size_t i = 0; i < map->length; ++i)
		if (map->data[i].key_hash == key_hash && !map->data[i].deleted)
			return true;
	return false;
}

void stringmap_set(
	StringMap* map, const char* key, size_t key_length, size_t value
)
{
	size_t key_hash
		= common_string_hash_djb2((const unsigned char*)key, key_length);
	for (size_t i = 0; i < map->length; ++i) {
		if (map->data[i].key_hash == key_hash && !map->data[i].deleted) {
			map->data[i].value = value;
			return;
		}
	}
	if (map->data == NULL) {
		map->capacity = 8;
		map->data = malloc(sizeof(StringMapEntry) * map->capacity);
	} else if (map->length == map->capacity) {
		map->capacity *= 2;
		map->data = realloc(map->data, sizeof(StringMapEntry) * map->capacity);
	}
	map->data[map->length] = (StringMapEntry) {
		.deleted = false,
		.key_hash = key_hash,
		.value = value,
	};
	map->length++;
}

void stringmap_reserve(StringMap* map, size_t minimum_size)
{
	if (map->capacity >= minimum_size)
		return;
	map->capacity = common_nearest_bigger_power_of_2_u64(minimum_size);
	map->data = realloc(map->data, sizeof(StringMapEntry) * map->capacity);
}

void stringmap_remove(StringMap* map, const char* key, size_t key_length)
{
	size_t key_hash
		= common_string_hash_djb2((const unsigned char*)key, key_length);
	for (size_t i = 0; i < map->length; ++i) {
		if (map->data[i].key_hash == key_hash && !map->data[i].deleted) {
			map->data[i].deleted = true;
		}
	}
}

void stringmap_clean(StringMap* map)
{
	size_t shift_amount = 0;
	for (size_t i = 0; i < map->length; ++i) {
		map->data[i - shift_amount] = map->data[i];
		if (map->data[i].deleted)
			shift_amount++;
	}
}

void stringmap_shrink(StringMap* map)
{
	size_t new_size = common_nearest_bigger_power_of_2_u64(map->length);
	if (new_size >= map->capacity)
		return;
	map->capacity = new_size;
	map->data = realloc(map->data, sizeof(StringMapEntry) * map->capacity);
}

void stringmap_clean_and_shrink(StringMap* map)
{
	stringmap_clean(map);
	stringmap_shrink(map);
}

size_t stringmap_length(StringMap* map) { return map->length; }

void stringmap_create(StringMap* map)
{
	*map = (StringMap) {
		.data = NULL,
		.length = 0,
		.capacity = 0,
	};
}

void stringmap_destroy(StringMap* map)
{
	if (map->data)
		free(map->data);
}