#pragma once

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

#define TAG_MASK    0xf

#define TAG_IMMED   0x1
#define TAG_STRING  0x3
#define TAG_CLOSURE 0x5
#define TAG_MODULE  0x7
#define TAG_LABELED 0x9
#define TAG_REF     0xb
#define TAG_FILE    0xd

#define SECOND_TAG_LABELED 0x10
#define SECOND_TAG_BOOLEAN 0x20

struct value {
    uint32_t bits;
};

struct heap {
    uint32_t size;
    struct value *values;
    uint32_t top;
};

struct closure {
    uint8_t num_params;
    uint16_t env_size;
    int32_t code_offset;
    struct value free_values[];
};

struct string {
    uint32_t size;
    uint8_t bytes[];
};

struct labeled_value {
    uint16_t label;
    struct value value;
};

struct module {
    uint16_t size;
    uint16_t index_begin;
    struct value entries[];
};

struct ref {
    struct value x;
};

struct file {
    FILE *stream;
};

extern uint16_t module_indices[];

#define var(i) stack[fp+3+(i)]

#define value(x) (struct value){(uint32_t)(x)}

#define push_var(x) stack[sp++] = (x)
#define pop_vars(n) sp -= (n)

#define push_frame(label) \
    do { \
        push_var(c); \
        push_var(value(fp)); \
        push_var(value(&&label - &&entry_0)); \
    } while (0)

#define alloc_prim(heap, num_params, label) \
    alloc_closure((heap), (num_params), 0, &&label - &&entry_0)

#define empty_tuple value(0 | TAG_IMMED)

#define true_value  value(0x100 | SECOND_TAG_BOOLEAN | TAG_IMMED)
#define false_value value(0x200 | SECOND_TAG_BOOLEAN | TAG_IMMED)

_Noreturn void
halt(void);

_Noreturn void
die(const char *s);

_Bool
has_tag(struct value x, uint32_t tag);

void *
address(struct heap *heap, struct value x);

struct value
reference_value(uint32_t offset, uint32_t tag);

struct value
labeled_empty_tuple(uint16_t label);

struct value
alloc_tuple(struct heap *heap, struct value *values, uint16_t size);

struct value
alloc_labeled_value(struct heap *heap, uint16_t label, struct value value);

uint16_t
value_label(struct heap *heap, struct value x);

void
open_labeled_value(struct heap *heap, struct value *frame, struct value x,
        int n);

struct value
remove_label(struct heap *heap, struct value x);

void
open_tuple(struct heap *heap, struct value *frame, struct value x, int n);

struct value
alloc_closure(struct heap *heap, int8_t num_params,
        uint32_t env_size, int32_t code_offset);

void
closure_store(struct heap *heap, struct value x, uint32_t i,
        struct value value);

struct value
alloc_string(struct heap *heap, const char *s);

struct value
number(int32_t n);

int32_t
value_unbox_int32(struct value x);

struct value
prim_die(struct heap *heap, struct value s);

struct value
prim_print_line(struct heap *heap, struct value s);

struct value
prim_string_length(struct heap *heap, struct value s);

struct value
prim_string_fetch(struct heap *heap, struct value s, struct value i);

struct value
prim_string_equal(struct heap *heap, struct value s1, struct value s2);

struct value
prim_string_compare(struct heap *heap, struct value s1, struct value s2);

struct value
prim_string_append(struct heap *heap, struct value s1, struct value s2);

struct value
prim_string_clip(struct heap *heap, struct value s, struct value begin,
        struct value end);

struct value
prim_file_create(struct heap *heap, struct value name);

struct value
prim_file_open(struct heap *heap, struct value name);

struct value
prim_file_close(struct heap *heap, struct value file);

struct value
prim_file_read_all(struct heap *heap, struct value file);

struct value
prim_file_write(struct heap *heap, struct value file, struct value str);

struct value
prim_show_integer(struct heap *heap, struct value n);

struct value
prim_bits(struct heap *heap, struct value x);

struct value
prim_multiply(struct heap *heap, struct value a, struct value b);

struct value
prim_add(struct heap *heap, struct value a, struct value b);

struct value
prim_negate(struct heap *heap, struct value n);

struct value
prim_equal(struct heap *heap, struct value a, struct value b);

struct value
prim_less(struct heap *heap, struct value a, struct value b);

struct value
prim_less_or_equal(struct heap *heap, struct value a, struct value b);

struct value
prim_greater(struct heap *heap, struct value a, struct value b);

struct value
prim_greater_or_equal(struct heap *heap, struct value a, struct value b);

struct value
prim_ref_new(struct heap *heap, struct value x);

struct value
prim_ref_store(struct heap *heap, struct value ref, struct value x);

struct value
prim_ref_fetch(struct heap *heap, struct value ref);

struct value
alloc_module(struct heap *heap, struct value *values, uint16_t size,
        uint16_t index_begin);

void
module_store(struct heap *heap, struct value x, uint16_t i,
        struct value value);

struct value
tuple_fetch(struct heap *heap, struct value x, uint16_t i);

struct value
module_fetch(struct heap *heap, struct value x, uint16_t label);

void *
run_machine(int argc, const char *argv[]);