/**
 * @file symtab.c
 * @brief Implements the fixed-capacity open-addressed symbol table.
 */

#include <akerror.h>
#include <akstdlib.h>

#include <akbasic/error.h>
#include <akbasic/symtab.h>

/*
 * Probe for `key`. On success *slot points at either the slot holding the key or
 * the first free slot it could occupy; *found says which. Uses
 * aksl_strhash_djb2 rather than a private hash. That wrapper sign-extends char,
 * so a high-bit byte hashes wrong (libakstdlib's UPGRADING.md) -- harmless
 * here because BASIC identifiers are 7-bit ASCII, and it would only ever cost
 * probe efficiency, never correctness, since the key comparison is a strcmp.
 */
static akerr_ErrorContext *probe(akbasic_SymbolTable *obj, const char *key, int *slot, bool *found)
{
    PREPARE_ERROR(errctx);
    uint32_t hashval = 0;
    size_t keylen = 0;
    int index = 0;
    int cmp = 0;
    int i = 0;

    PASS(errctx, aksl_strlen(key, &keylen));
    PASS(errctx, aksl_strhash_djb2((char *)key, keylen, &hashval));

    *found = false;
    index = (int)(hashval % (uint32_t)obj->capacity);
    for ( i = 0; i < obj->capacity; i++ ) {
	int probeidx = (index + i) % obj->capacity;
	if ( !obj->slots[probeidx].used ) {
	    *slot = probeidx;
	    SUCCEED_RETURN(errctx);
	}
	PASS(errctx, aksl_strcmp(obj->slots[probeidx].key, key, &cmp));
	if ( cmp == 0 ) {
	    *slot = probeidx;
	    *found = true;
	    SUCCEED_RETURN(errctx);
	}
    }
    FAIL_RETURN(errctx, AKBASIC_ERR_BOUNDS,
		"Symbol table is full (%d entries), cannot place '%s'",
		obj->capacity, key);
}

akerr_ErrorContext *akbasic_symtab_init(akbasic_SymbolTable *obj, int capacity)
{
    PREPARE_ERROR(errctx);

    FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
		     "NULL symbol table in init");
    FAIL_ZERO_RETURN(errctx, (capacity > 0 && capacity <= AKBASIC_SYMTAB_MAX_SLOTS),
		     AKBASIC_ERR_BOUNDS,
		     "Symbol table capacity %d out of range 1..%d",
		     capacity, AKBASIC_SYMTAB_MAX_SLOTS);

    PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
    obj->capacity = capacity;
    obj->count = 0;
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_symtab_set(akbasic_SymbolTable *obj, const char *key, void *value, int64_t ivalue)
{
    PREPARE_ERROR(errctx);
    size_t keylen = 0;
    int slot = 0;
    bool found = false;

    FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
		     "NULL symbol table in set");
    FAIL_ZERO_RETURN(errctx, (key != NULL), AKERR_NULLPOINTER,
		     "NULL key in symbol table set");
    PASS(errctx, aksl_strlen(key, &keylen));
    FAIL_ZERO_RETURN(errctx, (keylen < AKBASIC_SYMTAB_MAX_KEY),
		     AKBASIC_ERR_BOUNDS,
		     "Symbol name '%s' exceeds %d characters",
		     key, AKBASIC_SYMTAB_MAX_KEY - 1);

    PASS(errctx, probe(obj, key, &slot, &found));

    if ( !found ) {
	/* aksl_strcpy always terminates, so the explicit terminator this
	   replaced is no longer needed. The length check above has already
	   refused anything that would not fit, so it cannot fail here. */
	PASS(errctx, aksl_strcpy(obj->slots[slot].key, sizeof(obj->slots[slot].key), key));
	obj->slots[slot].used = true;
	obj->count += 1;
    }
    obj->slots[slot].value = value;
    obj->slots[slot].ivalue = ivalue;
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_symtab_get(akbasic_SymbolTable *obj, const char *key, void **value, int64_t *ivalue)
{
    PREPARE_ERROR(errctx);
    size_t keylen = 0;
    int slot = 0;
    bool found = false;

    FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
		     "NULL symbol table in get");
    FAIL_ZERO_RETURN(errctx, (key != NULL), AKERR_NULLPOINTER,
		     "NULL key in symbol table get");
    PASS(errctx, aksl_strlen(key, &keylen));
    FAIL_ZERO_RETURN(errctx, (keylen < AKBASIC_SYMTAB_MAX_KEY),
		     AKERR_KEY,
		     "Symbol '%s' is not present", key);

    PASS(errctx, probe(obj, key, &slot, &found));
    FAIL_ZERO_RETURN(errctx, found, AKERR_KEY, "Symbol '%s' is not present", key);

    if ( value != NULL ) {
	*value = obj->slots[slot].value;
    }
    if ( ivalue != NULL ) {
	*ivalue = obj->slots[slot].ivalue;
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_symtab_clear(akbasic_SymbolTable *obj)
{
    PREPARE_ERROR(errctx);
    int capacity = 0;

    FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
		     "NULL symbol table in clear");

    capacity = obj->capacity;
    PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
    obj->capacity = capacity;
    SUCCEED_RETURN(errctx);
}
