/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/**
 * \file
 * Portability wrappers around POSIX Mutexes
 *
 * Authors: Jeffrey Stedfast <fejj@ximian.com>
 *
 * Copyright 2002 Ximian, Inc. (www.ximian.com)
 *
 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
 */

#ifndef __MONO_OS_MUTEX_H__
#define __MONO_OS_MUTEX_H__

#include <config.h>
#include <glib.h>

#include <stdlib.h>
#include <string.h>
#include <time.h>

#if !defined(HOST_WIN32)
#include <pthread.h>
#include <errno.h>
#else
#include <winsock2.h>
#include <windows.h>
#endif

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#define MONO_INFINITE_WAIT ((guint32) 0xFFFFFFFF)

#if !defined(HOST_WIN32)

#if !defined(CLOCK_MONOTONIC) || defined(HOST_DARWIN) || defined(HOST_ANDROID) || defined(HOST_WASM)
#define BROKEN_CLOCK_SOURCE
#endif

typedef pthread_mutex_t mono_mutex_t;
typedef pthread_cond_t mono_cond_t;

#ifndef DISABLE_THREADS

static inline void
mono_os_mutex_init_type (mono_mutex_t *mutex, int type)
{
	int res;
	pthread_mutexattr_t attr;

	res = pthread_mutexattr_init (&attr);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_mutexattr_init failed with \"%s\" (%d)", __func__, g_strerror (res), res);

	res = pthread_mutexattr_settype (&attr, type);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_mutexattr_settype failed with \"%s\" (%d)", __func__, g_strerror (res), res);

#if !defined(__HAIKU__) && defined (PTHREAD_PRIO_INHERIT) && HAVE_DECL_PTHREAD_MUTEXATTR_SETPROTOCOL
	/* use PTHREAD_PRIO_INHERIT if possible */
	res = pthread_mutexattr_setprotocol (&attr, PTHREAD_PRIO_INHERIT);
	if (G_UNLIKELY (res != 0 && res != ENOTSUP))
		g_error ("%s: pthread_mutexattr_setprotocol failed with \"%s\" (%d)", __func__, g_strerror (res), res);
#endif

	res = pthread_mutex_init (mutex, &attr);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_mutex_init failed with \"%s\" (%d)", __func__, g_strerror (res), res);

	res = pthread_mutexattr_destroy (&attr);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_mutexattr_destroy failed with \"%s\" (%d)", __func__, g_strerror (res), res);
}

static inline void
mono_os_mutex_init (mono_mutex_t *mutex)
{
	mono_os_mutex_init_type(mutex, PTHREAD_MUTEX_DEFAULT);
}

static inline void
mono_os_mutex_init_recursive (mono_mutex_t *mutex)
{
	mono_os_mutex_init_type(mutex, PTHREAD_MUTEX_RECURSIVE);
}

static inline void
mono_os_mutex_destroy (mono_mutex_t *mutex)
{
	int res;

	res = pthread_mutex_destroy (mutex);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_mutex_destroy failed with \"%s\" (%d)", __func__, g_strerror (res), res);
}

static inline void
mono_os_mutex_lock (mono_mutex_t *mutex)
{
	int res;

	res = pthread_mutex_lock (mutex);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_mutex_lock failed with \"%s\" (%d)", __func__, g_strerror (res), res);
}

static inline int
mono_os_mutex_trylock (mono_mutex_t *mutex)
{
	int res;

	res = pthread_mutex_trylock (mutex);
	if (G_UNLIKELY (res != 0 && res != EBUSY))
		g_error ("%s: pthread_mutex_trylock failed with \"%s\" (%d)", __func__, g_strerror (res), res);

	return res != 0 ? -1 : 0;
}

static inline void
mono_os_mutex_unlock (mono_mutex_t *mutex)
{
	int res;

	res = pthread_mutex_unlock (mutex);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_mutex_unlock failed with \"%s\" (%d)", __func__, g_strerror (res), res);
}

#else /* DISABLE_THREADS */

static inline void
mono_os_mutex_init_type (mono_mutex_t *mutex, int type)
{
}

static inline void
mono_os_mutex_init (mono_mutex_t *mutex)
{
}

static inline void
mono_os_mutex_init_recursive (mono_mutex_t *mutex)
{
}

static inline void
mono_os_mutex_destroy (mono_mutex_t *mutex)
{
}

static inline void
mono_os_mutex_lock (mono_mutex_t *mutex)
{
}

static inline int
mono_os_mutex_trylock (mono_mutex_t *mutex)
{
	return 0;
}

static inline void
mono_os_mutex_unlock (mono_mutex_t *mutex)
{
}

#endif /* DISABLE_THREADS */

static inline void
mono_os_cond_init (mono_cond_t *cond)
{
	int res;

#ifdef BROKEN_CLOCK_SOURCE
	res = pthread_cond_init (cond, NULL);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_cond_init failed with \"%s\" (%d)", __func__, g_strerror (res), res);
