#include <cstdint>
#include <emscripten.h>
#include <emscripten/bind.h>
#include <string>
#include <stdio.h>
#include "janet.h"

using std::string;

// set all our exported Janet functions as null pointers
static JanetFunction *janet_ludus = NULL;

// these let us look up functions
Janet env_lookup(JanetTable *env, const char *name) {
  Janet out;
  janet_resolve(env, janet_csymbol(name), &out);
  return out;
}

JanetFunction *env_lookup_function(JanetTable *env, const char *name) {
  Janet value = env_lookup(env, name);
  if (!janet_checktype(value, JANET_FUNCTION)) {
    janet_panicf("expected %s to be a function, got %q\n", name, value);
  }
  return janet_unwrap_function(value);
}

// this lets us call a function
bool call_fn(JanetFunction *fn, int argc, const Janet *argv, Janet *out) {
  JanetFiber *fiber = NULL;
  if (janet_pcall(fn, argc, argv, out, &fiber) == JANET_SIGNAL_OK) {
    return true;
  } else {
    janet_stacktrace(fiber, *out);
    return false;
  }
}

// this is darkish magic, reads an embedded file
// do not fuck with this, fellas
unsigned char *read_file(const char *filename, size_t *length) {
  size_t capacity = 2 << 17;
  unsigned char *src = (unsigned char *)malloc(capacity * sizeof(unsigned char));
  assert(src);
  size_t total_bytes_read = 0;
  FILE *file = fopen(filename, "r");
  assert(file);
  size_t bytes_read;
  do {
    size_t remaining_capacity = capacity - total_bytes_read;
    if (remaining_capacity == 0) {
      capacity <<= 1;
      src = (unsigned char*)realloc(src, capacity * sizeof(unsigned char));
      assert(src);
      remaining_capacity = capacity - total_bytes_read;
    }

    bytes_read = fread(&src[total_bytes_read], sizeof(unsigned char), remaining_capacity, file);
    total_bytes_read += bytes_read;
  } while (bytes_read > 0);

  fclose(file);
  *length = total_bytes_read;
  return src;
}

// finally, getting a string back
// this is our result type
struct StringResult {
  string value;
};

// this is our result constructor
// Janet's getcstring resturns const char*
StringResult string_result(const char* cstr) {
  // ...which we have to cast to a std::string
  return (StringResult) {.value = (string) cstr };
}

// and this is a function that takes and returns a string
// it returns a StringResult, tho
StringResult ludus(string source) {
  Janet result;
  const Janet args[1] = {janet_cstringv(source.c_str())};
  call_fn(janet_ludus, 1, args, &result);
  // get the cstring in the result
  // the 0 passed here is the index in the result of the string
  const char* cstr = janet_getcstring(&result, 0);
  // return a constructed StringResult
  return string_result(cstr);
}

// This function sets up our Janet interpreter, and fixes the null pointers
EMSCRIPTEN_KEEPALIVE
int main() {
  janet_init(); // start the interpreter
  JanetTable *core_env = janet_core_env(NULL); // get a core env
  JanetTable *lookup = janet_env_lookup(core_env); // and get an env table

  // load the janet image into memory
  // note that the image is hardcoded here
  size_t image_length;
  unsigned char *image = read_file("ludus.jimage", &image_length);

  // load the image into the Janet environment
  Janet env = janet_unmarshal(image, image_length, 0, lookup, NULL);

  if(!janet_checktype(env, JANET_TABLE)) {
    janet_panicf("invalid image %q", env);
  }

  // fix the null pointers, as above
  // note that the bound symbols are just the normal fn names
  // no namespacing
  janet_ludus = env_lookup_function(janet_unwrap_table(env), "ludus");
  janet_gcroot(janet_wrap_function(janet_ludus));
}

// these bindings are exported into javascript
EMSCRIPTEN_BINDINGS(module) {
  using namespace emscripten;

  // these are the functions that will be available
  function("ludus", &ludus, allow_raw_pointers());

  // we also want a wrapper for our StringResult
  // we won't access it directly, but emcc makes it nice
  value_object<StringResult>("StringResult")
    .field("value", &StringResult::value);
}
