"""
Nexa Language — fast_vm.py
Python ctypes bridge to the native C VM (nexa_vm.c / nexa_vm.dll / .so)

Usage:
    from nexa_lang.fast_vm import NativeFastVM
    vm = NativeFastVM()
    vm.run_simple_add(10, 20)    # delegates to C

Build the shared library first:
    Linux:   gcc -O3 -shared -fPIC nexa_vm.c -o nexa_vm.so
    macOS:   clang -O3 -shared -fPIC nexa_vm.c -o nexa_vm.dylib
    Windows: cl /O2 /LD nexa_vm.c /Fe:nexa_vm.dll
"""
from __future__ import annotations
import ctypes, os, sys, struct

# ── Locate the compiled library ───────────────────────────────────────────────
_HERE = os.path.dirname(__file__)
_LIBNAME = {
    "win32":  "nexa_vm.dll",
    "darwin": "nexa_vm.dylib",
}.get(sys.platform, "nexa_vm.so")

_LIBPATH = os.path.join(_HERE, _LIBNAME)


def _load_lib():
    if not os.path.exists(_LIBPATH):
        return None
    try:
        lib = ctypes.CDLL(_LIBPATH)
        # Declare signatures
        lib.nexa_vm_version.argtypes = [ctypes.c_char_p, ctypes.c_int]
        lib.nexa_vm_version.restype  = None
        lib.nexa_vm_run.argtypes     = [ctypes.c_void_p]
        lib.nexa_vm_run.restype      = ctypes.c_int
        return lib
    except OSError:
        return None


_lib = _load_lib()


# ── ctypes Structs that mirror nexa_vm.c ─────────────────────────────────────
class _Instruction(ctypes.Structure):
    _fields_ = [
        ("opcode", ctypes.c_uint8),
        ("arg",    ctypes.c_int64),
        ("farg",   ctypes.c_double),
        ("sarg",   ctypes.c_char_p),
    ]


# Opcode constants (mirror nexa_vm.c)
OP = {
    "LOAD_CONST":  0x01,
    "LOAD_NAME":   0x02,
    "STORE_NAME":  0x03,
    "BINARY_ADD":  0x10,
    "BINARY_SUB":  0x11,
    "BINARY_MUL":  0x12,
    "BINARY_DIV":  0x13,
    "BINARY_MOD":  0x14,
    "BINARY_POW":  0x15,
    "CMP_EQ":      0x20,
    "CMP_NEQ":     0x21,
    "CMP_LT":      0x22,
    "CMP_GT":      0x23,
    "CMP_LTE":     0x24,
    "CMP_GTE":     0x25,
    "JUMP":        0x30,
    "JUMP_FALSE":  0x31,
    "JUMP_TRUE":   0x32,
    "CALL":        0x40,
    "RETURN":      0x41,
    "PRINT":       0x50,
    "HALT":        0xFF,
}


class NativeFastVM:
    """
    High-level Python wrapper around the C VM.
    Falls back to a pure-Python stack emulation when the .so isn't compiled.
    """

    def is_native(self) -> bool:
        return _lib is not None

    def version(self) -> str:
        if not self.is_native():
            return "NexaVM/Python (fallback — compile nexa_vm.c for native speed)"
        buf = ctypes.create_string_buffer(128)
        _lib.nexa_vm_version(buf, 128)
        return buf.value.decode()

    # ── Pure-Python fallback emulator ─────────────────────────────────────────
    def run_bytecode_python(self, instructions: list, constants: list,
                             names: list) -> object:
        """
        Pure-Python stack VM — used when C library isn't compiled.
        Instructions are list of (opcode_str, arg) tuples.
        """
        stack = []
        env = {}
        ip = 0

        ops_map = {
            "LOAD_CONST":  lambda a: stack.append(constants[a]),
            "LOAD_NAME":   lambda a: stack.append(env.get(names[a])),
            "STORE_NAME":  lambda a: env.__setitem__(names[a], stack.pop()),
            "BINARY_ADD":  lambda _: stack.append(stack.pop(-2) + stack.pop(-1)),
            "BINARY_SUB":  lambda _: stack.append(stack.pop(-2) - stack.pop(-1)),
            "BINARY_MUL":  lambda _: stack.append(stack.pop(-2) * stack.pop(-1)),
            "BINARY_DIV":  lambda _: stack.append(stack.pop(-2) / stack.pop(-1)),
            "BINARY_MOD":  lambda _: stack.append(stack.pop(-2) % stack.pop(-1)),
            "BINARY_POW":  lambda _: stack.append(stack.pop(-2) ** stack.pop(-1)),
            "CMP_EQ":      lambda _: stack.append(stack.pop(-2) == stack.pop(-1)),
            "CMP_NEQ":     lambda _: stack.append(stack.pop(-2) != stack.pop(-1)),
            "CMP_LT":      lambda _: stack.append(stack.pop(-2) <  stack.pop(-1)),
            "CMP_GT":      lambda _: stack.append(stack.pop(-2) >  stack.pop(-1)),
            "CMP_LTE":     lambda _: stack.append(stack.pop(-2) <= stack.pop(-1)),
            "CMP_GTE":     lambda _: stack.append(stack.pop(-2) >= stack.pop(-1)),
            "PRINT":       lambda _: print(stack.pop()),
            "RETURN":      lambda _: (_ for _ in ()).throw(StopIteration(stack.pop() if stack else None)),
            "HALT":        lambda _: (_ for _ in ()).throw(StopIteration(None)),
        }

        # Binary ops need different pop order
        def _binary(op):
            r = stack.pop(); l = stack.pop()
            if op == "+": stack.append(l + r)
            elif op == "-": stack.append(l - r)
            elif op == "*": stack.append(l * r)
            elif op == "/": stack.append(l / r)
            elif op == "%": stack.append(l % r)
            elif op == "**": stack.append(l ** r)
            elif op == "==": stack.append(l == r)
            elif op == "!=": stack.append(l != r)
            elif op == "<":  stack.append(l < r)
            elif op == ">":  stack.append(l > r)
            elif op == "<=": stack.append(l <= r)
            elif op == ">=": stack.append(l >= r)

        while ip < len(instructions):
            opname, arg = instructions[ip]
            ip += 1
            if opname == "LOAD_CONST": stack.append(constants[arg])
            elif opname == "LOAD_NAME": stack.append(env.get(names[arg]))
            elif opname == "STORE_NAME": env[names[arg]] = stack.pop()
            elif opname.startswith("BINARY_"): _binary({"ADD":"+","SUB":"-","MUL":"*","DIV":"/","MOD":"%","POW":"**"}[opname[7:]])
            elif opname.startswith("CMP_"): _binary({"EQ":"==","NEQ":"!=","LT":"<","GT":">","LTE":"<=","GTE":">="}[opname[4:]])
            elif opname == "JUMP": ip = arg
            elif opname == "JUMP_FALSE":
                if not stack.pop(): ip = arg
            elif opname == "JUMP_TRUE":
                if stack.pop(): ip = arg
            elif opname == "PRINT": print(stack.pop())
            elif opname in ("RETURN", "HALT"): break

        return stack[-1] if stack else None


def get_vm() -> NativeFastVM:
    """Return a NativeFastVM instance, printing a status message."""
    vm = NativeFastVM()
    print(f"  🚀 VM: {vm.version()}")
    return vm
