/**
 * @file struct_types.c
 * @brief Tests `TYPE` declarations, their layout, and records by value.
 *
 * The assertions here are about the *descriptor* -- what the prescan read out of
 * the source and how it laid an instance out -- because that is the part a BASIC
 * program cannot see and therefore cannot pin. What a program can see is pinned
 * by tests/language/structures/ instead.
 *
 * The layout assertions matter more than they look. A field's offset is the only
 * thing standing between `R@.H#` and `R@.W#`, and an off-by-one there reads the
 * neighbouring field rather than failing -- so it would pass every test that
 * only ever set one field and read it back.
 */

#include <string.h>

#include <akbasic/error.h>
#include <akbasic/runtime.h>
#include <akbasic/structtype.h>

#include "harness.h"
#include "testutil.h"

/** @brief Load a program and start it, which is what runs the prescan. */
static akerr_ErrorContext AKERR_NOIGNORE *declare(const char *source)
{
    PREPARE_ERROR(errctx);

    PASS(errctx, harness_start(NULL));
    PASS(errctx, akbasic_runtime_load(&HARNESS_RUNTIME, source));
    PASS(errctx, akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
    SUCCEED_RETURN(errctx);
}

static const char *RECT =
    "10 TYPE RECT\n"
    "20   W#\n"
    "30   H#\n"
    "40 END TYPE\n"
    "50 PRINT 1\n";

/** @brief A declaration becomes a descriptor with names, types and offsets. */
static void test_layout(void)
{
    akbasic_StructField *field = NULL;
    int index = -1;

    TEST_REQUIRE_OK(declare(RECT));
    TEST_REQUIRE_OK(akbasic_structtype_find(&HARNESS_RUNTIME.structtypes, "RECT", &index));
    TEST_REQUIRE(index >= 0, "RECT should have been declared");
    TEST_REQUIRE_INT(HARNESS_RUNTIME.structtypes.types[index].fieldcount, 2);
    TEST_REQUIRE_INT(HARNESS_RUNTIME.structtypes.types[index].slotcount, 2);

    /*
     * Offsets, in declaration order. This is the assertion an off-by-one hides
     * from: swapping these two would still let a program set W# and read W#.
     */
    TEST_REQUIRE_OK(akbasic_structtype_field(&HARNESS_RUNTIME.structtypes, index, "W#", &field));
    TEST_REQUIRE_INT(field->offset, 0);
    TEST_REQUIRE_INT(field->valuetype, AKBASIC_TYPE_INTEGER);
    TEST_REQUIRE_INT(field->slotcount, 1);
    TEST_REQUIRE_OK(akbasic_structtype_field(&HARNESS_RUNTIME.structtypes, index, "H#", &field));
    TEST_REQUIRE_INT(field->offset, 1);
    harness_stop();
}

/**
 * @brief A nested value flattens into its container's run.
 *
 * The property that lets an instance be one contiguous slice of the value pool,
 * exactly as an array is, and so needs no pool of its own.
 */
static void test_nesting_flattens(void)
{
    akbasic_StructField *field = NULL;
    int index = -1;

    TEST_REQUIRE_OK(declare("10 TYPE COORD\n"
			    "20   X#\n"
			    "30   Y#\n"
			    "40 END TYPE\n"
			    "50 TYPE SEGMENT\n"
			    "60   SRC@ AS COORD\n"
			    "70   DEST@ AS COORD\n"
			    "80   NAME$\n"
			    "90 END TYPE\n"
			    "100 PRINT 1\n"));
    TEST_REQUIRE_OK(akbasic_structtype_find(&HARNESS_RUNTIME.structtypes, "SEGMENT", &index));
    TEST_REQUIRE(index >= 0, "SEGMENT should have been declared");
    if ( index < 0 ) {
	harness_stop();
	return;
    }
    /* Two coordinates of two slots each, plus one string: five, not three. */
    TEST_REQUIRE_INT(HARNESS_RUNTIME.structtypes.types[index].slotcount, 5);

    TEST_REQUIRE_OK(akbasic_structtype_field(&HARNESS_RUNTIME.structtypes, index, "SRC@", &field));
    TEST_REQUIRE_INT(field->offset, 0);
    TEST_REQUIRE_INT(field->slotcount, 2);
    TEST_REQUIRE_OK(akbasic_structtype_field(&HARNESS_RUNTIME.structtypes, index, "DEST@", &field));
    TEST_REQUIRE_INT(field->offset, 2);
    TEST_REQUIRE_OK(akbasic_structtype_field(&HARNESS_RUNTIME.structtypes, index, "NAME$", &field));
    TEST_REQUIRE_INT(field->offset, 4);
    harness_stop();
}

/**
 * @brief A type may be named before it is declared.
 *
 * Which is why the prescan registers every name before it reads any field list.
 * A linked list needs it even for itself, and this is the case that pins it.
 */
static void test_forward_reference(void)
{
    int index = -1;

    TEST_REQUIRE_OK(declare("10 TYPE OUTER\n"
			    "20   INNER@ AS INNERT\n"
			    "30 END TYPE\n"
			    "40 TYPE INNERT\n"
			    "50   N#\n"
			    "60 END TYPE\n"
			    "70 PRINT 1\n"));
    TEST_REQUIRE_OK(akbasic_structtype_find(&HARNESS_RUNTIME.structtypes, "OUTER", &index));
    TEST_REQUIRE(index >= 0, "OUTER should have been declared");
    if ( index < 0 ) {
	harness_stop();
	return;
    }
    TEST_REQUIRE_INT(HARNESS_RUNTIME.structtypes.types[index].slotcount, 1);
    harness_stop();
}

/** @brief The field-missing refusal names the type and lists what it does have. */
static void test_missing_field_lists_the_others(void)
{
    akbasic_StructField *field = NULL;
    akerr_ErrorContext *raised = NULL;
    int index = -1;

    TEST_REQUIRE_OK(declare(RECT));
    TEST_REQUIRE_OK(akbasic_structtype_find(&HARNESS_RUNTIME.structtypes, "RECT", &index));

    raised = akbasic_structtype_field(&HARNESS_RUNTIME.structtypes, index, "NOPE#", &field);
    TEST_REQUIRE(raised != NULL, "a field the type does not declare must be refused");
    TEST_REQUIRE_INT(raised->status, AKBASIC_ERR_UNDEFINED);
    /*
     * The list is the point. A misspelled field is catchable only because the
     * set is closed and the program wrote it down, so the message shows it --
     * that is what turns "why is my total zero" into a one-line fix.
     */
    TEST_REQUIRE(strstr(raised->message, "W#") != NULL && strstr(raised->message, "H#") != NULL,
		 "the refusal should list the fields RECT does have, got \"%s\"", raised->message);
    test_discard_error(raised);
    harness_stop();
}

/**
 * @brief A type containing itself by value is refused, by name.
 *
 * It has no finite size, so the prescan's size resolution never completes for
 * it -- and "the set of types that never resolved" is exactly the set that
 * contains itself. Diagnosed there rather than discovered when the pool runs
 * out, and the message says what to do instead.
 */
static void test_self_by_value_refused(void)
{
    TEST_REQUIRE_OK(harness_start(NULL));
    TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME,
					 "10 TYPE NODE\n"
					 "20   COUNT#\n"
					 "30   TAIL@ AS NODE\n"
					 "40 END TYPE\n"
					 "50 PRINT 1\n"));
    TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
    TEST_REQUIRE_OK(akbasic_runtime_run(&HARNESS_RUNTIME, 0));
    /*
     * Reported as a BASIC error rather than raised at the host. A malformed
     * declaration is the program's mistake, and goal 3 says a script's mistakes
     * do not reach the embedding program.
     */
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "contains itself by value") != NULL,
		 "self-containment should be refused by name, got \"%s\"", HARNESS_OUTPUT);
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "PTR TO") != NULL,
		 "the refusal should say what to do instead, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    /* And through a pointer it is legal, which is how a list is built at all. */
    TEST_REQUIRE_OK(declare("10 TYPE NODE\n"
			    "20   COUNT#\n"
			    "30   TAIL@ AS PTR TO NODE\n"
			    "40 END TYPE\n"
			    "50 PRINT 1\n"));
    harness_stop();
}

