/**
 * @file value_arithmetic.c
 * @brief Tests the arithmetic operators, including the parts that look wrong.
 *
 * Where the reference does something odd, the assertion here pins the reference's
 * answer, not the tidy one. A "fix" that changes any of these changes observable
 * BASIC behaviour and belongs in its own commit with its own TODO.md entry.
 */

#include <string.h>

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

#include "testutil.h"

static akbasic_Value A;
static akbasic_Value B;
static akbasic_Value SCRATCH;

static void set_int(akbasic_Value *v, int64_t n)
{
    test_discard_error(akbasic_value_zero(v));
    v->valuetype = AKBASIC_TYPE_INTEGER;
    v->intval = n;
}

static void set_float(akbasic_Value *v, double n)
{
    test_discard_error(akbasic_value_zero(v));
    v->valuetype = AKBASIC_TYPE_FLOAT;
    v->floatval = n;
}

static void set_string(akbasic_Value *v, const char *s)
{
    test_discard_error(akbasic_value_zero(v));
    v->valuetype = AKBASIC_TYPE_STRING;
    strncpy(v->stringval, s, AKBASIC_MAX_STRING_LENGTH - 1);
}

/**
 * @brief A string that exactly fills the value's inline buffer round-trips.
 *
 * Written because mutation testing said nothing would have noticed if it did
 * not: `AKBASIC_MAX_STRING_LENGTH - 1` mutated to `+ 1` and to `- 0` survived at
 * both the strncpy and the NUL terminator in set_string(), and no test in the
 * suite wrote a maximum-length string. Both mutants are a real bug of that shape
 * -- one truncates a string that fits, the other writes one byte past the end of
 * a 256-byte buffer -- and the boundary is the only place either shows.
 *
 * Concatenation rather than a direct set, because set_string() is static and
 * concatenation is how every string in a BASIC program actually gets built.
 *
 * **The two operands are deliberately different lengths and different letters.**
 * A first attempt joined a full-length string to an empty one, and the
 * truncating mutant survived it: math_plus clones self into the scratch before
 * writing, so the byte a short copy failed to write was already the right one.
 * The parts have to sum to the limit without either of them being the answer.
 */
