/**
 * @file runtime_evaluate.c
 * @brief Tests the evaluator against hand-built and hand-parsed trees.
 */

#include "harness.h"

/* Parse an expression-bearing line and evaluate what it produced. */
static akerr_ErrorContext AKERR_NOIGNORE *eval_line(const char *line, akbasic_Value **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_ASTLeaf *leaf = NULL;

    PASS(errctx, akbasic_environment_zero(HARNESS_RUNTIME.environment));
    PASS(errctx, harness_parse(line, &leaf));
    PASS(errctx, akbasic_runtime_evaluate(&HARNESS_RUNTIME, leaf, dest));
    SUCCEED_RETURN(errctx);
}

static void expect_int(const char *line, int64_t want)
{
    akbasic_Value *out = NULL;
    akerr_ErrorContext *e = eval_line(line, &out);

    if ( e != NULL ) {
	fprintf(stderr, "FAIL: \"%s\": %s\n", line, e->message);
	akbasic_test_failures += 1;
	test_discard_error(e);
	return;
    }
    TEST_REQUIRE(out->valuetype == AKBASIC_TYPE_INTEGER,
		 "\"%s\" produced type %d, expected integer", line, (int)out->valuetype);
    TEST_REQUIRE(out->intval == want,
		 "\"%s\" produced %lld, expected %lld",
		 line, (long long)out->intval, (long long)want);
}

static void expect_bool(const char *line, bool want)
{
    akbasic_Value *out = NULL;
    akerr_ErrorContext *e = eval_line(line, &out);

    if ( e != NULL ) {
	fprintf(stderr, "FAIL: \"%s\": %s\n", line, e->message);
	akbasic_test_failures += 1;
	test_discard_error(e);
	return;
    }
    TEST_REQUIRE(akbasic_value_is_true(out) == want,
		 "\"%s\" expected %s", line, (want ? "true" : "false"));
}

