#include <emscripten/console.h>
#include <emscripten/wasm_worker.h>
#include <emscripten/threading.h>
#include <stdlib.h>
#include <assert.h>

// Tests emscripten_lock_async_acquire() function.

emscripten_lock_t lock = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER;

// Two shared variables, always a delta distance of one from each other.
volatile int sharedState0 = 0;
volatile int sharedState1 = 1;

bool testFinished = false;

int numTimesMainThreadAcquiredLock = 0;
int numTimesWasmWorkerAcquiredLock = 0;

void do_exit() {
  emscripten_out("do_exit");
  emscripten_terminate_all_wasm_workers();
  emscripten_force_exit(0);
}

void work() {
  // emscripten_out("work");
  volatile int x = sharedState0;
  volatile int y = sharedState1;
  assert(x == y+1 || y == x+1);

  if (emscripten_current_thread_is_wasm_worker()) {
    ++numTimesWasmWorkerAcquiredLock;
  } else {
    ++numTimesMainThreadAcquiredLock;
  }

  if (x < y) {
    x = y + 1;
    if (emscripten_current_thread_is_wasm_worker()) {
      emscripten_wasm_worker_sleep(/*nsecs=*/(rand()%100000));
    }
    sharedState0 = x;
  } else {
    y = x + 1;
    if (emscripten_current_thread_is_wasm_worker()) {
      emscripten_wasm_worker_sleep(/*nsecs=*/(rand()%100000));
    }
    sharedState1 = y;

    if (y > 100 && numTimesMainThreadAcquiredLock && numTimesWasmWorkerAcquiredLock) {
      if (!testFinished) {
        testFinished = true;
        emscripten_out("test finished");
        emscripten_wasm_worker_post_function_v(EMSCRIPTEN_WASM_WORKER_ID_PARENT, do_exit);
      }
    }
  }
}

void schedule_work(void *userData);

void lock_async_acquired(volatile void *addr, uint32_t val, ATOMICS_WAIT_RESULT_T waitResult, void *userData) {
  // emscripten_out("async lock acquired");
  assert(addr == &lock);
  assert(val == 0 || val == 1);
  assert(waitResult == ATOMICS_WAIT_OK);
  assert(userData == (void*)42);
  work();
  emscripten_lock_release(&lock);

  if (!testFinished) {
    emscripten_set_timeout(schedule_work, 10, 0);
  }
}

void schedule_work(void *userData) {
  if (emscripten_current_thread_is_wasm_worker() && emscripten_random() > 0.5) {
    emscripten_lock_waitinf_acquire(&lock);
    // emscripten_out("sync lock acquired");
    work();
    emscripten_lock_release(&lock);
    if (!testFinished) {
      emscripten_set_timeout(schedule_work, 0, 0);
    }
  } else {
    emscripten_lock_async_acquire(&lock, lock_async_acquired, (void*)42, EMSCRIPTEN_WAIT_ASYNC_INFINITY);
  }
}

void start_worker(int arg) {
  schedule_work(0);
}

int main() {
#define NUM_THREADS 10
  for (int i = 0; i < NUM_THREADS; ++i) {
    emscripten_wasm_worker_t worker = emscripten_malloc_wasm_worker(1024);
    emscripten_wasm_worker_post_function_vi(worker, start_worker, 0);
  }

  schedule_work(0);
  emscripten_exit_with_live_runtime();
}
