/*
 * Copyright 2019 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.
 */

#include <errno.h>
#include <stdbool.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include "pthread_impl.h"
#include <emscripten/stack.h>
#include <emscripten/threading.h>
#include <emscripten/emscripten.h>

#ifdef __EMSCRIPTEN_WASM_WORKERS__
#error "This file contains stubs that should not be included in wasm workers builds."
#endif

bool emscripten_has_threading_support() { return false; }

int emscripten_num_logical_cores() { return 1; }

// These low level primites are defined in both pthreads and wasm workers
// builds.

int emscripten_futex_wait(volatile void /*uint32_t*/* addr,
                          uint32_t val,
                          double maxWaitMilliseconds) {
  if (!addr) {
    return -EINVAL;
  }
  if (*(uint32_t*)addr != val) {
    return -EWOULDBLOCK;
  }
  return -ENOTSUP;
}

int emscripten_futex_wake(volatile void /*uint32_t*/* addr, int count) {
  // nop
  return 0; // success
}

bool emscripten_is_main_runtime_thread() {
  return true;
}

void __wait(volatile int *addr, volatile int *waiters, int val, int priv) {}

void __lock(void* ptr) {}

void __unlock(void* ptr) {}

void __inhibit_ptc() {}

void __acquire_ptc() {}

void __release_ptc() {}

void emscripten_main_thread_process_queued_calls() {
  // nop
}

void emscripten_current_thread_process_queued_calls() {
  // nop
}

int pthread_mutex_init(
  pthread_mutex_t* __restrict mutex, const pthread_mutexattr_t* __restrict attr) {
  return 0;
}

int __pthread_mutex_lock(pthread_mutex_t* mutex) { return 0; }

weak_alias(__pthread_mutex_lock, pthread_mutex_lock);

int __pthread_mutex_unlock(pthread_mutex_t* mutex) { return 0; }

weak_alias(__pthread_mutex_unlock, pthread_mutex_unlock);

int __pthread_mutex_trylock(pthread_mutex_t* mutex) { return 0; }

weak_alias(__pthread_mutex_trylock, pthread_mutex_trylock);

struct timespec;

int __pthread_mutex_timedlock(
  pthread_mutex_t* __restrict mutex, const struct timespec* __restrict t) {
  return 0;
}

weak_alias(__pthread_mutex_timedlock, pthread_mutex_timedlock);

int pthread_mutex_destroy(pthread_mutex_t* mutex) { return 0; }

int pthread_mutex_consistent(pthread_mutex_t* mutex) { return 0; }

int pthread_barrier_init(
  pthread_barrier_t* __restrict mutex, const pthread_barrierattr_t* __restrict attr, unsigned u) {
  return 0;
}

int pthread_barrier_destroy(pthread_barrier_t* mutex) { return 0; }

int pthread_barrier_wait(pthread_barrier_t* mutex) { return 0; }

int __pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) {
  // ENOTSUP, while not mentioned in the pthread_create docs, does better
  // describe the situation.
  // See https://github.com/WebAssembly/wasi-libc/pull/716 for discussion
  // on this error code vs, for example, EAGAIN.
  return ENOTSUP;
}

weak_alias(__pthread_create, emscripten_builtin_pthread_create);
weak_alias(__pthread_create, pthread_create);

int __pthread_join(pthread_t thread, void **retval) {
  return EINVAL;
}

weak_alias(__pthread_join, emscripten_builtin_pthread_join);
weak_alias(__pthread_join, pthread_join);

static void* tls_entries[PTHREAD_KEYS_MAX];
static bool tls_key_used[PTHREAD_KEYS_MAX];

int __pthread_key_create(pthread_key_t* key, void (*destructor)(void*)) {
  if (key == 0) {
    return EINVAL;
  }
  // Find empty spot.
  for (pthread_key_t entry = 0; entry < PTHREAD_KEYS_MAX; entry++) {
    if (!tls_key_used[entry]) {
      tls_key_used[entry] = true;
      tls_entries[entry] = NULL;
      *key = entry;
      return 0;
    }
  }
  // No empty spots, return an error
  return EAGAIN;
}

int __pthread_key_delete(pthread_key_t key) {
  if (key < 0 || key >= PTHREAD_KEYS_MAX) {
    return EINVAL;
  }
  if (!tls_key_used[key]) {
    return EINVAL;
  }
  tls_key_used[key] = false;
  tls_entries[key] = NULL;
  return 0;
}

