/**
 * @file machine_verbs.c
 * @brief Tests the group J verbs: FETCH, STASH and SYS.
 *
 * These operate on real process memory, exactly as POKE, PEEK and POINTER
 * already do, so the tests give them real addresses -- their own buffers, taken
 * with POINTER the way a BASIC program would have to.
 */

#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 STASH copies bytes, and FETCH copies them back.
 *
 * Addresses come from POINTER, which is the only way a BASIC program can name a
 * real one. Two string variables give two buffers to copy between.
 */
static void test_copy(void)
{
    TEST_REQUIRE_OK(run_program("10 A$ = \"SOURCE\"\n"
				"20 B$ = \"XXXXXX\"\n"
				"30 STASH 7, POINTER(A$), POINTER(B$)\n"
				"40 PRINT B$\n"));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "SOURCE\n");
    harness_stop();

    /* FETCH is the same copy; there is no expansion RAM to distinguish them. */
    TEST_REQUIRE_OK(run_program("10 A$ = \"HELLO!\"\n"
				"20 B$ = \"......\"\n"
				"30 FETCH 7, POINTER(A$), POINTER(B$)\n"
				"40 PRINT B$\n"));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "HELLO!\n");
    harness_stop();
}

/** @brief A zero-length copy does nothing, and a negative one is refused. */
static void test_copy_bounds(void)
{
    TEST_REQUIRE_OK(run_program("10 A$ = \"ABC\"\n"
				"20 B$ = \"XYZ\"\n"
				"30 STASH 0, POINTER(A$), POINTER(B$)\n"
				"40 PRINT B$\n"));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "XYZ\n");
    harness_stop();

    TEST_REQUIRE_OK(run_program("10 A$ = \"ABC\"\n"
				"20 STASH -1, POINTER(A$), POINTER(A$)\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "negative number of bytes") != NULL,
		 "a negative count should be refused, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    /* Address zero is refused rather than dereferenced. */
    TEST_REQUIRE_OK(run_program("10 A$ = \"ABC\"\n"
				"20 STASH 3, 0, POINTER(A$)\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "address zero") != NULL,
		 "address zero should be refused, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    /* Too few arguments is a syntax error naming the form. */
    TEST_REQUIRE_OK(run_program("10 STASH 3\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "source address") != NULL,
		 "an incomplete STASH should name the form, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();
}

/**
 * @brief SYS parses and is then refused by name.
 *
 * Parsing first is what keeps a listing containing SYS loadable and listable; it
 * is only running it that has no meaning.
 */
static void test_sys_refused(void)
{
    TEST_REQUIRE_OK(run_program("10 SYS 65490\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "no 6502") != NULL,
		 "SYS should be refused with a reason, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    /*
     * A SYS with no address is a syntax error, not the device refusal -- and it
     * names the verb. It used to fail deep in the expression parser with
     * "peek() returned nil token!", which every verb taking an argument list
     * shared.
     */
    TEST_REQUIRE_OK(run_program("10 SYS\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "SYS expected at least one argument") != NULL,
		 "a bare SYS should name the verb, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();
}

int main(void)
{
    test_copy();
    test_copy_bounds();
    test_sys_refused();
    return akbasic_test_failures;
}
