/*
 * This file is part of the Fun programming language.
 * https://fun-lang.xyz/
 *
 * Copyright 2025 Johannes Findeisen <you@hanez.org>
 * Licensed under the terms of the Apache-2.0 license.
 * https://opensource.org/license/apache-2-0
 */

/**
 * @file bytecode.h
 * @brief Definitions for the Fun VM bytecode: opcodes, instruction format, and bytecode container API.
 *
 * This header declares the VM's operation codes (@ref OpCode), the compact
 * instruction representation (@ref Instruction), and the owning bytecode
 * container (@ref Bytecode) together with minimal constructor/manipulation
 * helpers. The concrete execution semantics for each opcode are implemented
 * in the VM (see vm.c and vm/some_file.c handlers).
 */
#ifndef FUN_BYTECODE_H
#define FUN_BYTECODE_H

#include "value.h"
#include <stdint.h>

/**
 * @brief VM operation codes executed by the Fun virtual machine.
 *
 * Unless stated otherwise, opcodes operate on the VM stack. Comments on each
 * opcode describe stack effects using a left-to-right pop order and the value
 * pushed as a result. For example, "pops b, a; pushes a+b" means the
 * instruction will pop first b then a from the stack and finally push the
 * result of a+b.
 */
typedef enum {
  OP_NOP,
  OP_LOAD_CONST,  // operand = constant index
  OP_LOAD_LOCAL,  // operand = local slot index
  OP_STORE_LOCAL, // operand = local slot index

  OP_LOAD_GLOBAL,  //
  OP_STORE_GLOBAL, //

  OP_ADD, //
  OP_SUB, //
  OP_MUL, //
  OP_DIV, //

  OP_LT,  // a < b -> push 1/0
  OP_LTE, // a <= b -> push 1/0
  OP_GT,  // a > b -> push 1/0
  OP_GTE, // a >= b -> push 1/0
  OP_EQ,  // a == b -> push 1/0
  OP_NEQ, // a != b -> push 1/0

  OP_POP,           // discard top of stack
  OP_JUMP,          // unconditional jump
  OP_JUMP_IF_FALSE, // jump if top of stack is false (0)

  OP_CALL,   // operand = arg count; pops fn + args and enters fn
  OP_RETURN, // pop optional return value and return to caller

  OP_PRINT,
  OP_ECHO, // like print but does not append a newline; prints immediately
  OP_HALT,

  OP_LINE, // operand = source line number (debug marker)

  // add after existing opcodes
  OP_MOD, // a % b
  OP_AND, // logical AND
  OP_OR,  // logical OR
  OP_NOT, // logical NOT

  OP_DUP,  // duplicate top of stack
  OP_SWAP, // swap top two stack values

  // arrays
  OP_MAKE_ARRAY, // operand = element count; pops N values, pushes array
  OP_INDEX_GET,  // pops index, array; pushes element copy
  OP_INDEX_SET,  // pops value, index, array; sets in place

  // array and builtin helpers
  OP_LEN,    // pops array or string; pushes length
  OP_PUSH,   // pops value, array; pushes new length
  OP_APOP,   // pops array; pushes removed element
  OP_SET,    // pops value, index, array; pushes value
  OP_INSERT, // pops value, index, array; pushes new length
  OP_REMOVE, // pops index, array; pushes removed element
  OP_SLICE,  // pops end, start, array; pushes new array

  // conversions
  OP_TO_NUMBER, // pops any; pushes int (parse strings)
  OP_TO_STRING, // pops any; pushes string
  OP_CAST,      // pops typeName, value; pushes casted value (see vm/cast.c)
  OP_TYPEOF,    // pops any; pushes string name of type
  OP_UCLAMP,    // pops number; pushes number masked to N bits (operand = bits)
  OP_SCLAMP,    // pops number; pushes number clamped to signed N-bit range (operand = bits)

  // string ops
  OP_SPLIT,  // pops sep, string; pushes array of strings
  OP_JOIN,   // pops sep, array; pushes string
  OP_SUBSTR, // pops len, start, string; pushes string
  OP_FIND,   // pops needle, haystack; pushes int index or -1

  // regex ops (POSIX)
  OP_REGEX_MATCH,   // pops pattern, string; pushes 1/0 for full match
  OP_REGEX_SEARCH,  // pops pattern, string; pushes map {"match":str, "start":int, "end":int, "groups":array}
  OP_REGEX_REPLACE, // pops repl, pattern, string; pushes string with global replacements

  // array utils
  OP_CONTAINS, // pops value, array; pushes 1/0
  OP_INDEX_OF, // pops value, array; pushes index or -1
  OP_CLEAR,    // pops array; clears it; pushes nothing (we'll push 0)

  // iteration helpers
  OP_ENUMERATE, // pops array; pushes array of [index, value]
  OP_ZIP,       // pops b, a; pushes array of [a[i], b[i]]

  // math
  OP_MIN,         // pops b, a; pushes min(a,b)
  OP_MAX,         // pops b, a; pushes max(a,b)
  OP_CLAMP,       // pops hi, lo, x; pushes clamped
  OP_ABS,         // pops x; pushes |x|
  OP_POW,         // pops b, a; pushes a^b
  OP_RANDOM_SEED, // pops seed; sets RNG seed; pushes nothing (we'll push 0)
  OP_RANDOM_INT,  // pops hi, lo; pushes random int in [lo, hi)

  // maps
  OP_MAKE_MAP, // operand = pair count; pops 2*n (key,value)..., pushes map
  OP_KEYS,     // pops map; pushes array of keys
  OP_VALUES,   // pops map; pushes array of values
  OP_HAS_KEY,  // pops key, map; pushes 1/0

  // file I/O
  OP_READ_FILE,  // pops path string; pushes content string (or "")
  OP_WRITE_FILE, // pops data string, path string; pushes 1/0

  // OS
  OP_ENV,           // pops name string; pushes value string (or "")
  OP_INPUT_LINE,    // operand: 0=no prompt; 1=has prompt. Pops [prompt?]; pushes input string (no trailing newline)
  OP_PROC_RUN,      // pops command string; pushes map {"out": string, "code": int}
  OP_PROC_SYSTEM,   // pops command string; pushes exit code number
  OP_TIME_NOW_MS,   // pushes current wall-clock time in milliseconds since Unix epoch
  OP_CLOCK_MONO_MS, // pushes monotonic clock in milliseconds (not wall time)

  // kcgi (optional)
  OP_KCGI_PARSE,       // () -> Map | Nil (parse request via kcgi)
  OP_KCGI_REPLY_START, // (code:int, content_type:string) -> 1/0
  OP_KCGI_WRITE,       // (chunk:string) -> 1/0
  OP_KCGI_END,         // () -> 1/0 (free request)
  OP_DATE_FORMAT,   // pops fmt string, ms epoch (int); pushes formatted date string using strftime
  OP_ENV_ALL,       // pushes map of all environment variables
  OP_FUN_VERSION,   // pushes version string

  // Threads
  OP_THREAD_SPAWN,  // operand: 0=no args, 1=has args; pops [args?], fn; pushes thread id (int>0)
  OP_THREAD_JOIN,   // pops thread id; waits; pushes result value (or Nil)
  OP_SLEEP_MS,      // pops milliseconds; sleeps; pushes Nil (for statement POP safety)
  OP_RANDOM_NUMBER, // pops length; pushes hex string of that length from OS RNG (hex-encoded)

  // Bitwise (32-bit) and shifts/rotates
  OP_BAND, // pops b, a; pushes (uint32_t)(a & b)
  OP_BOR,  // pops b, a; pushes (uint32_t)(a | b)
  OP_BXOR, // pops b, a; pushes (uint32_t)(a ^ b)
  OP_BNOT, // pops a;   pushes (uint32_t)(~a)
  OP_SHL,  // pops s, a; pushes (uint32_t)(a << (s&31))
  OP_SHR,  // pops s, a; pushes (uint32_t)(a >> (s&31)) logical
  OP_ROTL, // pops s, a; pushes rotl32(a, s)
  OP_ROTR, // pops s, a; pushes rotr32(a, s)

  // JSON (json-c)
  OP_JSON_PARSE,     // pops text string; pushes value (or Nil on error)
  OP_JSON_STRINGIFY, // pops pretty(bool), value; pushes string
  OP_JSON_FROM_FILE, // pops path string; pushes value (or Nil)
  OP_JSON_TO_FILE,   // pops pretty(bool), value, path; pushes 1/0

  // CURL (libcurl)
  OP_CURL_GET,      // pops [headers map?], url; pushes response string (or "")
  OP_CURL_POST,     // pops [headers map?], body string, url; pushes response string (or "")
  OP_CURL_DOWNLOAD, // pops [headers map?], path, url; pushes 1/0

  // SQLite (optional)
  OP_SQLITE_OPEN,  // pops path; pushes handle (>0) or 0
  OP_SQLITE_CLOSE, // pops handle; pushes Nil
  OP_SQLITE_EXEC,  // pops sql, handle; pushes sqlite rc (0=OK)
  OP_SQLITE_QUERY, // pops sql, handle; pushes array<map>
  
  // PCSC (smart card) opcodes
  OP_PCSC_ESTABLISH,    // returns context id (>0) or 0
  OP_PCSC_RELEASE,      // pops ctx id; returns 1/0
  OP_PCSC_LIST_READERS, // pops ctx id; returns array of reader names (possibly empty)
  OP_PCSC_CONNECT,      // pops reader, ctx id; returns handle id (>0) or 0
  OP_PCSC_DISCONNECT,   // pops handle id; returns 1/0
  OP_PCSC_TRANSMIT,     // pops apdu array, handle id; returns map {"data":[],"sw1":n,"sw2":n,"code":n}

  // PCRE2 regex ops (optional)
  OP_PCRE2_TEST,    // pops flags, text, pattern; pushes 1/0
  OP_PCRE2_MATCH,   // pops flags, text, pattern; pushes match map or Nil
  OP_PCRE2_FINDALL, // pops flags, text, pattern; pushes array of match maps

  // OpenSSL (optional)
  OP_OPENSSL_MD5,       // pops data string; pushes md5 hex string
  OP_OPENSSL_SHA256,    // pops data string; pushes sha256 hex string
  OP_OPENSSL_SHA512,    // pops data string; pushes sha512 hex string
  OP_OPENSSL_RIPEMD160, // pops data string; pushes ripemd160 hex string
  
  // INI (iniparser 4.2.6) optional
  OP_INI_LOAD,       // pops path; pushes handle (>0) or 0
  OP_INI_FREE,       // pops handle; pushes 1/0
  OP_INI_GET_STRING, // pops def, key, section, handle; pushes string
  OP_INI_GET_INT,    // pops def, key, section, handle; pushes int
  OP_INI_GET_DOUBLE, // pops def, key, section, handle; pushes float
  OP_INI_GET_BOOL,   // pops def, key, section, handle; pushes int (0/1)
  OP_INI_SET,        // pops value, key, section, handle; pushes 1/0
  OP_INI_UNSET,      // pops key, section, handle; pushes 1/0
  OP_INI_SAVE,       // pops path, handle; pushes 1/0

  // XML (libxml2) optional minimal API
  OP_XML_PARSE, // pops text string; pushes doc handle (>0) or 0
  OP_XML_ROOT,  // pops doc handle; pushes node handle (>0) or 0
  OP_XML_NAME,  // pops node handle; pushes string (node name)
  OP_XML_TEXT,  // pops node handle; pushes string (node text)

  // Sockets (UNIX platforms)
  OP_SOCK_TCP_LISTEN,   // pops backlog, port; returns listen fd (>0) or 0
  OP_SOCK_TCP_ACCEPT,   // pops listen fd; returns client fd (>0) or 0
  OP_SOCK_TCP_CONNECT,  // pops port, host; returns fd (>0) or 0
  OP_SOCK_SEND,         // pops data string, fd; returns bytes sent (>=0) or -1
  OP_SOCK_RECV,         // pops maxlen, fd; returns data string ("" on EOF/error)
  OP_SOCK_CLOSE,        // pops fd; returns 1/0
  OP_SOCK_UNIX_LISTEN,  // pops backlog, path; returns listen fd (>0) or 0
  OP_SOCK_UNIX_CONNECT, // pops path; returns fd (>0) or 0

  // Async-friendly FD helpers (UNIX platforms)
  OP_FD_SET_NONBLOCK,   // pops on:int (0/1), fd:int; returns 1 on success, 0 on error/unsupported
  OP_FD_POLL_READ,      // pops timeout_ms:int, fd:int; returns 1 if readable, 0 if timeout/EOF, -1 on error
  OP_FD_POLL_WRITE,     // pops timeout_ms:int, fd:int; returns 1 if writable, 0 if timeout, -1 on error

  // process control
  OP_EXIT, // pops code (or uses operand) and terminates script with exit code

  // OS additions
  OP_OS_LIST_DIR, // pops path string; pushes array of strings

  // Serial communication (termios)
  OP_SERIAL_OPEN,   // pops baud_rate (int), path (string); returns fd (int) or 0
  OP_SERIAL_CONFIG, // pops flow_control, stop_bits, parity, data_bits, fd; returns 1/0
  OP_SERIAL_SEND,   // pops data (string), fd; returns bytes sent (int)
  OP_SERIAL_RECV,   // pops maxlen (int), fd; returns data (string)
  OP_SERIAL_CLOSE,  // pops fd; returns 1/0

  // exceptions (minimal)
  OP_TRY_PUSH, // operand = handler ip; push handler onto try-stack
  OP_TRY_POP,  // pop current handler
  OP_THROW,    // pops error value; if handler -> jump to it (push err), else print and terminate

  // C99 math.h rounding family (float-aware)
  OP_FLOOR, // pops x (int/float); pushes floor(x) (int if integral else float)
  OP_CEIL,  // pops x (int/float); pushes ceil(x)  (int if integral else float)
  OP_TRUNC, // pops x (int/float); pushes trunc(x) (int if integral else float)
  OP_ROUND, // pops x (int/float); pushes round(x) (half away from zero)

  // C99 math.h transcendentals (float-aware)
  OP_SIN,   // pops x (int/float); pushes sin(x)
  OP_COS,   // pops x (int/float); pushes cos(x)
  OP_TAN,   // pops x (int/float); pushes tan(x)
  OP_EXP,   // pops x (int/float); pushes exp(x)
  OP_LOG,   // pops x (int/float); pushes natural log ln(x)
  OP_LOG10, // pops x (int/float); pushes log10(x)
  OP_SQRT,  // pops x (int/float); pushes sqrt(x)

  // Integer math helpers
  OP_GCD,   // pops b, a; pushes gcd(|a|,|b|)
  OP_LCM,   // pops b, a; pushes lcm(|a|,|b|) (0 if either is 0)
  OP_ISQRT, // pops x; pushes floor(sqrt(max(0,x))) for integers
  OP_SIGN,  // pops x; pushes -1, 0, or 1 depending on the sign

  // Min/Max variants (float-aware, C99 semantics)
  OP_FMIN, // pops b, a (int/float); pushes fmin(a,b) (NaN handling per C99)
  OP_FMAX, // pops b, a (int/float); pushes fmax(a,b) (NaN handling per C99)

  // Rust FFI demo opcode(s)
  OP_RUST_HELLO,             // pushes string returned from Rust (hello world)
  OP_RUST_HELLO_ARGS,        // pops message string; prints it via Rust; pushes Nil
  OP_RUST_HELLO_ARGS_RETURN, // pops message string; returns it from Rust without printing; pushes returned string
  OP_RUST_GET_SP,            // pushes current VM stack pointer (via Rust reading VM memory)
  OP_RUST_SET_EXIT,          // pops int and sets VM exit_code (via Rust writing VM memory)

  // C++ demo opcode(s)
  OP_CPP_ADD, // pops b, a; pushes (a + b)
} OpCode;

typedef struct {
  OpCode op;
  int32_t operand;
} Instruction;

typedef struct Bytecode {
  Instruction *instructions;
  int instr_count;

  Value *constants;
  int const_count;

  /* debug metadata */
  const char *name;        /* function or module name (optional) */
  const char *source_file; /* originating source filename (optional) */
} Bytecode;

// constructors / manipulation
Bytecode *bytecode_new(void);
int bytecode_add_constant(Bytecode *bc, Value v); /* stores copy */
int bytecode_add_instruction(Bytecode *bc, OpCode op, int32_t operand);
void bytecode_set_operand(Bytecode *bc, int idx, int32_t operand); /* patching */
void bytecode_free(Bytecode *bc);

// utilities
void bytecode_dump(const Bytecode *bc);

#endif
