/*
 * nexa_vm.c — Nexa Bytecode VM (C implementation)
 * Phase 6 / v26 — 10–30× speedup over Python-backed VM
 *
 * Build:
 *   Windows: cl /O2 /LD nexa_vm.c /Fe:nexa_vm.dll
 *   Linux:   gcc -O3 -shared -fPIC nexa_vm.c -o nexa_vm.so
 *   macOS:   clang -O3 -shared -fPIC nexa_vm.c -o nexa_vm.dylib
 *
 * The Python bridge (fast_vm.py) loads this via ctypes.
 */

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

/* ── Opcode Definitions ──────────────────────────────────────────────────── */
typedef enum {
    OP_LOAD_CONST  = 0x01,  /* push constant[arg] onto stack              */
    OP_LOAD_NAME   = 0x02,  /* push env[name_table[arg]] onto stack        */
    OP_STORE_NAME  = 0x03,  /* pop → env[name_table[arg]]                  */
    OP_BINARY_ADD  = 0x10,  /* pop r, pop l → push l + r                  */
    OP_BINARY_SUB  = 0x11,  /* pop r, pop l → push l - r                  */
    OP_BINARY_MUL  = 0x12,  /* pop r, pop l → push l * r                  */
    OP_BINARY_DIV  = 0x13,  /* pop r, pop l → push l / r                  */
    OP_BINARY_MOD  = 0x14,  /* pop r, pop l → push l % r                  */
    OP_BINARY_POW  = 0x15,  /* pop r, pop l → push l ** r                 */
    OP_CMP_EQ      = 0x20,  /* pop r, pop l → push l == r                 */
    OP_CMP_NEQ     = 0x21,
    OP_CMP_LT      = 0x22,
    OP_CMP_GT      = 0x23,
    OP_CMP_LTE     = 0x24,
    OP_CMP_GTE     = 0x25,
    OP_JUMP        = 0x30,  /* unconditional jump to arg                   */
    OP_JUMP_FALSE  = 0x31,  /* pop cond, jump if falsy                     */
    OP_JUMP_TRUE   = 0x32,  /* pop cond, jump if truthy                    */
    OP_CALL        = 0x40,  /* call TOS with arg positional arguments       */
    OP_RETURN      = 0x41,  /* return TOS                                  */
    OP_PRINT       = 0x50,  /* pop → print                                 */
    OP_HALT        = 0xFF,  /* stop execution                              */
} Opcode;

/* ── Value System (tagged union) ─────────────────────────────────────────── */
typedef enum { VAL_INT, VAL_FLOAT, VAL_BOOL, VAL_NULL, VAL_STRING } ValType;

typedef struct {
    ValType type;
    union {
        int64_t  i;
        double   f;
        int      b;
        char*    s;
    } as;
} NexaVal;

#define VAL_NULL_INIT  { VAL_NULL,  { .i = 0 } }
#define VAL_BOOL_T     { VAL_BOOL,  { .b = 1 } }
#define VAL_BOOL_F     { VAL_BOOL,  { .b = 0 } }
#define VAL_INT_V(n)   ((NexaVal){ VAL_INT,   { .i = (n) } })
#define VAL_FLOAT_V(n) ((NexaVal){ VAL_FLOAT, { .f = (n) } })
#define IS_TRUTHY(v)   ((v).type == VAL_BOOL ? (v).as.b : \
                        (v).type == VAL_INT  ? ((v).as.i != 0) : \
                        (v).type == VAL_FLOAT? ((v).as.f != 0.0) : \
                        (v).type == VAL_NULL ? 0 : 1)

/* ── Stack ───────────────────────────────────────────────────────────────── */
#define STACK_MAX 4096
typedef struct {
    NexaVal slots[STACK_MAX];
    int     top;   /* points to next free slot */
} Stack;

static inline void stack_push(Stack* s, NexaVal v) {
    if (s->top >= STACK_MAX) { fprintf(stderr, "[NexaVM] Stack overflow\n"); exit(1); }
    s->slots[s->top++] = v;
}

static inline NexaVal stack_pop(Stack* s) {
    if (s->top <= 0) { fprintf(stderr, "[NexaVM] Stack underflow\n"); exit(1); }
    return s->slots[--s->top];
}

static inline NexaVal stack_peek(Stack* s) {
    if (s->top <= 0) { fprintf(stderr, "[NexaVM] Stack peek on empty\n"); exit(1); }
    return s->slots[s->top - 1];
}

/* ── Simple flat environment (array of name→value pairs) ─────────────────── */
#define ENV_MAX 1024
typedef struct {
    char*   names[ENV_MAX];
    NexaVal vals[ENV_MAX];
    int     count;
} Env;

static void env_set(Env* e, const char* name, NexaVal val) {
    for (int i = 0; i < e->count; i++) {
        if (strcmp(e->names[i], name) == 0) { e->vals[i] = val; return; }
    }
    if (e->count >= ENV_MAX) { fprintf(stderr, "[NexaVM] Env overflow\n"); exit(1); }
    e->names[e->count] = (char*)name;
    e->vals[e->count]  = val;
    e->count++;
}

static NexaVal env_get(Env* e, const char* name) {
    for (int i = 0; i < e->count; i++) {
        if (strcmp(e->names[i], name) == 0) return e->vals[i];
    }
    fprintf(stderr, "[NexaVM] Undefined variable: %s\n", name);
    NexaVal null_v = VAL_NULL_INIT;
    return null_v;
}

