#include "compiler_internal.h"

static inline DeclId *declentry_find(DeclId *entries, uint32_t capacity, const char *name)
{
	uintptr_t hash_key = (uintptr_t)name;
	uint32_t mask = capacity - 1;
	hash_key ^= hash_key >> 16;
	uint32_t index = (uint32_t)hash_key & mask;
	while (1)
	{
		DeclId *entry = &entries[index];
		DeclId decl_id = *entry;
		if (!decl_id) return entry;
		Decl *decl = declptr(*entry);
		if (decl->name == name) return entry;
		index = (index + 1) & mask;
	}
}

static inline void decltable_resize(DeclTable *table)
{
	ASSERT(table->capacity < MAX_HASH_SIZE && "Table size too large, exceeded max hash size");
	uint32_t new_capacity = table->capacity ? (table->capacity << 2u) : 16u;
	DeclId *new_data = CALLOC(new_capacity * sizeof(DeclId));
	table->count = 0;
	uint32_t len = table->capacity;
	for (uint32_t i = 0; i < len; i++)
	{
		DeclId *entry = &table->entries[i];
		DeclId id = *entry;
		if (!id) continue;
		Decl *decl = declptr(id);
		table->count++;
		DeclId *dest = declentry_find(new_data, new_capacity, decl->name);
		*dest = id;
	}
	table->entries = new_data;
	table->max_load = (uint32_t)(new_capacity * TABLE_MAX_LOAD);
	table->capacity = new_capacity;
}

void decltable_set(DeclTable *table, Decl *decl)
{
	ASSERT(decl && "Cannot insert NULL");
	DeclId *entry = declentry_find(table->entries, table->capacity, decl->name);
	DeclId decl_id = declid(decl);
	DeclId old_id = *entry;
	ASSERT(old_id != decl_id);
	// Simple case, a new decl
	if (!old_id)
	{
		DEBUG_LOG("Adding unique %s", decl->name);
		*entry = decl_id;
		table->count++;
		if (table->count >= table->max_load) decltable_resize(table);
		return;
	}
	Decl *old = declptr(old_id);
	if (old->decl_kind == DECL_DECLARRAY)
	{
		DEBUG_LOG("Adding %s to %s", decl->name, old->name);
		vec_add(old->decl_list, decl);
		return;
	}
	Decl *list = decl_calloc();
	list->decl_kind = DECL_DECLARRAY;
	list->name = decl->name;
	vec_add(list->decl_list, old);
	vec_add(list->decl_list, decl);
	DEBUG_LOG("Adding list of %s and %s", decl->name, old->name);
	*entry = declid(list);
}


void decltable_replace(DeclTable *table, Decl *old, Decl *new)
{
	DeclId *entry = declentry_find(table->entries, table->capacity, old->name);
	DeclId decl_id = declid(old);
	if (*entry == decl_id)
	{
		*entry = declid(new);
		return;
	}
	Decl *entry_decl = declptr(*entry);
	ASSERT(entry_decl->decl_kind == DECL_DECLARRAY);
	FOREACH_IDX(i, Decl *, d, entry_decl->decl_list)
	{
		if (d == old)
		{
			entry_decl->decl_list[i] = new;
			return;
		}
	}
	UNREACHABLE_VOID
}

DeclId decltable_get(DeclTable *table, const char *name)
{
	if (!table->entries) return 0;
	DeclId *entry = declentry_find(table->entries, table->capacity, name);
	return *entry;
}

void decltable_init(DeclTable *table, uint32_t initial_size)
{
	ASSERT(initial_size && "Size must be larger than 0");
	assert (is_power_of_two(initial_size) && "Must be a power of two");

	DeclId *entries = CALLOC(initial_size * sizeof(DeclId));
	table->count = 0;
	table->capacity = initial_size;
	table->max_load = (uint32_t)(initial_size * TABLE_MAX_LOAD);
	table->entries = entries;
}