static void test_maximum_length_string(void)
{
    enum { LIMIT = AKBASIC_MAX_STRING_LENGTH - 1, HEAD = LIMIT - 55, TAIL = 55 };
    akbasic_Value *out = NULL;
    char head[AKBASIC_MAX_STRING_LENGTH];
    char tail[AKBASIC_MAX_STRING_LENGTH];
    char joined[AKBASIC_MAX_STRING_LENGTH];

    memset(head, 'X', HEAD);
    head[HEAD] = '\0';
    memset(tail, 'Z', TAIL);
    tail[TAIL] = '\0';
    /* memcpy rather than snprintf: the lengths are known here and -Wformat-truncation
     * cannot see that HEAD + TAIL is exactly the buffer's capacity. */
    memcpy(joined, head, HEAD);
    memcpy(joined + HEAD, tail, TAIL);
    joined[HEAD + TAIL] = '\0';

    /* Exactly the limit: every byte lands, and the terminator is where it should be. */
    set_string(&A, head);
    set_string(&B, tail);
    TEST_REQUIRE_OK(akbasic_value_math_plus(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_INT(strlen(out->stringval), LIMIT);
    TEST_REQUIRE_STR(out->stringval, joined);
    TEST_REQUIRE_INT(out->stringval[LIMIT - 1], 'Z');
    TEST_REQUIRE_INT(out->stringval[LIMIT], '\0');

    /* One character past it is an error, not a silent truncation. */
    set_string(&A, joined);
    set_string(&B, "!");
    TEST_REQUIRE_STATUS(akbasic_value_math_plus(&A, &B, &SCRATCH, &out), AKBASIC_ERR_VALUE);

    /* The same boundary through the repeat operator, which has its own copy. */
    memset(head, 'Q', LIMIT / 2);
    head[LIMIT / 2] = '\0';
    set_string(&A, head);
    set_int(&B, 2);
    TEST_REQUIRE_OK(akbasic_value_math_multiply(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_INT(strlen(out->stringval), (LIMIT / 2) * 2);
    TEST_REQUIRE_INT(out->stringval[((LIMIT / 2) * 2) - 1], 'Q');
}

/**
 * @brief A freshly initialised value pool is empty, and its storage is zeroed.
 *
 * Also a mutation-driven test: `memset(obj, 0, ...)` mutated to `memset(obj, 1,
 * ...)`, the memset deleted outright, and `obj->next = 0` mutated to `= 1` all
 * survived, because nothing asserted what an initialised pool looks like. The
 * deleted-memset mutant is the one that matters -- a pool over stale stack
 * memory hands a BASIC program somebody else's array contents.
 */
static void test_pool_starts_empty(void)
{
    static akbasic_ValuePool pool;
    akbasic_Value *slice = NULL;
    int i = 0;

    /* Dirty every byte first, so a missing memset cannot pass by luck. */
    memset(&pool, 0xAB, sizeof(pool));
    TEST_REQUIRE_OK(akbasic_valuepool_init(&pool));

    TEST_REQUIRE_INT(pool.next, 0);
    for ( i = 0; i < AKBASIC_MAX_ARRAY_VALUES; i++ ) {
	if ( pool.values[i].valuetype != 0 || pool.values[i].intval != 0 ||
	     pool.values[i].stringval[0] != '\0' ) {
	    TEST_REQUIRE(false, "pool slot %d was not zeroed by init", i);
	    break;
	}
    }

    /* The first take starts at slot zero and the bump advances by exactly count. */
    TEST_REQUIRE_OK(akbasic_valuepool_take(&pool, 4, &slice));
    TEST_REQUIRE(slice == &pool.values[0], "the first take must start at slot zero");
    TEST_REQUIRE_INT(pool.next, 4);
    TEST_REQUIRE_OK(akbasic_valuepool_take(&pool, 1, &slice));
    TEST_REQUIRE(slice == &pool.values[4], "the second take must start where the first ended");
    TEST_REQUIRE_INT(pool.next, 5);

    /* Re-initialising takes it back to empty rather than merely rewinding. */
    TEST_REQUIRE_OK(akbasic_valuepool_init(&pool));
    TEST_REQUIRE_INT(pool.next, 0);

    TEST_REQUIRE_STATUS(akbasic_valuepool_init(NULL), AKERR_NULLPOINTER);
    TEST_REQUIRE_STATUS(akbasic_valuepool_take(&pool, 0, &slice), AKBASIC_ERR_BOUNDS);
    TEST_REQUIRE_STATUS(akbasic_valuepool_take(&pool, AKBASIC_MAX_ARRAY_VALUES + 1, &slice),
			AKBASIC_ERR_BOUNDS);
}

/**
 * @brief The left operand alone decides integer or float arithmetic.
 *
 * **This is the documented contract, not an accident**, which is the whole
 * reason it is asserted: every operator branches on `self->valuetype` and
 * converts the right operand to match, so an integer on the left truncates a
 * float on the right and `A# * 0.45` is 0 where `0.45 * A#` is 1.35.
 *
 * It is inherited from the Go reference (basicvalue.go:190-193) and a C128
 * promotes to float instead. It may well be the wrong choice -- what makes it
 * expensive is that **nothing fails**, so a program computes something else and
 * carries on; a game written against this interpreter lost a per-level speed
 * increase and bled velocity out of every bounce before either was noticed.
 *
 * Changing it to promotion is a real change of dialect semantics and wants its
 * own decision. This test is here so that it has to be one: docs/03 and docs/13
 * describe the behaviour below, and a change that makes them wrong fails here
 * first. TODO.md section 9 item 4.
 */
static void test_left_operand_decides_the_type(void)
{
    akbasic_Value *out = NULL;

    /* Integer on the left: the float on the right is truncated to 0. */
    set_int(&A, 3);
    set_float(&B, 0.45);
    TEST_REQUIRE_OK(akbasic_value_math_multiply(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_INT(out->valuetype, AKBASIC_TYPE_INTEGER);
    TEST_REQUIRE_INT(out->intval, 0);

    /* Float on the left: the integer on the right is widened. */
    set_float(&A, 0.45);
    set_int(&B, 3);
    TEST_REQUIRE_OK(akbasic_value_math_multiply(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_INT(out->valuetype, AKBASIC_TYPE_FLOAT);
    TEST_REQUIRE_FEQ(out->floatval, 1.35);

    /* The same asymmetry in subtraction, which is how a program negates. */
    set_int(&A, 0);
    set_float(&B, 6.4);
    TEST_REQUIRE_OK(akbasic_value_math_minus(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_INT(out->valuetype, AKBASIC_TYPE_INTEGER);
    TEST_REQUIRE_INT(out->intval, -6);

    set_float(&A, 0.0);
    set_float(&B, 6.4);
    TEST_REQUIRE_OK(akbasic_value_math_minus(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_INT(out->valuetype, AKBASIC_TYPE_FLOAT);
    TEST_REQUIRE_FEQ(out->floatval, -6.4);
}

int main(void)
{
    akbasic_Value *out = NULL;
    char rendered[AKBASIC_MAX_STRING_LENGTH];

    TEST_REQUIRE_OK(akbasic_error_register());

    /* Integer arithmetic. */
    set_int(&A, 7);
    set_int(&B, 3);
    TEST_REQUIRE_OK(akbasic_value_math_plus(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_INT(out->intval, 10);
    TEST_REQUIRE_OK(akbasic_value_math_minus(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_INT(out->intval, 4);
    TEST_REQUIRE_OK(akbasic_value_math_multiply(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_INT(out->intval, 21);
    TEST_REQUIRE_OK(akbasic_value_math_divide(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_INT(out->intval, 2);	/* truncating, as C and Go both do */

    /* Float arithmetic. */
    set_float(&A, 1.20);
    set_float(&B, 0.4);
    TEST_REQUIRE_OK(akbasic_value_math_divide(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_OK(akbasic_value_to_string(out, rendered, sizeof(rendered)));
    /* tests/language/arithmetic/float.txt says 3.000000, not 2.999999. */
    TEST_REQUIRE_STR(rendered, "3.000000");

    /* String concatenation, including the mixed-type forms. */
    set_string(&A, "SORTED IN ");
    set_int(&B, 4);
    TEST_REQUIRE_OK(akbasic_value_math_plus(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_STR(out->stringval, "SORTED IN 4");

    set_string(&A, "X=");
    set_float(&B, 2.5);
    TEST_REQUIRE_OK(akbasic_value_math_plus(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_STR(out->stringval, "X=2.500000");

    /* String multiplication is repetition. */
    set_string(&A, "ab");
    set_int(&B, 3);
    TEST_REQUIRE_OK(akbasic_value_math_multiply(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_STR(out->stringval, "ababab");

    /*
     * math_plus clones like every other operator, mutable operand or not.
     *
     * This used to assert the opposite. The reference mutates `self` in place
     * when it is mutable, which made `A# + 1` modify `A#` whenever the left
     * operand came out of a variable -- TODO.md section 6 item 4. It was left
     * alone because NEXT's loop increment relied on the mutation to advance the
     * counter; NEXT writes the result back itself now, so the asymmetry is gone
     * and this asserts that it stays gone.
     */
    set_int(&A, 10);
    A.mutable_ = true;
    set_int(&B, 5);
    TEST_REQUIRE_OK(akbasic_value_math_plus(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE(out == &SCRATCH, "math_plus must use the scratch even for a mutable operand");
    TEST_REQUIRE_INT(A.intval, 10);
    TEST_REQUIRE_INT(SCRATCH.intval, 15);

    set_int(&A, 10);
    A.mutable_ = false;
    TEST_REQUIRE_OK(akbasic_value_math_plus(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE(out == &SCRATCH, "math_plus on an immutable value must use the scratch");
    TEST_REQUIRE_INT(A.intval, 10);

    /*
     * The unused numeric field is ignored, not added in.
     *
     * The reference reads a right-hand operand as `intval + int64(floatval)`,
     * which gives the right answer only for as long as whichever field is unused
     * happens to be zero -- TODO.md section 6 item 5, filed as a landmine
     * because no program can reach it. It is reachable from here: a value is a
     * plain struct and nothing stops one carrying both. Without this the fix is
     * untested, and the old code passes every other test in the tree.
     */
    set_int(&A, 2);
    set_int(&B, 5);
    B.floatval = 3.0;			/* stale, from whatever B held before */
    TEST_REQUIRE_OK(akbasic_value_math_plus(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_INT(out->intval, 7);

    set_float(&A, 2.0);
    set_float(&B, 5.0);
    B.intval = 3;			/* stale the other way round */
    TEST_REQUIRE_OK(akbasic_value_math_plus(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_FEQ(out->floatval, 7.0);

    /* And a truth value used as a number is -1, which is what AND/OR rely on. */
    set_int(&A, 0);
    TEST_REQUIRE_OK(akbasic_value_set_bool(&B, true));
    TEST_REQUIRE_OK(akbasic_value_math_plus(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_INT(out->intval, -1);

    /* Type errors. */
    set_string(&A, "text");
    set_int(&B, 1);
    TEST_REQUIRE_STATUS(akbasic_value_math_minus(&A, &B, &SCRATCH, &out), AKBASIC_ERR_TYPE);
    TEST_REQUIRE_STATUS(akbasic_value_math_divide(&A, &B, &SCRATCH, &out), AKBASIC_ERR_TYPE);
    TEST_REQUIRE_STATUS(akbasic_value_invert(&A, &SCRATCH, &out), AKBASIC_ERR_TYPE);

    /*
     * A left operand the numeric path cannot read is refused, not computed.
     *
     * `math_minus`, `math_multiply` and `math_divide` were `if ( INTEGER ) ...
     * else <treat as float>`, and that else was a catch-all: it read `floatval`
     * from whatever it was handed. A truth value keeps its payload in
     * `boolvalue` and leaves `floatval` zero, so the operation computed into a
     * field nothing reads and left the type alone -- `(A# == 1) - 1` rendered
     * `true` rather than -2. Silent, and wrong in both value and type.
     *
     * `math_plus` never had it, because it enumerates its cases and ends in an
     * error. These assert the other three now agree with it.
     */
    TEST_REQUIRE_OK(akbasic_value_set_bool(&A, true));
    set_int(&B, 1);
    TEST_REQUIRE_STATUS(akbasic_value_math_minus(&A, &B, &SCRATCH, &out), AKBASIC_ERR_TYPE);
    TEST_REQUIRE_STATUS(akbasic_value_math_multiply(&A, &B, &SCRATCH, &out), AKBASIC_ERR_TYPE);
    TEST_REQUIRE_STATUS(akbasic_value_math_divide(&A, &B, &SCRATCH, &out), AKBASIC_ERR_TYPE);
    TEST_REQUIRE_STATUS(akbasic_value_math_plus(&A, &B, &SCRATCH, &out), AKBASIC_ERR_TYPE);

    /* An undefined value is refused for the same reason and by the same guard. */
    TEST_REQUIRE_OK(akbasic_value_zero(&A));
    TEST_REQUIRE_STATUS(akbasic_value_math_minus(&A, &B, &SCRATCH, &out), AKBASIC_ERR_TYPE);
    TEST_REQUIRE_STATUS(akbasic_value_math_multiply(&A, &B, &SCRATCH, &out), AKBASIC_ERR_TYPE);
    TEST_REQUIRE_STATUS(akbasic_value_math_divide(&A, &B, &SCRATCH, &out), AKBASIC_ERR_TYPE);

    /*
     * But a truth value on the *right* stays legal, and that asymmetry is the
     * point: the left operand picks the branch and must be a number, while the
     * right is read through rval_as_int(), which handles -1/0 on purpose. It is
     * the same property that lets AND and OR double as logical operators, so
     * refusing it here would break `5 - (A# == 1)`, which is 6.
     */
    set_int(&A, 5);
    TEST_REQUIRE_OK(akbasic_value_set_bool(&B, true));
    TEST_REQUIRE_OK(akbasic_value_math_minus(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_INT(out->intval, 6);
    TEST_REQUIRE_OK(akbasic_value_math_multiply(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_INT(out->intval, -5);

    /* And a string repeat is not caught by the numeric guard in front of it. */
    set_string(&A, "xy");
    set_int(&B, 2);
    TEST_REQUIRE_OK(akbasic_value_math_multiply(&A, &B, &SCRATCH, &out));
    TEST_REQUIRE_STR(out->stringval, "xyxy");

    /* Division by zero raises rather than trapping or panicking. */
    set_int(&A, 1);
    set_int(&B, 0);
    TEST_REQUIRE_STATUS(akbasic_value_math_divide(&A, &B, &SCRATCH, &out), AKBASIC_ERR_VALUE);

    /* Unary minus. */
    set_int(&A, 42);
    TEST_REQUIRE_OK(akbasic_value_invert(&A, &SCRATCH, &out));
    TEST_REQUIRE_INT(out->intval, -42);

    /* nil rval is rejected everywhere. */
    TEST_REQUIRE_STATUS(akbasic_value_math_plus(&A, NULL, &SCRATCH, &out), AKERR_NULLPOINTER);

    test_maximum_length_string();
    test_pool_starts_empty();
    test_left_operand_decides_the_type();

    return akbasic_test_failures;
}