#else
	/* POSIX standard does not compel to have CLOCK_MONOTONIC */
	pthread_condattr_t attr;

	res = pthread_condattr_init (&attr);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_condattr_init failed with \"%s\" (%d)", __func__, g_strerror (res), res);

	res = pthread_condattr_setclock (&attr, CLOCK_MONOTONIC);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_condattr_setclock failed with \"%s\" (%d)", __func__, g_strerror (res), res);

	/* Attach an attribute having CLOCK_MONOTONIC to condition */
	res = pthread_cond_init (cond, &attr);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_cond_init failed with \"%s\" (%d)", __func__, g_strerror (res), res);

	res = pthread_condattr_destroy (&attr);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_condattr_destroy failed with \"%s\" (%d)", __func__, g_strerror (res), res);
#endif
}

static inline void
mono_os_cond_destroy (mono_cond_t *cond)
{
	int res;

	res = pthread_cond_destroy (cond);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_cond_destroy failed with \"%s\" (%d)", __func__, g_strerror (res), res);
}

static inline void
mono_os_cond_wait (mono_cond_t *cond, mono_mutex_t *mutex)
{
	int res;

	res = pthread_cond_wait (cond, mutex);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_cond_wait failed with \"%s\" (%d)", __func__, g_strerror (res), res);
}

int
mono_os_cond_timedwait (mono_cond_t *cond, mono_mutex_t *mutex, guint32 timeout_ms);

static inline void
mono_os_cond_signal (mono_cond_t *cond)
{
	int res;

	res = pthread_cond_signal (cond);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_cond_signal failed with \"%s\" (%d)", __func__, g_strerror (res), res);
}

static inline void
mono_os_cond_broadcast (mono_cond_t *cond)
{
	int res;

	res = pthread_cond_broadcast (cond);
	if (G_UNLIKELY (res != 0))
		g_error ("%s: pthread_cond_broadcast failed with \"%s\" (%d)", __func__, g_strerror (res), res);
}

#else

/* Vanilla MinGW is missing some defs, load them from MinGW-w64. */
#if defined __MINGW32__ && !defined __MINGW64_VERSION_MAJOR && (_WIN32_WINNT >= 0x0600)

/* Fixme: Opaque structs */
typedef PVOID RTL_CONDITION_VARIABLE;
typedef PVOID RTL_SRWLOCK;

#ifndef _RTL_RUN_ONCE_DEF
#define _RTL_RUN_ONCE_DEF 1
typedef PVOID RTL_RUN_ONCE, *PRTL_RUN_ONCE;
typedef DWORD (WINAPI *PRTL_RUN_ONCE_INIT_FN)(PRTL_RUN_ONCE, PVOID, PVOID *);
#define RTL_RUN_ONCE_INIT 0
#define RTL_RUN_ONCE_CHECK_ONLY 1UL
#define RTL_RUN_ONCE_ASYNC 2UL
#define RTL_RUN_ONCE_INIT_FAILED 4UL
#define RTL_RUN_ONCE_CTX_RESERVED_BITS 2
#endif /* _RTL_RUN_ONCE_DEF */
#define RTL_SRWLOCK_INIT 0
#define RTL_CONDITION_VARIABLE_INIT 0
#define RTL_CONDITION_VARIABLE_LOCKMODE_SHARED 1

#define CONDITION_VARIABLE_INIT RTL_CONDITION_VARIABLE_INIT
#define CONDITION_VARIABLE_LOCKMODE_SHARED RTL_CONDITION_VARIABLE_LOCKMODE_SHARED
#define SRWLOCK_INIT RTL_SRWLOCK_INIT

/*Condition Variables http://msdn.microsoft.com/en-us/library/ms682052%28VS.85%29.aspx*/
typedef RTL_CONDITION_VARIABLE CONDITION_VARIABLE, *PCONDITION_VARIABLE;
typedef RTL_SRWLOCK SRWLOCK, *PSRWLOCK;

WINBASEAPI VOID WINAPI InitializeConditionVariable(PCONDITION_VARIABLE ConditionVariable);
WINBASEAPI WINBOOL WINAPI SleepConditionVariableCS(PCONDITION_VARIABLE ConditionVariable, PCRITICAL_SECTION CriticalSection, DWORD dwMilliseconds);
WINBASEAPI WINBOOL WINAPI SleepConditionVariableSRW(PCONDITION_VARIABLE ConditionVariable, PSRWLOCK SRWLock, DWORD dwMilliseconds, ULONG Flags);
WINBASEAPI VOID WINAPI WakeAllConditionVariable(PCONDITION_VARIABLE ConditionVariable);
WINBASEAPI VOID WINAPI WakeConditionVariable(PCONDITION_VARIABLE ConditionVariable);

/*Slim Reader/Writer (SRW) Locks http://msdn.microsoft.com/en-us/library/aa904937%28VS.85%29.aspx*/
WINBASEAPI VOID WINAPI AcquireSRWLockExclusive(PSRWLOCK SRWLock);
WINBASEAPI VOID WINAPI AcquireSRWLockShared(PSRWLOCK SRWLock);
WINBASEAPI VOID WINAPI InitializeSRWLock(PSRWLOCK SRWLock);
WINBASEAPI VOID WINAPI ReleaseSRWLockExclusive(PSRWLOCK SRWLock);
WINBASEAPI VOID WINAPI ReleaseSRWLockShared(PSRWLOCK SRWLock);