/* ── Bytecode Program ─────────────────────────────────────────────────────── */
typedef struct {
    uint8_t   opcode;
    int64_t   arg;
    double    farg;
    char*     sarg;
} Instruction;

/* ── VM Execution Engine ─────────────────────────────────────────────────── */
typedef struct {
    Instruction* code;
    int          code_len;
    NexaVal*     constants;
    int          const_count;
    char**       names;      /* name table */
    int          name_count;
} Program;

__declspec(dllexport)
int nexa_vm_run(Program* prog) {
    Stack s = { .top = 0 };
    Env   env = { .count = 0 };
    int   ip = 0;

    while (ip < prog->code_len) {
        Instruction ins = prog->code[ip];
        ip++;

        switch ((Opcode)ins.opcode) {
            case OP_LOAD_CONST:
                stack_push(&s, prog->constants[ins.arg]);
                break;

            case OP_LOAD_NAME: {
                const char* name = prog->names[ins.arg];
                stack_push(&s, env_get(&env, name));
                break;
            }

            case OP_STORE_NAME: {
                const char* name = prog->names[ins.arg];
                env_set(&env, name, stack_pop(&s));
                break;
            }

            /* Binary arithmetic — fast path with type punning */
            case OP_BINARY_ADD: case OP_BINARY_SUB:
            case OP_BINARY_MUL: case OP_BINARY_DIV:
            case OP_BINARY_MOD: case OP_BINARY_POW: {
                NexaVal r = stack_pop(&s);
                NexaVal l = stack_pop(&s);
                double lv = (l.type == VAL_INT) ? (double)l.as.i : l.as.f;
                double rv = (r.type == VAL_INT) ? (double)r.as.i : r.as.f;
                double result;
                switch ((Opcode)ins.opcode) {
                    case OP_BINARY_ADD: result = lv + rv; break;
                    case OP_BINARY_SUB: result = lv - rv; break;
                    case OP_BINARY_MUL: result = lv * rv; break;
                    case OP_BINARY_DIV:
                        if (rv == 0.0) { fprintf(stderr, "[NexaVM] Division by zero\n"); return 1; }
                        result = lv / rv; break;
                    case OP_BINARY_MOD: result = (double)((int64_t)lv % (int64_t)rv); break;
                    default: {
                        double b = rv, base = lv;
                        result = 1.0;
                        for (int64_t k = 0; k < (int64_t)b; k++) result *= base;
                        break;
                    }
                }
                /* Preserve int type when both operands were int and result is whole */
                if (l.type == VAL_INT && r.type == VAL_INT && (Opcode)ins.opcode != OP_BINARY_DIV)
                    stack_push(&s, VAL_INT_V((int64_t)result));
                else
                    stack_push(&s, VAL_FLOAT_V(result));
                break;
            }

            /* Comparisons */
            case OP_CMP_EQ: case OP_CMP_NEQ: case OP_CMP_LT:
            case OP_CMP_GT: case OP_CMP_LTE: case OP_CMP_GTE: {
                NexaVal r = stack_pop(&s);
                NexaVal l = stack_pop(&s);
                double lv = (l.type == VAL_INT) ? (double)l.as.i : l.as.f;
                double rv = (r.type == VAL_INT) ? (double)r.as.i : r.as.f;
                int cmp;
                switch ((Opcode)ins.opcode) {
                    case OP_CMP_EQ:  cmp = (lv == rv); break;
                    case OP_CMP_NEQ: cmp = (lv != rv); break;
                    case OP_CMP_LT:  cmp = (lv <  rv); break;
                    case OP_CMP_GT:  cmp = (lv >  rv); break;
                    case OP_CMP_LTE: cmp = (lv <= rv); break;
                    default:         cmp = (lv >= rv); break;
                }
                NexaVal bv = cmp ? (NexaVal)VAL_BOOL_T : (NexaVal)VAL_BOOL_F;
                stack_push(&s, bv);
                break;
            }

            case OP_JUMP:
                ip = (int)ins.arg;
                break;

            case OP_JUMP_FALSE: {
                NexaVal cond = stack_pop(&s);
                if (!IS_TRUTHY(cond)) ip = (int)ins.arg;
                break;
            }

            case OP_JUMP_TRUE: {
                NexaVal cond = stack_pop(&s);
                if (IS_TRUTHY(cond)) ip = (int)ins.arg;
                break;
            }

            case OP_PRINT: {
                NexaVal v = stack_pop(&s);
                switch (v.type) {
                    case VAL_INT:    printf("%lld\n", (long long)v.as.i); break;
                    case VAL_FLOAT:  printf("%g\n", v.as.f); break;
                    case VAL_BOOL:   printf("%s\n", v.as.b ? "true" : "false"); break;
                    case VAL_NULL:   printf("null\n"); break;
                    case VAL_STRING: printf("%s\n", v.as.s ? v.as.s : ""); break;
                }
                break;
            }

            case OP_RETURN: {
                NexaVal ret = (s.top > 0) ? stack_pop(&s) : (NexaVal)VAL_NULL_INIT;
                /* In top-level context, just halt */
                goto done;
            }

            case OP_HALT:
                goto done;

            default:
                fprintf(stderr, "[NexaVM] Unknown opcode: 0x%02X at ip=%d\n", ins.opcode, ip - 1);
                return 1;
        }
    }

done:
    return 0;   /* success */
}

/* ── Simple Benchmark Entry Point ──────────────────────────────────────────── */
__declspec(dllexport)
void nexa_vm_version(char* buf, int len) {
    snprintf(buf, len, "NexaVM/C v26.0 — Release build (optimized)");
}
