/**
 * @file renumber.c
 * @brief Tests RENUMBER: the lines move, and every reference to one moves with it.
 *
 * The reason this verb was deferred rather than written early is that the easy
 * half is worthless on its own. Moving lines is a permutation; a RENUMBER that
 * did only that would silently break every branch in the program. So most of
 * what is asserted here is the *rewriting*, including the two things a textual
 * rewrite can get wrong: a number inside a string literal, and a target naming a
 * line that does not exist.
 */

#include <string.h>

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

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

/** @brief Load a program without running it. */
static akerr_ErrorContext AKERR_NOIGNORE *load(const char *source)
{
    PREPARE_ERROR(errctx);

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

/** @brief Run whatever is loaded, bounded so a broken branch fails rather than hangs. */
static akerr_ErrorContext AKERR_NOIGNORE *run_loaded(void)
{
    PREPARE_ERROR(errctx);

    HARNESS_RUNTIME.environment->nextline = 0;
    PASS(errctx, akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
    PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 40000));
    SUCCEED_RETURN(errctx);
}

/** @brief Lines land on the numbers asked for, in source order. */
static void test_lines_move(void)
{
    TEST_REQUIRE_OK(load("5 PRINT \"A\"\n"
			 "7 PRINT \"B\"\n"
			 "9 PRINT \"C\"\n"));
    TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "PRINT \"A\"");
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[20].code, "PRINT \"B\"");
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[30].code, "PRINT \"C\"");
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[5].code, "");
    harness_stop();

    /* A different start and increment. */
    TEST_REQUIRE_OK(load("1 PRINT \"A\"\n2 PRINT \"B\"\n"));
    TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 100, 5, 0));
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[100].code, "PRINT \"A\"");
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[105].code, "PRINT \"B\"");
    harness_stop();
}

/** @brief GOTO and GOSUB targets follow the lines they name. */
static void test_branches_rewritten(void)
{
    TEST_REQUIRE_OK(load("5 GOTO 9\n"
			 "7 PRINT \"SKIPPED\"\n"
			 "9 PRINT \"ARRIVED\"\n"));
    TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "GOTO 30");
    TEST_REQUIRE_OK(run_loaded());
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "ARRIVED\n");
    harness_stop();

    TEST_REQUIRE_OK(load("5 GOSUB 9\n"
			 "6 END\n"
			 "9 PRINT \"SUB\"\n"
			 "11 RETURN\n"));
    TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "GOSUB 30");
    TEST_REQUIRE_OK(run_loaded());
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "SUB\n");
    harness_stop();
}

/** @brief A GOTO after THEN or ELSE is rewritten; it is not at a statement start. */
static void test_branch_inside_a_condition(void)
{
    TEST_REQUIRE_OK(load("5 IF 1 == 1 THEN GOTO 9 ELSE GOTO 7\n"
			 "7 PRINT \"ELSE\"\n"
			 "9 PRINT \"THEN\"\n"));
    TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "IF 1 == 1 THEN GOTO 30 ELSE GOTO 20");
    harness_stop();
}

/** @brief ON's whole target list is rewritten, not just the first entry. */
static void test_on_list_rewritten(void)
{
    TEST_REQUIRE_OK(load("5 ON 2 GOTO 7, 9\n"
			 "7 PRINT \"ONE\"\n"
			 "9 PRINT \"TWO\"\n"));
    TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
    /* The original spacing survives; only the numbers change. */
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "ON 2 GOTO 20, 30");
    TEST_REQUIRE_OK(run_loaded());
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "TWO\n");
    harness_stop();
}

/** @brief RESTORE, TRAP and COLLISION targets are rewritten too. */
static void test_other_verbs_rewritten(void)
{
    TEST_REQUIRE_OK(load("5 RESTORE 9\n"
			 "7 TRAP 9\n"
			 "8 COLLISION 1, 9\n"
			 "9 DATA 1\n"));
    TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "RESTORE 40");
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[20].code, "TRAP 40");
    /* COLLISION's handler is its *second* argument, so the type is left alone. */
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[30].code, "COLLISION 1, 40");
    harness_stop();
}

/**
 * @brief A number inside a string literal is not a line reference.
 *
 * The one thing a textual rewrite is most likely to get wrong, and the reason
 * the rewrite has to understand quotes at all.
 */
static void test_string_literals_untouched(void)
{
    TEST_REQUIRE_OK(load("5 PRINT \"GOTO 9 IS TEXT\"\n"
			 "9 PRINT \"END\"\n"));
    TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "PRINT \"GOTO 9 IS TEXT\"");
    harness_stop();
}