int main(void)
{
    akbasic_Value *out = NULL;
    akerr_ErrorContext *e = NULL;

    TEST_REQUIRE_OK(harness_start(NULL));

    /* Literals and arithmetic. */
    expect_int("A# = 42", 42);
    expect_int("A# = 1 + 2 * 3", 7);
    expect_int("A# = (1 + 2) * 3", 9);
    expect_int("A# = -5 + 10", 5);
    expect_int("A# = 8 / 2", 4);

    /* Identifiers read back what was assigned. */
    expect_int("A# = 7", 7);
    expect_int("B# = A# + 1", 8);

    /*
     * Comparisons yield BASIC booleans. They are evaluated bare rather than
     * assigned: assign() accepts only INTEGER or FLOAT into a `#` variable, so
     * `A# = 1 == 1` is "Incompatible types in variable assignment" -- which is
     * also why IF works on the comparison directly and never through a variable.
     *
     * They are wrapped in parens because a bare leading integer in token
     * position 0 is consumed as a *line number*, not as a literal.
     */
    expect_bool("(1 == 1)", true);
    expect_bool("(1 == 2)", false);
    expect_bool("(2 > 1)", true);
    expect_bool("(1 <> 2)", true);
    TEST_REQUIRE_OK(akbasic_environment_zero(HARNESS_RUNTIME.environment));
    TEST_REQUIRE_STATUS(eval_line("A# = 1 == 1", &out), AKBASIC_ERR_TYPE);

    /* Bitwise operators. */
    expect_int("A# = 12 AND 10", 8);
    expect_int("A# = 12 OR 10", 14);

    /* Arrays: assign through a subscript and read it back. */
    {
	akbasic_ASTLeaf *leaf = NULL;
	TEST_REQUIRE_OK(akbasic_environment_zero(HARNESS_RUNTIME.environment));
	TEST_REQUIRE_OK(harness_parse("DIM C#(4)", &leaf));
	TEST_REQUIRE_OK(akbasic_runtime_evaluate(&HARNESS_RUNTIME, leaf, &out));
    }
    expect_int("C#(2) = 99", 99);
    expect_int("D# = C#(2)", 99);
    expect_int("D# = C#(0)", 0);

    /* An out-of-bounds subscript raises with the reference's message. */
    TEST_REQUIRE_OK(akbasic_environment_zero(HARNESS_RUNTIME.environment));
    e = eval_line("D# = C#(9)", &out);
    TEST_REQUIRE(e != NULL, "an out-of-bounds read must raise");
    if ( e != NULL ) {
	TEST_REQUIRE_STR(e->message,
			 "Variable index access out of bounds at dimension 0: 9 (max 3)");
	test_discard_error(e);
    }

    /* Built-in functions dispatch through the table. */
    expect_int("N# = -9", -9);
    expect_int("A# = ABS(N#)", 9);
    expect_int("A# = SGN(N#)", -1);

    /*
     * A negative *literal* argument, which is TODO.md section 6 item 13's
     * regression test. The reference hangs a unary leaf's operand off .right,
     * which is also where an argument list chains its arguments, so `ABS(-9)`
     * counted as two arguments and was refused outright -- no builtin could be
     * called with a negative literal at all. The corpus hid it: sgn.bas assigns
     * -1 to a variable first, which is what the three lines above do.
     */
    expect_int("A# = ABS(-9)", 9);
    expect_int("A# = SGN(-5)", -1);
    /*
     * And with a second argument after the unary one, which is the other half of
     * the same defect: chaining the next argument through .right overwrote the
     * operand the unary leaf was keeping there.
     */
    expect_int("A# = SHL(-1, 0)", -1);
    expect_int("A# = MOD(-7, 3)", -1);
    expect_int("A# = INSTR(\"HELLO\", \"L\")", 2);

    /*
     * An argument that is itself an expression, and one that is an array
     * reference. Both used to be counted as several arguments, because the
     * argument chain and the leaves' own `.right` were the same field: a binary
     * argument's right-hand side and an identifier's subscript list both looked
     * like more arguments. Arguments now chain through `.next`. TODO.md section
     * 4, "array references in parameter lists", which turned out to be section 6
     * item 13 a second time.
     */
    expect_int("A# = MOD(7, 1 + 2)", 1);
    expect_int("A# = SHL(1 + 1, 2 + 1)", 16);
    expect_int("A# = ABS(0 - 4)", 4);
    /*
     * Subscript zero rather than a DIM: every scalar is really a one-element
     * array, so `N#(0)` is a subscripted reference to an ordinary variable and
     * exercises the same parse path a DIMmed array would, without needing one.
     */
    TEST_REQUIRE_OK(akbasic_environment_zero(HARNESS_RUNTIME.environment));
    expect_int("N#(0) = -9", -9);
    expect_int("A# = ABS(N#(0))", 9);
    expect_int("A# = MOD(N#(0) + 16, 4)", 3);
    expect_int("A# = LEN(\"HELLO\")", 5);
    expect_int("A# = SHL(1, 4)", 16);
    expect_int("A# = SHR(16, 4)", 1);
    expect_int("A# = XOR(12, 10)", 6);
    expect_int("A# = MOD(7, 3)", 1);
    expect_int("A# = INSTR(\"HELLO\", \"LL\")", 2);
    expect_int("A# = INSTR(\"HELLO\", \"ZZ\")", -1);

    /* RND auto-seeds from host time and follows the documented LCG. */
    HARNESS_RUNTIME.rndseeded = false;
    TEST_REQUIRE_OK(akbasic_runtime_settime(&HARNESS_RUNTIME, 12345));
    expect_int("A# = RND(6)", 0);
    expect_int("A# = RND(6)", 4);
    expect_int("A# = RND(6)", 1);
    expect_int("A# = RND(6)", 0);
    expect_int("A# = RND(6)", 1);
    expect_int("A# = RND(1)", 0);
    TEST_REQUIRE_STATUS(eval_line("A# = RND(0)", &out), AKBASIC_ERR_VALUE);
    TEST_REQUIRE_STATUS(eval_line("A# = RND(-1)", &out), AKBASIC_ERR_VALUE);
    TEST_REQUIRE_STATUS(eval_line("A# = RND(\"x\")", &out), AKBASIC_ERR_TYPE);

    /* ASC is the inverse of CHR for ASCII and non-ASCII code points. */
    expect_int("A# = ASC(CHR(97))", 97);
    expect_int("A# = ASC(\"A\")", 65);
    expect_int("A# = ASC(CHR(8364))", 8364);
    TEST_REQUIRE_STATUS(eval_line("A# = ASC(\"\")", &out), AKBASIC_ERR_BOUNDS);
    TEST_REQUIRE_STATUS(eval_line("A# = ASC(65)", &out), AKBASIC_ERR_TYPE);

    /* An unknown verb is diagnosed rather than silently ignored. */
    TEST_REQUIRE_OK(akbasic_environment_zero(HARNESS_RUNTIME.environment));
    TEST_REQUIRE_STATUS(akbasic_runtime_evaluate(&HARNESS_RUNTIME, NULL, &out),
			AKERR_NULLPOINTER);

    /* A label resolves to its line number through a bare identifier. */
    TEST_REQUIRE_OK(akbasic_environment_set_label(HARNESS_RUNTIME.environment, "TOP", 30));
    expect_int("A# = TOP", 30);

    harness_stop();
    return akbasic_test_failures;
}
