/**
 * @file variable_subscript.c
 * @brief Tests variable storage, typing by suffix, and subscript arithmetic.
 */

#include <string.h>

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

#include "testutil.h"

static akbasic_ValuePool POOL;
static akbasic_Variable VAR;

static void name_it(const char *name)
{
    memset(&VAR, 0, sizeof(VAR));
    strncpy(VAR.name, name, sizeof(VAR.name) - 1);
}

int main(void)
{
    akbasic_Value *slot = NULL;
    akerr_ErrorContext *e = NULL;
    int64_t sizes1[1] = { 5 };
    int64_t sizes2[2] = { 8, 8 };
    int64_t sizes3[3] = { 2, 3, 4 };
    int64_t at[3] = { 0, 0, 0 };

    TEST_REQUIRE_OK(akbasic_error_register());
    TEST_REQUIRE_OK(akbasic_valuepool_init(&POOL));

    /* The suffix picks the type. Note % is float here, inverting Commodore. */
    name_it("A#");
    TEST_REQUIRE_OK(akbasic_variable_init(&VAR, &POOL, sizes1, 1));
    TEST_REQUIRE_INT(VAR.valuetype, AKBASIC_TYPE_INTEGER);
    TEST_REQUIRE_INT(VAR.valuecount, 5);

    name_it("A%");
    TEST_REQUIRE_OK(akbasic_variable_init(&VAR, &POOL, sizes1, 1));
    TEST_REQUIRE_INT(VAR.valuetype, AKBASIC_TYPE_FLOAT);

    name_it("A$");
    TEST_REQUIRE_OK(akbasic_variable_init(&VAR, &POOL, sizes1, 1));
    TEST_REQUIRE_INT(VAR.valuetype, AKBASIC_TYPE_STRING);

    /* One dimension: round-trip every element. */
    name_it("B#");
    TEST_REQUIRE_OK(akbasic_variable_init(&VAR, &POOL, sizes1, 1));
    for ( at[0] = 0; at[0] < 5; at[0]++ ) {
	TEST_REQUIRE_OK(akbasic_variable_set_integer(&VAR, at[0] * 10, at, 1));
    }
    for ( at[0] = 0; at[0] < 5; at[0]++ ) {
	TEST_REQUIRE_OK(akbasic_variable_get_subscript(&VAR, at, 1, &slot));
	TEST_REQUIRE_INT(slot->intval, at[0] * 10);
    }

    /* Two dimensions, the shape array_multidimensional.bas uses. */
    name_it("C#");
    TEST_REQUIRE_OK(akbasic_variable_init(&VAR, &POOL, sizes2, 2));
    TEST_REQUIRE_INT(VAR.valuecount, 64);
    at[0] = 0; at[1] = 7;
    TEST_REQUIRE_OK(akbasic_variable_set_integer(&VAR, 31337, at, 2));
    at[0] = 1; at[1] = 7;
    TEST_REQUIRE_OK(akbasic_variable_set_integer(&VAR, 65535, at, 2));
    at[0] = 0; at[1] = 7;
    TEST_REQUIRE_OK(akbasic_variable_get_subscript(&VAR, at, 2, &slot));
    TEST_REQUIRE_INT(slot->intval, 31337);
    at[0] = 1; at[1] = 7;
    TEST_REQUIRE_OK(akbasic_variable_get_subscript(&VAR, at, 2, &slot));
    TEST_REQUIRE_INT(slot->intval, 65535);

    /* Three dimensions, to prove the flattening multiplier walks correctly. */
    name_it("D#");
    TEST_REQUIRE_OK(akbasic_variable_init(&VAR, &POOL, sizes3, 3));
    TEST_REQUIRE_INT(VAR.valuecount, 24);
    at[0] = 1; at[1] = 2; at[2] = 3;
    TEST_REQUIRE_OK(akbasic_variable_set_integer(&VAR, 99, at, 3));
    TEST_REQUIRE_OK(akbasic_variable_get_subscript(&VAR, at, 3, &slot));
    TEST_REQUIRE_INT(slot->intval, 99);
    at[0] = 0; at[1] = 0; at[2] = 0;
    TEST_REQUIRE_OK(akbasic_variable_get_subscript(&VAR, at, 3, &slot));
    TEST_REQUIRE_INT(slot->intval, 0);

    /*
     * The out-of-bounds message is compared with strcmp, not merely for status:
     * tests/language/array_outofbounds.txt reproduces it verbatim, so a reworded
     * message is a failing golden case.
     */
    name_it("A#");
    sizes1[0] = 3;
    TEST_REQUIRE_OK(akbasic_variable_init(&VAR, &POOL, sizes1, 1));
    at[0] = 4;
    e = akbasic_variable_get_subscript(&VAR, at, 1, &slot);
    TEST_REQUIRE(e != NULL, "an out-of-bounds subscript must raise");
    if ( e != NULL ) {
	TEST_REQUIRE_INT(e->status, AKBASIC_ERR_BOUNDS);
	TEST_REQUIRE_STR(e->message,
			 "Variable index access out of bounds at dimension 0: 4 (max 2)");
	test_discard_error(e);
    }

    /* Wrong subscript count. */
    name_it("E#");
    TEST_REQUIRE_OK(akbasic_variable_init(&VAR, &POOL, sizes2, 2));
    at[0] = 0;
    TEST_REQUIRE_STATUS(akbasic_variable_get_subscript(&VAR, at, 1, &slot), AKBASIC_ERR_BOUNDS);

    /* A dimension must be positive. */
    name_it("F#");
    sizes1[0] = 0;
    TEST_REQUIRE_STATUS(akbasic_variable_init(&VAR, &POOL, sizes1, 1), AKBASIC_ERR_VALUE);
    sizes1[0] = -1;
    TEST_REQUIRE_STATUS(akbasic_variable_init(&VAR, &POOL, sizes1, 1), AKBASIC_ERR_VALUE);

    /* An unnamed variable has no type to derive. */
    memset(&VAR, 0, sizeof(VAR));
    sizes1[0] = 1;
    TEST_REQUIRE_STATUS(akbasic_variable_init(&VAR, &POOL, sizes1, 1), AKBASIC_ERR_VALUE);

    return akbasic_test_failures;
}
