/*
 * Native threading implementation definition, used by the C++ library to provide C++ threads.
 */
#ifndef LIBC___THREADING_SUPPORT_H
#define LIBC___THREADING_SUPPORT_H

#include <chrono>
#include <cerrno>
#include <cstdint>

#include <threads.h>
#include <sys/syscalls.h>
#include <time.h>

#include <_libc.h>

_LIBCPP_BEGIN_NAMESPACE_STD

// use the system timespec type
typedef ::timespec __libcpp_timespec_t;

// calling convention for TLS destructors: we use the standard C calling convention
#define _LIBCPP_TLS_DESTRUCTOR_CC

// declare types
typedef mtx_t __libcpp_mutex_t;
#define _LIBCPP_MUTEX_INITIALIZER { .flag = 0 }

typedef mtx_t __libcpp_recursive_mutex_t;

typedef cnd_t __libcpp_condvar_t;
#define _LIBCPP_CONDVAR_INITIALIZER {}

typedef once_flag __libcpp_exec_once_flag;
#define _LIBCPP_EXEC_ONCE_INITIALIZER ONCE_FLAG_INIT

typedef thrd_t __libcpp_thread_id;
#define _LIBCPP_NULL_THREAD 0U
typedef thrd_t __libcpp_thread_t;

typedef tss_t __libcpp_tls_key;

// typedef void *__libcpp_semaphore_t;

// declare some helpers
namespace __thread_helpers {
/**
 * Helper to convert a number of nanoseconds to a timespec value.
 */
inline struct timespec __convert_to_timespec(const chrono::nanoseconds& __ns) {
  using namespace chrono;
  seconds __s = duration_cast<seconds>(__ns);
  __libcpp_timespec_t __ts;
  typedef decltype(__ts.tv_sec) __ts_sec;
  const __ts_sec __ts_sec_max = numeric_limits<__ts_sec>::max();

  if (__s.count() < __ts_sec_max)
  {
    __ts.tv_sec = static_cast<__ts_sec>(__s.count());
    __ts.tv_nsec = static_cast<decltype(__ts.tv_nsec)>((__ns - __s).count());
  }
  else
  {
    __ts.tv_sec = __ts_sec_max;
    __ts.tv_nsec = 999999999; // (10^9 - 1)
  }

  return __ts;
};
}

// Mutex
inline int __libcpp_recursive_mutex_init(__libcpp_recursive_mutex_t *__m) {
    return mtx_init(__m, mtx_plain | mtx_recursive) == thrd_success ? 0 : EINVAL;
} 
inline int __libcpp_recursive_mutex_lock(__libcpp_recursive_mutex_t *__m) {
    return mtx_lock(__m) == thrd_success ? 0 : EINVAL;
}
inline bool __libcpp_recursive_mutex_trylock(__libcpp_recursive_mutex_t *__m) {
    return mtx_trylock(__m) == thrd_success;
}
inline int __libcpp_recursive_mutex_unlock(__libcpp_recursive_mutex_t *__m) {
    return mtx_unlock(__m) == thrd_success ? 0 : EINVAL;
}
inline int __libcpp_recursive_mutex_destroy(__libcpp_recursive_mutex_t *__m) {
    mtx_destroy(__m);
    return 0;
}

inline int __libcpp_mutex_lock(__libcpp_mutex_t *__m) {
    return mtx_lock(__m) == thrd_success ? 0 : EINVAL;
}
inline bool __libcpp_mutex_trylock(__libcpp_mutex_t *__m) {
    return mtx_trylock(__m) == thrd_success;
}
inline int __libcpp_mutex_unlock(__libcpp_mutex_t *__m) {
    return mtx_unlock(__m) == thrd_success ? 0 : EINVAL;
}
inline int __libcpp_mutex_destroy(__libcpp_mutex_t *__m) {
    mtx_destroy(__m);
    return 0;
}

