#include "buffer.h"
#include "util.h"
#include <cassert>
#include <chrono>
#include <cstring>
#include <emscripten.h>
#include <emscripten/atomic.h>
#include <emscripten/bind.h>
#include <emscripten/console.h>
#include <emscripten/em_math.h>
#include <emscripten/val.h>
#include <emscripten/wasm_worker.h>
#include <emscripten/webaudio.h>
#include <iostream>
#include <map>
#include <stdio.h>
#include <wasm_simd128.h>

extern "C" {
#include <libavutil/log.h>
}

template <typename F> void profile_thing(int iter, const char *name, F f) {
  constexpr int BUFFER_SIZE = 1024;

  auto startTime = std::chrono::high_resolution_clock::now();

  for (int i = 0; i < iter; i++) {
    f();
  }

  auto endTime = std::chrono::high_resolution_clock::now();

  std::cout << (std::chrono::duration_cast<std::chrono::milliseconds>(endTime -
                                                                      startTime)
                    .count()) /
                   (float)iter
            << "ms per " << name << "\n";
}

emscripten_lock_t worker_lock;
std::map<int, std::string> modulePaths;

extern "C" void _embind_initialize_bindings();

void worker_start() {
  _embind_initialize_bindings();
  emscripten_lock_raii lg(worker_lock);
  auto it = modulePaths.find(emscripten_wasm_worker_self_id());
  if (it == modulePaths.end()) {
    return;
  }
  auto modulePath = it->second;
  modulePaths.erase(it);
  EM_ASM({self.__emModule = Module});
  EM_ASM({ import(UTF8ToString($0)); }, modulePath.c_str());
}

emscripten::val doImportInWorker(const std::string &module) {
  emscripten_lock_raii lg(worker_lock);
  emscripten_wasm_worker_t worker =
      emscripten_malloc_wasm_worker(/*stackSize: */ 3670016);
  emscripten_wasm_worker_post_function_v(worker, worker_start);
  modulePaths[worker] = module;
  EM_ASM({window.__emWasmWorkers = _wasmWorkers});
  return emscripten::val::global("__emWasmWorkers")[(int)worker];
}

__attribute__((noinline)) void testFaultInJs2() {
  EM_ASM({ throw new Error("test fault in js"); });
}

__attribute__((noinline)) void testFaultInJs() { testFaultInJs2(); }

EMSCRIPTEN_BINDINGS(main) {
  using namespace emscripten;

  function("doImportInWorker", &doImportInWorker);
  function("testFaultInJs", &testFaultInJs);
};

int main() {
  emscripten_lock_init(&worker_lock);
  av_log_set_level(AV_LOG_QUIET);
  av_log_set_callback(nullptr);
  return 0;
}