/*
 * Copyright 2009-present MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <common-prelude.h>

#include <common-macros-private.h>

#include <common-config.h>

#ifndef MONGO_C_DRIVER_COMMON_THREAD_PRIVATE_H
#define MONGO_C_DRIVER_COMMON_THREAD_PRIVATE_H

#include <bson/compat.h>
#include <bson/config.h>
#include <bson/macros.h>

BSON_BEGIN_DECLS

#define mcommon_thread_create COMMON_NAME(thread_create)
#define mcommon_thread_join COMMON_NAME(thread_join)

#if defined(BSON_OS_UNIX)
#include <pthread.h>

#define BSON_ONCE_FUN(n) void n(void)
#define BSON_ONCE_RETURN return
#define BSON_ONCE_INIT PTHREAD_ONCE_INIT
#define bson_once(o, c)                         \
   do {                                         \
      BSON_ASSERT(pthread_once((o), (c)) == 0); \
   } while (0)
#define bson_once_t pthread_once_t
#define bson_thread_t pthread_t
#define BSON_THREAD_FUN(_function_name, _arg_name) void *(_function_name)(void *(_arg_name))
#define BSON_THREAD_FUN_TYPE(_function_name) void *(*(_function_name))(void *)
#define BSON_THREAD_RETURN return NULL

/* this macro can be defined as a as a build configuration option
 * with -DENABLE_DEBUG_ASSERTIONS=ON.  its purpose is to allow for functions
 * that require a mutex to be locked on entry to assert that the mutex
 * is actually locked.
 * this can prevent bugs where a caller forgets to lock the mutex. */

#ifndef MONGOC_ENABLE_DEBUG_ASSERTIONS

#define bson_mutex_destroy(m)                       \
   do {                                             \
      BSON_ASSERT(pthread_mutex_destroy((m)) == 0); \
   } while (0)

#define bson_mutex_init(_n)                             \
   do {                                                 \
      BSON_ASSERT(pthread_mutex_init((_n), NULL) == 0); \
   } while (0)

#define bson_mutex_lock(m)                       \
   do {                                          \
      BSON_ASSERT(pthread_mutex_lock((m)) == 0); \
   } while (0)

#define bson_mutex_t pthread_mutex_t

#define bson_mutex_unlock(m)                       \
   do {                                            \
      BSON_ASSERT(pthread_mutex_unlock((m)) == 0); \
   } while (0)

#else
#include <mlib/config.h>

typedef struct {
   pthread_t lock_owner;
   pthread_mutex_t wrapped_mutex;
   bool valid_tid;
} bson_mutex_t;

#define bson_mutex_destroy(mutex)                                       \
   do {                                                                 \
      BSON_ASSERT(pthread_mutex_destroy(&(mutex)->wrapped_mutex) == 0); \
   } while (0);

#define bson_mutex_init(mutex)                                             \
   do {                                                                    \
      BSON_ASSERT(pthread_mutex_init(&(mutex)->wrapped_mutex, NULL) == 0); \
      (mutex)->valid_tid = false;                                          \
   } while (0);

#define bson_mutex_lock(mutex)                                       \
   do {                                                              \
      BSON_ASSERT(pthread_mutex_lock(&(mutex)->wrapped_mutex) == 0); \
      (mutex)->lock_owner = pthread_self();                          \
      (mutex)->valid_tid = true;                                     \
   } while (0);

#define bson_mutex_unlock(mutex)                                       \
   do {                                                                \
      (mutex)->valid_tid = false;                                      \
      BSON_ASSERT(pthread_mutex_unlock(&(mutex)->wrapped_mutex) == 0); \
   } while (0);

#endif

#else
#include <process.h>
#define BSON_ONCE_FUN(n)                                                                         \
   BOOL CALLBACK MLIB_PRAGMA_IF_MSVC(warning(push)) MLIB_PRAGMA_IF_MSVC(warning(disable : 4100)) \
      n(PINIT_ONCE _ignored_a, PVOID _ignored_b, PVOID *_ignored_c) MLIB_PRAGMA_IF_MSVC(warning(pop))
