/*
 * Copyright 2026 The Emscripten Authors.  All rights reserved.
 * Emscripten is available under two separate licenses, the MIT license and the
 * University of Illinois/NCSA Open Source License.  Both these licenses can be
 * found in the LICENSE file.
 *
 * Low level threading primitives for lock, condvar and mutex, build on the
 * emscripten atomics API.
 */
#include <emscripten/threading.h>

#include "emscripten_internal.h"

void emscripten_lock_init(emscripten_lock_t *lock) {
  emscripten_atomic_store_u32((void*)lock, EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER);
}

bool emscripten_lock_wait_acquire(emscripten_lock_t *lock, int64_t maxWaitNanoseconds) {
  emscripten_lock_t val = emscripten_atomic_cas_u32((void*)lock, 0, 1);
  if (!val) return true;
  int64_t waitEnd = (int64_t)(emscripten_performance_now() * 1e6) + maxWaitNanoseconds;
  while (maxWaitNanoseconds > 0) {
    emscripten_atomic_wait_u32((void*)lock, val, maxWaitNanoseconds);
    val = emscripten_atomic_cas_u32((void*)lock, 0, 1);
    if (!val) return true;
    maxWaitNanoseconds = waitEnd - (int64_t)(emscripten_performance_now() * 1e6);
  }
  return false;
}

void emscripten_lock_waitinf_acquire(emscripten_lock_t *lock) {
  emscripten_lock_t val;
  do {
    val = emscripten_atomic_cas_u32((void*)lock, 0, 1);
    if (val) {
      emscripten_atomic_wait_u32((void*)lock, val, ATOMICS_WAIT_DURATION_INFINITE);
    }
  } while (val);
}

bool emscripten_lock_busyspin_wait_acquire(emscripten_lock_t *lock, double maxWaitMilliseconds) {
  // TODO: we changed the performance_now calls to get_now, which can be applied
  // to the remaining code (since all calls defer to the best internal option).
  emscripten_lock_t val = emscripten_atomic_cas_u32((void*)lock, 0, 1);
  if (!val) return true;

  double t = emscripten_get_now();
  double waitEnd = t + maxWaitMilliseconds;
  while (t < waitEnd) {
    val = emscripten_atomic_cas_u32((void*)lock, 0, 1);
    if (!val) return true;
    t = emscripten_get_now();
  }
  return false;
}

void emscripten_lock_busyspin_waitinf_acquire(emscripten_lock_t *lock) {
  emscripten_lock_t val;
  do {
    val = emscripten_atomic_cas_u32((void*)lock, 0, 1);
  } while (val);
}

bool emscripten_lock_try_acquire(emscripten_lock_t *lock) {
  emscripten_lock_t val = emscripten_atomic_cas_u32((void*)lock, 0, 1);
  return !val;
}

void emscripten_lock_release(emscripten_lock_t *lock) {
  emscripten_atomic_store_u32((void*)lock, 0);
  emscripten_atomic_notify((void*)lock, 1);
}

void emscripten_semaphore_init(emscripten_semaphore_t *sem, int num) {
  emscripten_atomic_store_u32((void*)sem, num);
}

int emscripten_semaphore_try_acquire(emscripten_semaphore_t *sem, int num) {
  uint32_t val = num;
  for (;;) {
    uint32_t ret = emscripten_atomic_cas_u32((void*)sem, val, val - num);
    if (ret == val) return val - num;
    if (ret < num) return -1;
    val = ret;
  }
}

int emscripten_semaphore_wait_acquire(emscripten_semaphore_t *sem, int num, int64_t maxWaitNanoseconds) {
  int val = emscripten_atomic_load_u32((void*)sem);
  int64_t waitEnd = 0;
  for (;;) {
    while (val < num && maxWaitNanoseconds > 0) {
      // Deley initialization of waitEnd until we know we are going to need to
      // wait (since emscripten_performance_now is not cheap).
      if (!waitEnd) waitEnd = (int64_t)(emscripten_performance_now() * 1e6) + maxWaitNanoseconds;
      emscripten_atomic_wait_u32((void*)sem, val, maxWaitNanoseconds);
      val = emscripten_atomic_load_u32((void*)sem);
      maxWaitNanoseconds = waitEnd - (int64_t)(emscripten_performance_now() * 1e6);
    }
    // If we exited the loop and still don't have enough, it means we timed out.
    if (val < num) return -1;
    int ret = (int)emscripten_atomic_cas_u32((void*)sem, val, val - num);
    if (ret == val) return val - num;
    val = ret;
  }
}

int emscripten_semaphore_waitinf_acquire(emscripten_semaphore_t *sem, int num) {
  int val = emscripten_atomic_load_u32((void*)sem);
  for (;;) {
    while (val < num) {
      emscripten_atomic_wait_u32((void*)sem, val, ATOMICS_WAIT_DURATION_INFINITE);
      val = emscripten_atomic_load_u32((void*)sem);
    }
    int ret = (int)emscripten_atomic_cas_u32((void*)sem, val, val - num);
    if (ret == val) return val - num;
    val = ret;
  }
}

uint32_t emscripten_semaphore_release(emscripten_semaphore_t *sem, int num) {
  uint32_t ret = emscripten_atomic_add_u32((void*)sem, num);
  emscripten_atomic_notify((void*)sem, num);
  return ret;
}

void emscripten_condvar_init(emscripten_condvar_t *condvar) {
  *condvar = EMSCRIPTEN_CONDVAR_T_STATIC_INITIALIZER;
}

void emscripten_condvar_waitinf(emscripten_condvar_t *condvar, emscripten_lock_t *lock) {
  int val = emscripten_atomic_load_u32((void*)condvar);
  emscripten_lock_release(lock);
  emscripten_atomic_wait_u32((void*)condvar, val, ATOMICS_WAIT_DURATION_INFINITE);
  emscripten_lock_waitinf_acquire(lock);
}

bool emscripten_condvar_wait(emscripten_condvar_t* condvar,
                             emscripten_lock_t* lock,
                             int64_t maxWaitNanoseconds) {
  int val = emscripten_atomic_load_u32((void*)condvar);
  emscripten_lock_release(lock);
  int waitValue = emscripten_atomic_wait_u32((void*)condvar, val, maxWaitNanoseconds);
  if (waitValue == ATOMICS_WAIT_TIMED_OUT) {
    return false;
  }

  return emscripten_lock_wait_acquire(lock, maxWaitNanoseconds);
}

ATOMICS_WAIT_TOKEN_T emscripten_condvar_wait_async(emscripten_condvar_t *condvar,
                                                   emscripten_lock_t *lock,
                                                   emscripten_async_wait_callback_t asyncWaitFinished,
                                                   void *userData,
                                                   double maxWaitMilliseconds) {
  int val = emscripten_atomic_load_u32((void*)condvar);
  emscripten_lock_release(lock);
  return emscripten_atomic_wait_async((void*)condvar, val, asyncWaitFinished, userData, maxWaitMilliseconds);
}

void emscripten_condvar_signal(emscripten_condvar_t *condvar, uint32_t numWaitersToSignal) {
  emscripten_atomic_add_u32((void*)condvar, 1);
  emscripten_atomic_notify((void*)condvar, numWaitersToSignal);
}