/**
 * @brief A target naming no line is left alone rather than invented.
 *
 * `GOTO 9999` in a program with no line 9999 is already broken; rewriting it to
 * point somewhere would hide that rather than fix it.
 */
static void test_dangling_target_left_alone(void)
{
    TEST_REQUIRE_OK(load("5 GOTO 9999\n"
			 "9 PRINT \"HERE\"\n"));
    TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "GOTO 9999");
    harness_stop();
}

/** @brief A label target needs no rewriting, which is the case for using labels. */
static void test_labels_need_no_rewriting(void)
{
    TEST_REQUIRE_OK(load("5 GOTO DONE\n"
			 "7 PRINT \"SKIPPED\"\n"
			 "9 LABEL DONE\n"
			 "11 PRINT \"ARRIVED\"\n"));
    TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "GOTO DONE");
    TEST_REQUIRE_OK(run_loaded());
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "ARRIVED\n");
    harness_stop();
}

/** @brief RENUMBER from a line leaves everything before it where it was. */
static void test_partial_renumber(void)
{
    TEST_REQUIRE_OK(load("10 PRINT \"KEPT\"\n"
			 "55 PRINT \"MOVED\"\n"
			 "57 PRINT \"ALSO MOVED\"\n"));
    TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 100, 10, 50));
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "PRINT \"KEPT\"");
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[100].code, "PRINT \"MOVED\"");
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[110].code, "PRINT \"ALSO MOVED\"");
    harness_stop();
}

/** @brief A renumbering that would land on a kept line is refused, not applied. */
static void test_collision_refused(void)
{
    TEST_REQUIRE_OK(load("10 PRINT \"KEPT\"\n"
			 "55 PRINT \"MOVED\"\n"));
    TEST_REQUIRE_STATUS(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 50), AKBASIC_ERR_VALUE);
    /* And nothing moved: the map is built and checked before anything is written. */
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "PRINT \"KEPT\"");
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[55].code, "PRINT \"MOVED\"");
    harness_stop();
}

/** @brief A non-positive increment is refused rather than looping forever. */
static void test_bad_arguments(void)
{
    TEST_REQUIRE_OK(load("10 PRINT \"A\"\n"));
    TEST_REQUIRE_STATUS(akbasic_renumber(&HARNESS_RUNTIME, 10, 0, 0), AKBASIC_ERR_VALUE);
    TEST_REQUIRE_STATUS(akbasic_renumber(&HARNESS_RUNTIME, 10, -1, 0), AKBASIC_ERR_VALUE);
    TEST_REQUIRE_STATUS(akbasic_renumber(&HARNESS_RUNTIME, 0, 10, 0), AKBASIC_ERR_VALUE);
    TEST_REQUIRE_STATUS(akbasic_renumber(NULL, 10, 10, 0), AKERR_NULLPOINTER);
    harness_stop();
}

/** @brief The verb reaches all of it, with its defaults. */
static void test_verb(void)
{
    akbasic_ASTLeaf *leaf = NULL;
    akbasic_Value *out = NULL;

    TEST_REQUIRE_OK(load("5 GOTO 9\n7 PRINT \"SKIPPED\"\n9 PRINT \"ARRIVED\"\n"));
    TEST_REQUIRE_OK(akbasic_environment_zero(HARNESS_RUNTIME.environment));
    TEST_REQUIRE_OK(harness_parse("RENUMBER", &leaf));
    TEST_REQUIRE_OK(akbasic_runtime_evaluate(&HARNESS_RUNTIME, leaf, &out));
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "GOTO 30");
    harness_stop();

    /* And with arguments. */
    TEST_REQUIRE_OK(load("5 GOTO 9\n9 PRINT \"X\"\n"));
    TEST_REQUIRE_OK(akbasic_environment_zero(HARNESS_RUNTIME.environment));
    TEST_REQUIRE_OK(harness_parse("RENUMBER 100, 5", &leaf));
    TEST_REQUIRE_OK(akbasic_runtime_evaluate(&HARNESS_RUNTIME, leaf, &out));
    TEST_REQUIRE_STR(HARNESS_RUNTIME.source[100].code, "GOTO 105");
    harness_stop();
}

int main(void)
{
    test_lines_move();
    test_branches_rewritten();
    test_branch_inside_a_condition();
    test_on_list_rewritten();
    test_other_verbs_rewritten();
    test_string_literals_untouched();
    test_dangling_target_left_alone();
    test_labels_need_no_rewriting();
    test_partial_renumber();
    test_collision_refused();
    test_bad_arguments();
    test_verb();
    return akbasic_test_failures;
}