#define BSON_ONCE_INIT INIT_ONCE_STATIC_INIT
#define BSON_ONCE_RETURN return true
#define bson_mutex_destroy DeleteCriticalSection
#define bson_mutex_init InitializeCriticalSection
#define bson_mutex_lock EnterCriticalSection
#define bson_mutex_t CRITICAL_SECTION
#define bson_mutex_unlock LeaveCriticalSection
#define bson_once(o, c)                                       \
   do {                                                       \
      BSON_ASSERT(InitOnceExecuteOnce((o), (c), NULL, NULL)); \
   } while (0)
#define bson_once_t INIT_ONCE
#define bson_thread_t HANDLE
#define BSON_THREAD_FUN(_function_name, _arg_name) unsigned(__stdcall _function_name)(void *(_arg_name))
#define BSON_THREAD_FUN_TYPE(_function_name) unsigned(__stdcall * _function_name)(void *)
#define BSON_THREAD_RETURN return 0
#endif

/* Functions that require definitions get the common prefix (_mongoc for
 * libmongoc or _bson for libbson) to avoid duplicate symbols when linking both
 * libbson and libmongoc statically. */
int
mcommon_thread_join(bson_thread_t thread);
// mcommon_thread_create returns 0 on success. Returns a non-zero error code on
// error. Callers may use `bson_strerror_r` to get an error message from the
// returned error code.
int
mcommon_thread_create(bson_thread_t *thread, BSON_THREAD_FUN_TYPE(func), void *arg);

#if defined(MONGOC_ENABLE_DEBUG_ASSERTIONS) && defined(BSON_OS_UNIX)
#define mcommon_mutex_is_locked COMMON_NAME(mutex_is_locked)
bool
mcommon_mutex_is_locked(bson_mutex_t *mutex);
#endif

/**
 * @brief A shared mutex (a read-write lock)
 *
 * A shared mutex can be locked in 'shared' mode or 'exclusive' mode. Only one
 * thread may hold exclusive mode at a time. Any number of threads may hold
 * the lock in shared mode simultaneously. No thread can hold in exclusive mode
 * while another thread holds in shared mode, and vice-versa.
 */
typedef struct bson_shared_mutex_t {
   BSON_IF_WINDOWS(SRWLOCK native;)
   BSON_IF_POSIX(pthread_rwlock_t native;)
} bson_shared_mutex_t;

static BSON_INLINE void
bson_shared_mutex_init(bson_shared_mutex_t *mtx)
{
   BSON_IF_WINDOWS(InitializeSRWLock(&mtx->native));
   BSON_IF_POSIX(BSON_ASSERT(pthread_rwlock_init(&mtx->native, NULL) == 0);)
}

static BSON_INLINE void
bson_shared_mutex_destroy(bson_shared_mutex_t *mtx)
{
   BSON_IF_WINDOWS((void)mtx;)
   BSON_IF_POSIX(BSON_ASSERT(pthread_rwlock_destroy(&mtx->native) == 0);)
}

static BSON_INLINE void
bson_shared_mutex_lock_shared(bson_shared_mutex_t *mtx)
{
   BSON_IF_WINDOWS(AcquireSRWLockShared(&mtx->native);)
   BSON_IF_POSIX(BSON_ASSERT(pthread_rwlock_rdlock(&mtx->native) == 0);)
}

static BSON_INLINE void
bson_shared_mutex_lock(bson_shared_mutex_t *mtx)
{
   BSON_IF_WINDOWS(AcquireSRWLockExclusive(&mtx->native);)
   BSON_IF_POSIX(BSON_ASSERT(pthread_rwlock_wrlock(&mtx->native) == 0);)
}

static BSON_INLINE void
bson_shared_mutex_unlock(bson_shared_mutex_t *mtx)
{
   BSON_IF_WINDOWS(ReleaseSRWLockExclusive(&mtx->native);)
   BSON_IF_POSIX(BSON_ASSERT(pthread_rwlock_unlock(&mtx->native) == 0);)
}

static BSON_INLINE void
bson_shared_mutex_unlock_shared(bson_shared_mutex_t *mtx)
{
   BSON_IF_WINDOWS(ReleaseSRWLockShared(&mtx->native);)
   BSON_IF_POSIX(BSON_ASSERT(pthread_rwlock_unlock(&mtx->native) == 0);)
}

BSON_END_DECLS

#endif /* MONGO_C_DRIVER_COMMON_THREAD_PRIVATE_H */
