#pragma once

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

typedef enum Op {
  Exit, // Pop value from the stack and return as exit code. Return 0 if the
        // stack is empty.
  Push,
  Pop,
  Add,
  Sub,
  Mul,
  Div,
  Dec,   // Decrement the top of the stack by 1.
  Empty, // Check whether the stack is empty. Pushes a bool.
  Cmp,   // Pop the top of the stack and compare it with the payload. Pushes a
         // bool.
  /* Blocks */
  End,   // Marks the end of a block.
  Break, // Exit the current block.
  Loop,  // Push a loop block. Payload (i32): label.
  /* Branches */
  Br, // Branch. Payload (i64): [(i32) conditional? | (i32) label].
      // A condtional branch pops a bool from the stack and branches if true.
      // The condition can also be negated. See br_if().
  /* Functions */
  Func,
  Arg,
  Call,
  /* Locals */
  Local,   // Create a local variable.
  LocalRd, // Load a local variable into the top of the stack.
  LocalWr, // Pop the top of the stack and store it in a local variable.
} Op;

typedef enum Type {
  I32,
  F32,
} Type;

// Label type for blocks and locals.
typedef uint32_t Label;

typedef struct Branch {
  Label label;
  bool  conditional : 1; // True for conditional branches.
  bool  expected    : 1; // Comparison value for conditional branches.
} Branch;

typedef struct Function {
  Label label;
} Function;

typedef struct Value {
  union {
    uint64_t u64;
    int32_t  i32;
    float    f32;
    Branch   branch;
    Label    label;
  };
} Value;

typedef struct Inst {
  Op    op   : 5;
  Type  type : 2;
  Value payload;
} Inst;

typedef struct Vm Vm;

// -----------------------------------------------------------------------------
// VM API

/// Create a new virtual machine.
Vm* vm_new();

/// Destroy the virtual machine.
void vm_del(Vm**);

/// Execute code on the virtual machine.
///
/// Returns the program exit code if an exit operation is executed, 0 otherwise.
int vm_run(Vm*, const Inst[], size_t count);

/// Prints the virtual machine's stack to stdout.
void vm_print_stack(const Vm*);

// -----------------------------------------------------------------------------
// Programming API

/// Exit the program.
static inline Inst vmExit() { return (Inst){.op = Exit}; }

/// Push a value.
static inline Inst vmPushI32(int32_t value) {
  return (Inst){.op = Push, .type = I32, .payload = (Value){.i32 = value}};
}

/// Pop a value.
static inline Inst vmPop(Type type) { return (Inst){.op = Pop, .type = type}; }

/// Add two values.
static inline Inst vmAdd(Type type) { return (Inst){.op = Add, .type = type}; }

/// Decrement a value.
static inline Inst vmDec(Type type) { return (Inst){.op = Dec, .type = type}; }

/// Compare a value.
static inline Inst vmCmpI32(int32_t value) {
  return (Inst){.op = Cmp, .type = I32, .payload = (Value){.i32 = value}};
}

/// End the current block.
static inline Inst vmEnd() { return (Inst){.op = End}; }

/// Create a loop.
static inline Inst vmLoop(Label label) {
  return (Inst){.op = Loop, .payload = (Value){.label = label}};
}

/// Create the payload of a conditional branch.
static inline Inst vmBr_if(bool value, Label label) {
  return (Inst){
      .op      = Br,
      .payload = (Value){
          .branch = {
              .label       = label,
              .conditional = 1,
              .expected    = value,
          }}};
}

/// Create a function.
static inline Inst vmFunc(Label label) {
  return (Inst){.op = Func, .payload = (Value){.label = label}};
}

/// Create a function argument.
static inline Inst vmArg(Type type, Label label) {
  return (Inst){.op = Arg, .type = type, .payload = (Value){.label = label}};
}

/// Call a function.
static inline Inst vmCall(Label label) {
  return (Inst){.op = Call, .payload = (Value){.label = label}};
}

/// Create a local variable.
static inline Inst vmLocal(Type type, Label label) {
  return (Inst){.op = Local, .type = type, .payload = (Value){.label = label}};
}

/// Read a local variable.
static inline Inst vmLocalRd(Type type, Label label) {
  return (Inst){
      .op = LocalRd, .type = type, .payload = (Value){.label = label}};
}

/// Write a local variable.
static inline Inst vmLocalWr(Type type, Label label) {
  return (Inst){
      .op = LocalWr, .type = type, .payload = (Value){.label = label}};
}