// Condition variable
inline int __libcpp_condvar_signal(__libcpp_condvar_t* __cv) {
    return cnd_signal(__cv) == thrd_success ? 0 : EINVAL;
}
inline int __libcpp_condvar_broadcast(__libcpp_condvar_t* __cv) {
    return cnd_broadcast(__cv) == thrd_success ? 0 : EINVAL;
}
inline int __libcpp_condvar_wait(__libcpp_condvar_t* __cv, __libcpp_mutex_t* __m) {
    return cnd_wait(__cv, __m) == thrd_success ? 0 : EINVAL;
}
inline int __libcpp_condvar_timedwait(__libcpp_condvar_t *__cv, __libcpp_mutex_t *__m,
        __libcpp_timespec_t *__ts) {
    int __ec = cnd_timedwait(__cv, __m, __ts);
    return __ec == thrd_timedout ? ETIMEDOUT : __ec;
}
inline int __libcpp_condvar_destroy(__libcpp_condvar_t* __cv) {
    cnd_destroy(__cv);
    return 0;
}

/*
#ifndef _LIBCPP_NO_NATIVE_SEMAPHORES
// Semaphore
LIBC_EXPORT bool __libcpp_semaphore_init(__libcpp_semaphore_t* __sem, int __init);
LIBC_EXPORT bool __libcpp_semaphore_destroy(__libcpp_semaphore_t* __sem);
LIBC_EXPORT bool __libcpp_semaphore_post(__libcpp_semaphore_t* __sem);
LIBC_EXPORT bool __libcpp_semaphore_wait(__libcpp_semaphore_t* __sem);
LIBC_EXPORT bool __libcpp_semaphore_wait_timed(__libcpp_semaphore_t* __sem,
std::chrono::nanoseconds const& __ns);

#endif // _LIBCPP_NO_NATIVE_SEMAPHORES
*/

// Execute once
inline int __libcpp_execute_once(__libcpp_exec_once_flag *flag, void (*init_routine)(void)) {
    ::call_once(flag, init_routine);
    return 0;
}

// Thread id
inline bool __libcpp_thread_id_equal(__libcpp_thread_id t1, __libcpp_thread_id t2) {
    return thrd_equal(t1, t2) != 0;
}

// Returns non-zero if t1 < t2, otherwise 0
inline bool __libcpp_thread_id_less(__libcpp_thread_id t1, __libcpp_thread_id t2) {
    return t1 < t2;
}

// creates a new thread
inline int __libcpp_thread_create(__libcpp_thread_t *__t, void *(*__func)(void *),
                           void *__arg) {
    int __ec = thrd_create(__t, reinterpret_cast<thrd_start_t>(__func), __arg);
    switch(__ec) {
        case thrd_success:
            return 0;
        case thrd_nomem:
            return ENOMEM;
        default:
            return EINVAL;
    }
}

inline __libcpp_thread_id __libcpp_thread_get_current_id() {
    return thrd_current();
}
inline __libcpp_thread_id __libcpp_thread_get_id(const __libcpp_thread_t *__t) {
    return *__t;
}
inline int __libcpp_thread_join(__libcpp_thread_t *__t) {
    return thrd_join(*__t, nullptr) == thrd_success ? 0 : EINVAL;
}

inline int __libcpp_thread_detach(__libcpp_thread_t *__t) {
    return thrd_detach(*__t) == thrd_success ? 0 : EINVAL;
}

inline void __libcpp_thread_yield() {
    thrd_yield();
}

inline void __libcpp_thread_sleep_for(const chrono::nanoseconds& __ns) {
    struct timespec __ts = __thread_helpers::__convert_to_timespec(__ns);
    thrd_sleep(&__ts, nullptr);
}

/// Since thrd_t is a pointer, nullptr is an invalid thread.
inline bool __libcpp_thread_isnull(const __libcpp_thread_t *__t) {
    return __libcpp_thread_get_id(__t) == 0;
}

// Thread local storage
inline int __libcpp_tls_create(__libcpp_tls_key* __key, 
        void(_LIBCPP_TLS_DESTRUCTOR_CC* __at_exit)(void*)) {
    return tss_create(__key, __at_exit) == thrd_success ? 0 : EINVAL;
}
inline void *__libcpp_tls_get(__libcpp_tls_key __key) {
    return tss_get(__key);
}
inline int __libcpp_tls_set(__libcpp_tls_key __key, void *__p) {
    return tss_set(__key, __p) == thrd_success ? 0 : EINVAL;
}

_LIBCPP_END_NAMESPACE_STD
#endif
