/**
 * @file for_semantics.c
 * @brief Known-failing. Asserts what FOR/NEXT *should* do, which is not what it does.
 *
 * Registered in `AKBASIC_KNOWN_FAILING_TESTS`, so CTest expects it to fail. When
 * it starts passing, CTest reports "unexpectedly passed" and that is the cue to
 * move it into `AKBASIC_TESTS` along with the fix.
 *
 * Two defects, both found while writing tests/for_next.c and both recorded in
 * TODO.md section 6. Neither is the Go reference's fault alone -- the port
 * reproduced them faithfully -- and neither can be fixed without moving a golden
 * file, which is why they are written down rather than quietly corrected.
 *
 * **1. The body runs once with the overshot value.** The loop condition is
 * tested against the counter *before* the increment, so the increment's result
 * reaches the body before anything checks it against the limit.
 * `FOR I = 1 TO 9 STEP 3` runs its body with 1, 4, 7 and then 10.
 *
 * **2. The counter does not survive the loop.** It lives in the environment the
 * loop pushed, which pops when the loop ends, so reading it afterwards finds a
 * fresh variable holding zero. A C128 leaves the counter at the value that ended
 * the loop, and plenty of published listings read it afterwards.
 *
 * They are related but not the same fix: the first is an ordering bug in
 * akbasic_cmd_next(), and the second is a scoping decision about where a loop
 * counter is created.
 */

#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 A step that overshoots the limit must not run the body again. */
static void test_step_does_not_overshoot(void)
{
    TEST_REQUIRE_OK(run_program("10 FOR I# = 1 TO 9 STEP 3\n"
				"20 PRINT I#\n"
				"30 NEXT I#\n"));
    /* Today: "1\n4\n7\n10\n" -- the body runs with 10, which is past the limit. */
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "1\n4\n7\n");
    harness_stop();

    TEST_REQUIRE_OK(run_program("10 FOR I# = 1 TO 2 STEP 100\n"
				"20 PRINT I#\n"
				"30 NEXT I#\n"));
    /* Today: "1\n101\n". */
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "1\n");
    harness_stop();
}

/** @brief The loop counter is readable after the loop, holding where it stopped. */
static void test_counter_survives_the_loop(void)
{
    TEST_REQUIRE_OK(run_program("10 FOR I# = 1 TO 4\n"
				"20 NEXT I#\n"
				"30 PRINT I#\n"));
    /* Today: "0\n" -- the loop's environment took the variable with it. */
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "5\n");
    harness_stop();
}

/**
 * @brief A loop whose limit equals its start still runs its body once.
 *
 * `FOR I = 1 TO 1` executes the body one time on every BASIC there has ever
 * been. Here the entry test treats "the counter has reached the limit" as
 * "do not enter", so the body is skipped entirely -- and
 * tests/language/flowcontrol/forloopwaitingforcommand.bas pins that,
 * which is why this cannot simply be corrected.
 */
static void test_single_iteration_loop(void)
{
    TEST_REQUIRE_OK(run_program("10 FOR I# = 1 TO 1\n"
				"20 PRINT \"ONCE\"\n"
				"30 NEXT I#\n"));
    /* Today: "" -- the body never runs. */
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "ONCE\n");
    harness_stop();
}

int main(void)
{
    test_step_does_not_overshoot();
    test_counter_survives_the_loop();
    test_single_iteration_loop();
    return akbasic_test_failures;
}