/** @brief A declaration error reaches the program, not the host. */
static void test_declaration_errors_are_basic_errors(void)
{
    static const char *CASES[] = {
	"10 TYPE RECT\n20   W\n30 END TYPE\n40 PRINT 1\n",
	"10 TYPE RECT\n20   W#\n30 PRINT 1\n",
	"10 TYPE RECT\n20   W#\n30 END TYPE\n40 TYPE RECT\n50   H#\n60 END TYPE\n70 PRINT 1\n",
	"10 TYPE RECT\n20   W#\n30   W#\n40 END TYPE\n50 PRINT 1\n",
	"10 TYPE RECT\n20   INNER@\n30 END TYPE\n40 PRINT 1\n"
    };
    size_t i = 0;

    for ( i = 0; i < sizeof(CASES) / sizeof(CASES[0]); i++ ) {
	TEST_REQUIRE_OK(harness_start(NULL));
	TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME, CASES[i]));
	TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
	TEST_REQUIRE_OK(akbasic_runtime_run(&HARNESS_RUNTIME, 0));
	TEST_REQUIRE(strstr(HARNESS_OUTPUT, "PARSE ERROR") != NULL,
		     "case %zu should report a parse error, got \"%s\"", i, HARNESS_OUTPUT);
	harness_stop();
    }
}

