// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

// test_vm.c -- Quick test for the DVX BASIC VM
//
// Hand-assembles a small p-code program and executes it.
// Tests: PRINT "Hello, World!", arithmetic, FOR loop, string ops.
//
// Build: make -C dvxbasic tests

#include "compiler/opcodes.h"
#include "runtime/vm.h"
#include "runtime/values.h"

#include <stdio.h>
#include <string.h>

// ============================================================
// Helper: emit bytes into a code buffer
// ============================================================

static uint8_t sCode[4096];
static int32_t sCodeLen = 0;

// Function prototypes
static void emit16(int16_t v);
static void emit8(uint8_t b);
static void emitU16(uint16_t v);
static void test1(void);
static void test2(void);
static void test3(void);
static void test4(void);


static void emit16(int16_t v) {
    memcpy(&sCode[sCodeLen], &v, 2);
    sCodeLen += 2;
}


static void emit8(uint8_t b) {
    sCode[sCodeLen++] = b;
}


static void emitU16(uint16_t v) {
    emit16((int16_t)v);
}


static void test1(void) {
    printf("--- Test 1: PRINT \"Hello, World!\" ---\n");

    sCodeLen = 0;

    // String constant pool
    BasStringT *consts[1];
    consts[0] = basStringNew("Hello, World!", 13);

    // Code: PUSH_STR 0; PRINT; PRINT_NL; HALT
    emit8(OP_PUSH_STR);
    emitU16(0);
    emit8(OP_PRINT);
    emit8(OP_PRINT_NL);
    emit8(OP_HALT);

    BasModuleT module;
    memset(&module, 0, sizeof(module));
    module.code       = sCode;
    module.codeLen    = sCodeLen;
    module.constants  = consts;
    module.constCount = 1;
    module.entryPoint = 0;

    BasVmT *vm = basVmCreate();
    basVmLoadModule(vm, &module);
    BasVmResultE result = basVmRun(vm);
    printf("Result: %d (expected %d = HALTED)\n\n", result, BAS_VM_HALTED);
    basVmDestroy(vm);
    basStringUnref(consts[0]);
}


static void test2(void) {
    printf("--- Test 2: PRINT 2 + 3 * 4 (expect 14) ---\n");

    sCodeLen = 0;

    // Code: PUSH 3; PUSH 4; MUL; PUSH 2; ADD; PRINT; PRINT_NL; HALT
    emit8(OP_PUSH_INT16);
    emit16(3);
    emit8(OP_PUSH_INT16);
    emit16(4);
    emit8(OP_MUL_INT);
    emit8(OP_PUSH_INT16);
    emit16(2);
    emit8(OP_ADD_INT);
    emit8(OP_PRINT);
    emit8(OP_PRINT_NL);
    emit8(OP_HALT);

    BasModuleT module;
    memset(&module, 0, sizeof(module));
    module.code       = sCode;
    module.codeLen    = sCodeLen;
    module.entryPoint = 0;

    BasVmT *vm = basVmCreate();
    basVmLoadModule(vm, &module);
    basVmRun(vm);
    basVmDestroy(vm);
    printf("\n");
}


static void test3(void) {
    printf("--- Test 3: PRINT \"Hello\" & \" \" & \"BASIC\" ---\n");

    sCodeLen = 0;

    BasStringT *consts[3];
    consts[0] = basStringNew("Hello", 5);
    consts[1] = basStringNew(" ", 1);
    consts[2] = basStringNew("BASIC", 5);

    // Code: PUSH consts[0]; PUSH consts[1]; CONCAT; PUSH consts[2]; CONCAT; PRINT; PRINT_NL; HALT
    emit8(OP_PUSH_STR); emitU16(0);
    emit8(OP_PUSH_STR); emitU16(1);
    emit8(OP_STR_CONCAT);
    emit8(OP_PUSH_STR); emitU16(2);
    emit8(OP_STR_CONCAT);
    emit8(OP_PRINT);
    emit8(OP_PRINT_NL);
    emit8(OP_HALT);

    BasModuleT module;
    memset(&module, 0, sizeof(module));
    module.code       = sCode;
    module.codeLen    = sCodeLen;
    module.constants  = consts;
    module.constCount = 3;
    module.entryPoint = 0;

    BasVmT *vm = basVmCreate();
    basVmLoadModule(vm, &module);
    basVmRun(vm);
    basVmDestroy(vm);
    printf("\n");

    basStringUnref(consts[0]);
    basStringUnref(consts[1]);
    basStringUnref(consts[2]);
}


static void test4(void) {
    printf("--- Test 4: FOR i = 1 TO 5: PRINT i: NEXT ---\n");

    sCodeLen = 0;

    // We need a call frame with at least 1 local (the loop variable)
    // For module-level code, we use callStack[0] as implicit frame

    // Setup: store initial value in local 0
    // PUSH 1; STORE_LOCAL 0  -- i = 1
    emit8(OP_PUSH_INT16); emit16(1);
    emit8(OP_STORE_LOCAL); emitU16(0);

    // Push limit and step for FOR_INIT
    // PUSH 5 (limit); PUSH 1 (step)
    emit8(OP_PUSH_INT16); emit16(5);
    emit8(OP_PUSH_INT16); emit16(1);
    emit8(OP_FOR_INIT); emitU16(0); emit8(1); emit16(0); // scope=local, skipOffset patched below

    // Loop body start (record PC for FOR_NEXT offset)
    int32_t loopBody = sCodeLen;

    // LOAD_LOCAL 0; PRINT; PRINT " "
    emit8(OP_LOAD_LOCAL); emitU16(0);
    emit8(OP_PRINT);

    // FOR_NEXT: increment i, test, jump back
    emit8(OP_FOR_NEXT);
    emitU16(0);  // local index
    emit8(SCOPE_LOCAL);
    int16_t offset = (int16_t)(loopBody - (sCodeLen + 2));
    emit16(offset);

    // After loop
    emit8(OP_PRINT_NL);
    emit8(OP_HALT);

    BasModuleT module;
    memset(&module, 0, sizeof(module));
    module.code       = sCode;
    module.codeLen    = sCodeLen;
    module.entryPoint = 0;

    BasVmT *vm = basVmCreate();

    // Initialize the implicit main frame with 1 local
    vm->callStack[0].localCount = 1;
    vm->callDepth = 1;

    basVmLoadModule(vm, &module);
    basVmRun(vm);
    basVmDestroy(vm);
    printf("\n");
}


int main(void) {
    printf("DVX BASIC VM Tests\n");
    printf("==================\n\n");

    test1();
    test2();
    test3();
    test4();

    printf("All tests complete.\n");
    return 0;
}