WINBASEAPI BOOLEAN TryAcquireSRWLockExclusive(PSRWLOCK SRWLock);
WINBASEAPI BOOLEAN TryAcquireSRWLockShared(PSRWLOCK SRWLock);

/*One-Time Initialization http://msdn.microsoft.com/en-us/library/aa363808(VS.85).aspx*/
#define INIT_ONCE_ASYNC 0x00000002UL
#define INIT_ONCE_INIT_FAILED 0x00000004UL

typedef PRTL_RUN_ONCE PINIT_ONCE;
typedef PRTL_RUN_ONCE LPINIT_ONCE;
typedef WINBOOL CALLBACK (*PINIT_ONCE_FN) (PINIT_ONCE InitOnce, PVOID Parameter, PVOID *Context);

WINBASEAPI WINBOOL WINAPI InitOnceBeginInitialize(LPINIT_ONCE lpInitOnce, DWORD dwFlags, PBOOL fPending, LPVOID *lpContext);
WINBASEAPI WINBOOL WINAPI InitOnceComplete(LPINIT_ONCE lpInitOnce, DWORD dwFlags, LPVOID lpContext);
WINBASEAPI WINBOOL WINAPI InitOnceExecuteOnce(PINIT_ONCE InitOnce, PINIT_ONCE_FN InitFn, PVOID Parameter, LPVOID *Context);

/* https://msdn.microsoft.com/en-us/library/windows/desktop/ms683477(v=vs.85).aspx */
WINBASEAPI BOOL WINAPI InitializeCriticalSectionEx(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount, DWORD Flags);

#define CRITICAL_SECTION_NO_DEBUG_INFO 0x01000000

#endif /* defined __MINGW32__ && !defined __MINGW64_VERSION_MAJOR && (_WIN32_WINNT >= 0x0600) */

typedef CRITICAL_SECTION mono_mutex_t;
typedef CONDITION_VARIABLE mono_cond_t;

static inline void
mono_os_mutex_init (mono_mutex_t *mutex)
{
	BOOL res;

	res = InitializeCriticalSectionEx (mutex, 0, CRITICAL_SECTION_NO_DEBUG_INFO);
	if (G_UNLIKELY (res == 0))
		g_error ("%s: InitializeCriticalSectionEx failed with error %d", __func__, GetLastError ());
}

static inline void
mono_os_mutex_init_recursive (mono_mutex_t *mutex)
{
	BOOL res;

	res = InitializeCriticalSectionEx (mutex, 0, CRITICAL_SECTION_NO_DEBUG_INFO);
	if (G_UNLIKELY (res == 0))
		g_error ("%s: InitializeCriticalSectionEx failed with error %d", __func__, GetLastError ());
}

static inline void
mono_os_mutex_destroy (mono_mutex_t *mutex)
{
	DeleteCriticalSection (mutex);
}

static inline void
mono_os_mutex_lock (mono_mutex_t *mutex)
{
	EnterCriticalSection (mutex);
}

static inline int
mono_os_mutex_trylock (mono_mutex_t *mutex)
{
	return TryEnterCriticalSection (mutex) == 0 ? -1 : 0;
}

static inline void
mono_os_mutex_unlock (mono_mutex_t *mutex)
{
	LeaveCriticalSection (mutex);
}

static inline void
mono_os_cond_init (mono_cond_t *cond)
{
	InitializeConditionVariable (cond);
}

static inline void
mono_os_cond_destroy (mono_cond_t *cond)
{
	/* Beauty of win32 API: do not destroy it */
}

static inline void
mono_os_cond_wait (mono_cond_t *cond, mono_mutex_t *mutex)
{
	BOOL res;

	res = SleepConditionVariableCS (cond, mutex, INFINITE);
	if (G_UNLIKELY (res == 0))
		g_error ("%s: SleepConditionVariableCS failed with error %d", __func__, GetLastError ());
}

static inline int
mono_os_cond_timedwait (mono_cond_t *cond, mono_mutex_t *mutex, guint32 timeout_ms)
{
	BOOL res;

	res = SleepConditionVariableCS (cond, mutex, timeout_ms);
	if (G_UNLIKELY (res == 0 && GetLastError () != ERROR_TIMEOUT))
		g_error ("%s: SleepConditionVariableCS failed with error %d", __func__, GetLastError ());

	return res == 0 ? -1 : 0;
}

static inline void
mono_os_cond_signal (mono_cond_t *cond)
{
	WakeConditionVariable (cond);
}

static inline void
mono_os_cond_broadcast (mono_cond_t *cond)
{
	WakeAllConditionVariable (cond);
}

#endif

#endif /* __MONO_OS_MUTEX_H__ */
