/**
 * @file for_next.c
 * @brief Full coverage of FOR/NEXT, which TODO.md section 6 item 4 is gated on.
 *
 * That item is `math_plus` mutating its left operand in place when the operand
 * is mutable, where every other operator clones. It is a real defect -- `A# + 1`
 * modifying `A#` depending on where the value came from -- and it was left alone
 * because `NEXT`'s loop increment *relied* on the mutation: fixing one without
 * the other silently breaks every FOR loop in the language.
 *
 * The golden corpus covers a plain loop, a nested pair and the
 * `waitingForCommand` case. What it does not cover is `STEP`, a negative step, a
 * float counter, `EXIT`, or a body that assigns to the loop variable -- and
 * those are the cases where an increment that writes to the wrong storage shows
 * up. This file is what made the fix safe to make.
 */

#include <string.h>

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

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

/** @brief Run a program to completion in RUN mode, from a string. */
static akerr_ErrorContext AKERR_NOIGNORE *run_program(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));
    PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 0));
    SUCCEED_RETURN(errctx);
}

/** @brief The counter advances by one and stops *after* the limit, not on it. */
static void test_plain_loop(void)
{
    TEST_REQUIRE_OK(run_program("10 FOR I# = 1 TO 4\n"
				"20 PRINT I#\n"
				"30 NEXT I#\n"));
    /*
     * Every value from the start to the limit, once each. That the body sees the
     * *incremented* value at all is what says the increment reached the variable
     * rather than a scratch copy of it -- the whole point of this file.
     *
     * What the counter reads after the loop is a separate question, and this
     * interpreter answers it differently from a C128: see tests/for_semantics.c.
     */
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "1\n2\n3\n4\n");
    harness_stop();
}

/** @brief STEP advances by what it says, and the limit is still a limit. */
static void test_step(void)
{
    /*
     * A step that lands exactly on the limit. The overshooting case is a known
     * defect and lives in tests/for_semantics.c; what is asserted here is only
     * that STEP is read and applied.
     */
    TEST_REQUIRE_OK(run_program("10 FOR I# = 0 TO 9 STEP 3\n"
				"20 PRINT I#\n"
				"30 NEXT I#\n"));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "0\n3\n6\n9\n");
    harness_stop();
}

/** @brief A negative step counts down, and the comparison flips with it. */
static void test_negative_step(void)
{
    TEST_REQUIRE_OK(run_program("10 FOR I# = 5 TO 1 STEP -2\n"
				"20 PRINT I#\n"
				"30 NEXT I#\n"));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "5\n3\n1\n");
    harness_stop();
}

/** @brief A float counter accumulates in the float field, not the integer one. */
static void test_float_counter(void)
{
    TEST_REQUIRE_OK(run_program("10 FOR I% = 1.0 TO 2.0 STEP 0.5\n"
				"20 PRINT I%\n"
				"30 NEXT I%\n"));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "1.000000\n1.500000\n2.000000\n");
    harness_stop();
}

/**
 * @brief A body that assigns to the loop variable changes where the loop goes.
 *
 * The counter is ordinary storage, so writing to it is legal and the loop reads
 * the written value. This is the case that tells an increment landing in the
 * variable apart from one landing in a scratch value that happens to be read
 * back: with a scratch increment the assignment on line 20 would be overwritten
 * rather than built on.
 */
static void test_body_assigns_to_counter(void)
{
    TEST_REQUIRE_OK(run_program("10 FOR I# = 1 TO 10\n"
				"20 I# = I# + 3\n"
				"30 PRINT I#\n"
				"40 NEXT I#\n"));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "4\n8\n12\n");
    harness_stop();
}

/** @brief EXIT leaves the loop and lands after its NEXT, with the wait cleared. */
static void test_exit(void)
{
    TEST_REQUIRE_OK(run_program("10 FOR I# = 1 TO 10\n"
				"20 PRINT I#\n"
				"30 IF I# = 2 THEN EXIT\n"
				"40 NEXT I#\n"
				"50 PRINT \"OUT\"\n"));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "1\n2\nOUT\n");
    harness_stop();

    /* And a second loop afterwards still runs, so the wait really was cleared. */
    TEST_REQUIRE_OK(run_program("10 FOR I# = 1 TO 10\n"
				"20 IF I# = 1 THEN EXIT\n"
				"30 NEXT I#\n"
				"40 FOR J# = 1 TO 2\n"
				"50 PRINT J#\n"
				"60 NEXT J#\n"));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "1\n2\n");
    harness_stop();
}

/** @brief Nested loops each advance their own counter and unwind in order. */
static void test_nested(void)
{
    TEST_REQUIRE_OK(run_program("10 FOR I# = 1 TO 2\n"
				"20 FOR J# = 1 TO 2\n"
				"30 PRINT I# * 10 + J#\n"
				"40 NEXT J#\n"
				"50 NEXT I#\n"));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "11\n12\n21\n22\n");
    harness_stop();
}

/**
 * @brief `A# + 1` does not modify `A#`.
 *
 * The defect itself, stated as a program. Addition on a variable read out of the
 * environment used to update the variable in place, so this printed `2` and then
 * `2` -- the first PRINT changing what the second one saw.
 */
static void test_addition_does_not_mutate(void)
{
    TEST_REQUIRE_OK(run_program("10 A# = 1\n"
				"20 PRINT A# + 1\n"
				"30 PRINT A#\n"
				"40 PRINT A# + 1\n"
				"50 PRINT A#\n"));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "2\n1\n2\n1\n");
    harness_stop();

    /* The same for a float, and for a string, which concatenates. */
    TEST_REQUIRE_OK(run_program("10 A% = 1.5\n"
				"20 PRINT A% + 1.0\n"
				"30 PRINT A%\n"));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "2.500000\n1.500000\n");
    harness_stop();

    TEST_REQUIRE_OK(run_program("10 A$ = \"X\"\n"
				"20 PRINT A$ + \"Y\"\n"
				"30 PRINT A$\n"));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "XY\nX\n");
    harness_stop();

    /* And inside an expression used twice on one line. */
    TEST_REQUIRE_OK(run_program("10 A# = 5\n"
				"20 PRINT (A# + 1) + (A# + 1)\n"
				"30 PRINT A#\n"));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "12\n5\n");
    harness_stop();
}

int main(void)
{
    test_plain_loop();
    test_step();
    test_negative_step();
    test_float_counter();
    test_body_assigns_to_counter();
    test_exit();
    test_nested();
    test_addition_does_not_mutate();
    return akbasic_test_failures;
}