/** @brief A field declaration error reports the field's source line. */
static void test_type_prescan_error_line(void)
{
    TEST_REQUIRE_OK(harness_start(NULL));
    TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME,
					 "10 TYPE RECT\n"
					 "20 W# EXTRA\n"
					 "30 END TYPE\n"
					 "40 PRINT 1\n"));
    TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "? 20 : PARSE ERROR") != NULL,
		 "TYPE prescan should report its source line, got \"%s\"",
		 HARNESS_OUTPUT);
    harness_stop();
}

/**
 * @brief An over-long type or field name is refused, not silently trimmed.
 *
 * Truncation is an error everywhere else here, and it matters more for a name
 * than for a value: two long names trimmed to the same 31 characters would
 * collide, and the second would be reported as a redeclaration of a type the
 * program never wrote.
 *
 * The check was unreachable when it was first written, because the prescan read
 * words into a buffer the size of the limit and so trimmed them before anything
 * could look. Pinned here so it stays reachable.
 */
static void test_long_names_are_refused(void)
{
    TEST_REQUIRE_OK(harness_start(NULL));
    TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME,
					 "10 TYPE AVERYVERYVERYLONGTYPENAMETHATGOESONANDON\n"
					 "20   W#\n"
					 "30 END TYPE\n"
					 "40 PRINT 1\n"));
    TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
    TEST_REQUIRE_OK(akbasic_runtime_run(&HARNESS_RUNTIME, 0));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "exceeds the 31 character limit") != NULL,
		 "an over-long type name should be refused, got \"%s\"", HARNESS_OUTPUT);
    /* The whole name, not the trimmed one -- that is what makes it actionable. */
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "GOESONANDON") != NULL,
		 "the refusal should show the name as written, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    TEST_REQUIRE_OK(harness_start(NULL));
    TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME,
					 "10 TYPE OKNAME\n"
					 "20   AVERYVERYVERYLONGFIELDNAMETHATGOESON#\n"
					 "30 END TYPE\n"
					 "40 PRINT 1\n"));
    TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
    TEST_REQUIRE_OK(akbasic_runtime_run(&HARNESS_RUNTIME, 0));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "exceeds the 31 character limit") != NULL,
		 "an over-long field name should be refused, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();
}

int main(void)
{
    TEST_REQUIRE_OK(akbasic_error_register());

    test_layout();
    test_nesting_flattens();
    test_forward_reference();
    test_missing_field_lists_the_others();
    test_self_by_value_refused();
    test_declaration_errors_are_basic_errors();
    test_type_prescan_error_line();
    test_long_names_are_refused();

    return akbasic_test_failures;
}
