// Host-side test for examples/agi/agiVm.c (Phase 1 sub-A).
//
// Loads a LOGIC resource (default 0), runs the VM until it halts,
// and prints the halt reason plus any non-zero vars/flags. Since
// only the arithmetic and flag opcodes (0x00-0x11) are implemented
// this pass, expectation is one of:
//   - HALT_RETURN at offset N  (logic was entirely sub-A opcodes)
//   - HALT_UNKNOWN_OP 0xXX at offset N  (hit IF / new.room / etc.)
//   - HALT_TRUNCATED at offset N  (malformed resource)
//
// Usage:
//   testAgiVm <game-directory> [logic-id]

#include "agi.h"

#include <stdio.h>
#include <stdlib.h>


// ----- Prototypes -----

static const char *haltName(AgiVmHaltE r);
static void        printNonZeroState(const AgiVmT *vm);


// ----- Helpers (alphabetical) -----

static const char *haltName(AgiVmHaltE r) {
    switch (r) {
        case AGI_VM_HALT_NONE:        return "NONE";
        case AGI_VM_HALT_RETURN:      return "RETURN";
        case AGI_VM_HALT_UNKNOWN_OP:  return "UNKNOWN_OP";
        case AGI_VM_HALT_TRUNCATED:   return "TRUNCATED";
        case AGI_VM_HALT_NEW_ROOM:    return "NEW_ROOM";
        default:                      return "?";
    }
}


static void printNonZeroState(const AgiVmT *vm) {
    uint16_t  i;
    uint16_t  nonZeroVars;
    uint16_t  nonZeroFlags;

    nonZeroVars  = 0u;
    nonZeroFlags = 0u;
    for (i = 0u; i < AGI_VM_NUM_VARS; i++) {
        if (vm->vars[i] != 0u) {
            if (nonZeroVars == 0u) {
                printf("  vars:");
            }
            printf("  v%u=%u", (unsigned)i, (unsigned)vm->vars[i]);
            nonZeroVars++;
        }
    }
    if (nonZeroVars > 0u) {
        printf("\n");
    }
    for (i = 0u; i < AGI_VM_NUM_FLAGS; i++) {
        if (vm->flags[i] != 0u) {
            if (nonZeroFlags == 0u) {
                printf("  flags:");
            }
            printf("  f%u", (unsigned)i);
            nonZeroFlags++;
        }
    }
    if (nonZeroFlags > 0u) {
        printf("\n");
    }
}


// ----- main -----

int main(int argc, char **argv) {
    AgiGameT     game;
    AgiVmT       vm;
    uint16_t     logicId;
    uint8_t     *bytes;
    uint16_t     length;
    AgiVmHaltE   reason;

    if (argc < 2 || argc > 3) {
        fprintf(stderr, "usage: %s <game-directory> [logic-id]\n", argv[0]);
        return 2;
    }

    if (!agiResOpen(&game)) {
        fprintf(stderr, "FAIL: agiResOpen rejected '%s'\n", argv[1]);
        return 1;
    }

    logicId = (argc == 3) ? (uint16_t)atoi(argv[2]) : 0u;
    if (logicId >= game.resCount[AGI_RES_LOGIC]) {
        fprintf(stderr, "FAIL: no LOGIC at id %u\n", (unsigned)logicId);
        agiResClose(&game);
        return 1;
    }

    bytes = agiResLoad(&game, AGI_RES_LOGIC, logicId, &length);
    if (bytes == NULL) {
        fprintf(stderr, "FAIL: LOGIC %u failed to load\n", (unsigned)logicId);
        agiResClose(&game);
        return 1;
    }

    agiVmInit(&vm);
    if (!agiVmLoadLogic(&vm, bytes, length)) {
        fprintf(stderr, "FAIL: agiVmLoadLogic rejected LOGIC %u (truncated header?)\n", (unsigned)logicId);
        free(bytes);
        agiResClose(&game);
        return 1;
    }

    printf("LOGIC %u: %u bytes resource, %u bytes bytecode\n",
           (unsigned)logicId, (unsigned)length, (unsigned)vm.codeLength);

    reason = agiVmRun(&vm);

    printf("  halt=%s at pc=%u/%u",
           haltName(reason), (unsigned)vm.pc, (unsigned)vm.codeLength);
    if (reason == AGI_VM_HALT_UNKNOWN_OP) {
        printf("  opcode=0x%02X", (unsigned)vm.lastUnknownOp);
    }
    if (reason == AGI_VM_HALT_NEW_ROOM) {
        printf("  newRoom=%u", (unsigned)vm.newRoomId);
    }
    printf("\n");
    printNonZeroState(&vm);

    free(bytes);
    agiResClose(&game);
    return 0;
}
