#include "pocketpy/objects/codeobject.h"
#include "pocketpy/pocketpy.h"

#include "pocketpy/common/utils.h"
#include "pocketpy/common/name.h"
#include "pocketpy/interpreter/vm.h"

_Thread_local VM* pk_current_vm;

static bool pk_initialized;
static bool pk_finalized;

static VM pk_default_vm;
static VM* pk_all_vm[16];
static py_TValue _True, _False, _None, _NIL;

void py_initialize() {
    c11__rtassert(!pk_finalized);

    if(pk_initialized) {
        // c11__abort("py_initialize() can only be called once!");
        return;
    }

    pk_names_initialize();

    // check endianness
    int x = 1;
    bool is_little_endian = *(char*)&x == 1;
    if(!is_little_endian) c11__abort("is_little_endian != true");

    _Static_assert(sizeof(py_TValue) == 24, "sizeof(py_TValue) != 24");
    _Static_assert(offsetof(py_TValue, extra) == 4, "offsetof(py_TValue, extra) != 4");

    // check sizes
    _Static_assert(sizeof(float) == 4, "");
    _Static_assert(sizeof(double) == 8, "");

    pk_current_vm = pk_all_vm[0] = &pk_default_vm;

    // initialize some convenient references
    py_newbool(&_True, true);
    py_newbool(&_False, false);
    py_newnone(&_None);
    py_newnil(&_NIL);
    VM__ctor(&pk_default_vm);

    pk_initialized = true;
}

void py_finalize() {
    if(pk_finalized) c11__abort("py_finalize() can only be called once!");
    pk_finalized = true;

    for(int i = 1; i < 16; i++) {
        VM* vm = pk_all_vm[i];
        if(vm) {
            // temp fix https://github.com/pocketpy/pocketpy/issues/315
            // TODO: refactor VM__ctor and VM__dtor
            pk_current_vm = vm;
            VM__dtor(vm);
            PK_FREE(vm);
        }
    }
    pk_current_vm = &pk_default_vm;
    VM__dtor(&pk_default_vm);
    pk_current_vm = NULL;

    pk_names_finalize();
}

int VM__index(VM* self) {
    for(int i = 0; i < 16; i++) {
        if(pk_all_vm[i] == self) return i;
    }
    return -1;
}

int py_currentvm() { return VM__index(pk_current_vm); }

void py_switchvm(int index) {
    if(index < 0 || index >= 16) c11__abort("invalid vm index");
    if(!pk_all_vm[index]) {
        pk_current_vm = pk_all_vm[index] = PK_MALLOC(sizeof(VM));
        memset(pk_current_vm, 0, sizeof(VM));
        VM__ctor(pk_all_vm[index]);
    } else {
        pk_current_vm = pk_all_vm[index];
    }
}

void py_resetvm() {
    VM* vm = pk_current_vm;
    VM__dtor(vm);
    memset(vm, 0, sizeof(VM));
    VM__ctor(vm);
}

void py_resetallvm() {
    for(int i = 0; i < 16; i++) {
        py_switchvm(i);
        py_resetvm();
    }
    py_switchvm(0);
}

void* py_getvmctx() { return pk_current_vm->ctx; }

void py_setvmctx(void* ctx) { pk_current_vm->ctx = ctx; }

py_Callbacks* py_callbacks() { return &pk_current_vm->callbacks; }

py_AppCallbacks* py_appcallbacks() {
    static py_AppCallbacks _callbacks = {0};
    return &_callbacks;
}

/////////////////////////////

void py_sys_setargv(int argc, char** argv) {
    py_GlobalRef sys = py_getmodule("sys");
    py_Ref argv_list = py_getdict(sys, py_name("argv"));
    py_list_clear(argv_list);
    for(int i = 1; i < argc; i++) {
        py_newstr(py_list_emplace(argv_list), argv[i]);
    }
}

void py_sys_settrace(py_TraceFunc func, bool reset) {
    TraceInfo* info = &pk_current_vm->trace_info;
    info->func = func;
    if(!reset) return;
    if(info->prev_loc.src) {
        PK_DECREF(info->prev_loc.src);
        info->prev_loc.src = NULL;
    }
    info->prev_loc.lineno = -1;
}

int py_gc_collect() {
    ManagedHeap* heap = &pk_current_vm->heap;
    return ManagedHeap__collect(heap);
}

/////////////////////////////

void* py_malloc(size_t size) { return PK_MALLOC(size); }

void* py_realloc(void* ptr, size_t size) { return PK_REALLOC(ptr, size); }

void py_free(void* ptr) { PK_FREE(ptr); }

/////////////////////////////

py_GlobalRef py_True() { return &_True; }

py_GlobalRef py_False() { return &_False; }

py_GlobalRef py_None() { return &_None; }

py_GlobalRef py_NIL() { return &_NIL; }

/////////////////////////////

const char* pk_opname(Opcode op) {
    const static char* OP_NAMES[] = {
#define OPCODE(name) #name,
#include "pocketpy/xmacros/opcodes.h"
#undef OPCODE
    };
    return OP_NAMES[op];
}