weak_alias(__pthread_key_delete, pthread_key_delete);
weak_alias(__pthread_key_create, pthread_key_create);

void* pthread_getspecific(pthread_key_t key) {
  if (key < 0 || key >= PTHREAD_KEYS_MAX) {
    return NULL;
  }
  if (!tls_key_used[key]) {
    return NULL;
  }
  return tls_entries[key];
}

int pthread_setspecific(pthread_key_t key, const void* value) {
  if (key < 0 || key >= PTHREAD_KEYS_MAX) {
    return EINVAL;
  }
  if (!tls_key_used[key]) {
    return EINVAL;
  }
  tls_entries[key] = (void*)value;
  return 0;
}

/*magic number to detect if we have not run yet*/
#define PTHREAD_ONCE_MAGIC_ID 0x13579BDF

int __pthread_once(pthread_once_t* once_control, void (*init_routine)(void)) {
  if (*once_control != PTHREAD_ONCE_MAGIC_ID) {
    init_routine();
    *once_control = PTHREAD_ONCE_MAGIC_ID;
  }
  return 0;
}

weak_alias(__pthread_once, pthread_once);

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) {
  return 0;
}

int pthread_cond_signal(pthread_cond_t *cond) {
  return 0;
}

int __private_cond_signal(pthread_cond_t *c, int n) {
  return 0;
}

int pthread_cond_broadcast(pthread_cond_t *cond) {
  return 0;
}

int pthread_cond_init(pthread_cond_t *__restrict x, const pthread_condattr_t *__restrict y) {
  return 0;
}

int pthread_cond_destroy(pthread_cond_t * x) {
  return 0;
}

int __pthread_cond_timedwait(pthread_cond_t *__restrict x, pthread_mutex_t *__restrict y, const struct timespec *__restrict z) {
  return 0;
}

weak_alias(__pthread_cond_timedwait, pthread_cond_timedwait);

int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)) {
  return 0;
}

int pthread_cancel(pthread_t thread) {
  return 0;
}

void __pthread_testcancel() {}

weak_alias(__pthread_testcancel, pthread_testcancel);

_Noreturn void __pthread_exit(void* status) {
   exit(0);
}

weak_alias(__pthread_exit, emscripten_builtin_pthread_exit);
weak_alias(__pthread_exit, pthread_exit);

int __pthread_detach(pthread_t t) {
  return 0;
}

weak_alias(__pthread_detach, emscripten_builtin_pthread_detach);
weak_alias(__pthread_detach, pthread_detach);
weak_alias(__pthread_detach, thrd_detach);

// pthread_equal is defined as a macro in C, as a function for C++; undef it
// here so we define the function for C++ that links to us.
#ifdef pthread_equal
#undef pthread_equal
#endif

int pthread_equal(pthread_t t1, pthread_t t2) {
  return t1 == t2;
}

int pthread_kill(pthread_t thread, int sig) {
  if (thread != pthread_self()) {
    return EINVAL;
  }
  raise(sig);
  return 0;
}

int pthread_setcancelstate(int state, int* oldstate) {
  return 0;
}

int pthread_setcanceltype(int type, int* oldtype) {
  return 0;
}

int pthread_rwlock_init(pthread_rwlock_t* rwlock, const pthread_rwlockattr_t* attr) {
  return 0;
}

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock) {
  return 0;
}

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock) {
  return 0;
}

int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock) {
  return 0;
}

int pthread_rwlock_timedrdlock(pthread_rwlock_t* rwlock, const struct timespec* abs_timeout) {
  return 0;
}

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock) {
  return 0;
}

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock) {
  return 0;
}

int pthread_rwlock_timedwrlock(pthread_rwlock_t* rwlock, const struct timespec* abs_timeout) {
  return 0;
}

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock) {
  return 0;
}

int sem_post(sem_t *sem) {
  return 0;
}

int sem_wait(sem_t *sem) {
  return 0;
}

int sem_trywait(sem_t *sem) {
  return 0;
}

// When pthreads is not enabled, we can't use the Atomics futex api to do
// proper sleeps, so simulate a busy spin wait loop instead.
void emscripten_thread_sleep(double msecs) {
  double start = emscripten_get_now();
  double now = start;
  do {
    _emscripten_yield(now);
    now = emscripten_get_now();
  } while (now - start < msecs);
}
