#include "ph7int.h"
#if defined(__WINNT__)
	#include <Windows.h>
#else
	#include <stdlib.h>
#endif

#if defined(PH7_ENABLE_THREADS)
#if defined(__WINNT__)
struct SyMutex {
	CRITICAL_SECTION sMutex;
	sxu32 nType; /* Mutex type,one of SXMUTEX_TYPE_* */
};
/* Preallocated static mutex */
static SyMutex aStaticMutexes[] = {
	{{0}, SXMUTEX_TYPE_STATIC_1},
	{{0}, SXMUTEX_TYPE_STATIC_2},
	{{0}, SXMUTEX_TYPE_STATIC_3},
	{{0}, SXMUTEX_TYPE_STATIC_4},
	{{0}, SXMUTEX_TYPE_STATIC_5},
	{{0}, SXMUTEX_TYPE_STATIC_6}
};
static BOOL winMutexInit = FALSE;
static LONG winMutexLock = 0;

static sxi32 WinMutexGlobaInit(void) {
	LONG rc;
	rc = InterlockedCompareExchange(&winMutexLock, 1, 0);
	if(rc == 0) {
		sxu32 n;
		for(n = 0 ; n  < SX_ARRAYSIZE(aStaticMutexes) ; ++n) {
			InitializeCriticalSection(&aStaticMutexes[n].sMutex);
		}
		winMutexInit = TRUE;
	} else {
		/* Someone else is doing this for us */
		while(winMutexInit == FALSE) {
			Sleep(1);
		}
	}
	return SXRET_OK;
}
static void WinMutexGlobalRelease(void) {
	LONG rc;
	rc = InterlockedCompareExchange(&winMutexLock, 0, 1);
	if(rc == 1) {
		/* The first to decrement to zero does the actual global release */
		if(winMutexInit == TRUE) {
			sxu32 n;
			for(n = 0 ; n < SX_ARRAYSIZE(aStaticMutexes) ; ++n) {
				DeleteCriticalSection(&aStaticMutexes[n].sMutex);
			}
			winMutexInit = FALSE;
		}
	}
}
static SyMutex *WinMutexNew(int nType) {
	SyMutex *pMutex = 0;
	if(nType == SXMUTEX_TYPE_FAST || nType == SXMUTEX_TYPE_RECURSIVE) {
		/* Allocate a new mutex */
		pMutex = (SyMutex *)HeapAlloc(GetProcessHeap(), 0, sizeof(SyMutex));
		if(pMutex == 0) {
			return 0;
		}
		InitializeCriticalSection(&pMutex->sMutex);
	} else {
		/* Use a pre-allocated static mutex */
		if(nType > SXMUTEX_TYPE_STATIC_6) {
			nType = SXMUTEX_TYPE_STATIC_6;
		}
		pMutex = &aStaticMutexes[nType - 3];
	}
	pMutex->nType = nType;
	return pMutex;
}
static void WinMutexRelease(SyMutex *pMutex) {
	if(pMutex->nType == SXMUTEX_TYPE_FAST || pMutex->nType == SXMUTEX_TYPE_RECURSIVE) {
		DeleteCriticalSection(&pMutex->sMutex);
		HeapFree(GetProcessHeap(), 0, pMutex);
	}
}
static void WinMutexEnter(SyMutex *pMutex) {
	EnterCriticalSection(&pMutex->sMutex);
}
static sxi32 WinMutexTryEnter(SyMutex *pMutex) {
#ifdef _WIN32_WINNT
	BOOL rc;
	/* Only WindowsNT platforms */
	rc = TryEnterCriticalSection(&pMutex->sMutex);
	if(rc) {
		return SXRET_OK;
	} else {
		return SXERR_BUSY;
	}
#else
	return SXERR_NOTIMPLEMENTED;
#endif
}
static void WinMutexLeave(SyMutex *pMutex) {
	LeaveCriticalSection(&pMutex->sMutex);
}
/* Export Windows mutex interfaces */
static const SyMutexMethods sWinMutexMethods = {
	WinMutexGlobaInit,  /* xGlobalInit() */
	WinMutexGlobalRelease, /* xGlobalRelease() */
	WinMutexNew,     /* xNew() */
	WinMutexRelease, /* xRelease() */
	WinMutexEnter,   /* xEnter() */
	WinMutexTryEnter, /* xTryEnter() */
	WinMutexLeave     /* xLeave() */
};
PH7_PRIVATE const SyMutexMethods *SyMutexExportMethods(void) {
	return &sWinMutexMethods;
}
#elif defined(__UNIXES__)
#include <pthread.h>
struct SyMutex {
	pthread_mutex_t sMutex;
	sxu32 nType;
};
static SyMutex *UnixMutexNew(int nType) {
	static SyMutex aStaticMutexes[] = {
		{PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_1},
		{PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_2},
		{PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_3},
		{PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_4},
		{PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_5},
		{PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_6}
	};
	SyMutex *pMutex;
	if(nType == SXMUTEX_TYPE_FAST || nType == SXMUTEX_TYPE_RECURSIVE) {
		pthread_mutexattr_t sRecursiveAttr;
		/* Allocate a new mutex */
		pMutex = (SyMutex *)malloc(sizeof(SyMutex));
		if(pMutex == 0) {
			return 0;
		}
		if(nType == SXMUTEX_TYPE_RECURSIVE) {
			pthread_mutexattr_init(&sRecursiveAttr);
			pthread_mutexattr_settype(&sRecursiveAttr, PTHREAD_MUTEX_RECURSIVE);
		}
		pthread_mutex_init(&pMutex->sMutex, nType == SXMUTEX_TYPE_RECURSIVE ? &sRecursiveAttr : 0);
		if(nType == SXMUTEX_TYPE_RECURSIVE) {
			pthread_mutexattr_destroy(&sRecursiveAttr);
		}
	} else {
		/* Use a pre-allocated static mutex */
		if(nType > SXMUTEX_TYPE_STATIC_6) {
			nType = SXMUTEX_TYPE_STATIC_6;
		}
		pMutex = &aStaticMutexes[nType - 3];
	}
	pMutex->nType = nType;
	return pMutex;
}
static void UnixMutexRelease(SyMutex *pMutex) {
	if(pMutex->nType == SXMUTEX_TYPE_FAST || pMutex->nType == SXMUTEX_TYPE_RECURSIVE) {
		pthread_mutex_destroy(&pMutex->sMutex);
		free(pMutex);
	}
}
static void UnixMutexEnter(SyMutex *pMutex) {
	pthread_mutex_lock(&pMutex->sMutex);
}
static void UnixMutexLeave(SyMutex *pMutex) {
	pthread_mutex_unlock(&pMutex->sMutex);
}
/* Export pthread mutex interfaces */
static const SyMutexMethods sPthreadMutexMethods = {
	0, /* xGlobalInit() */
	0, /* xGlobalRelease() */
	UnixMutexNew,      /* xNew() */
	UnixMutexRelease,  /* xRelease() */
	UnixMutexEnter,    /* xEnter() */
	0,                 /* xTryEnter() */
	UnixMutexLeave     /* xLeave() */
};
PH7_PRIVATE const SyMutexMethods *SyMutexExportMethods(void) {
	return &sPthreadMutexMethods;
}
#else
/* Host application must register their own mutex subsystem if the target
 * platform is not an UNIX-like or windows systems.
 */
struct SyMutex {
	sxu32 nType;
};
static SyMutex *DummyMutexNew(int nType) {
	static SyMutex sMutex;
	SXUNUSED(nType);
	return &sMutex;
}
static void DummyMutexRelease(SyMutex *pMutex) {
	SXUNUSED(pMutex);
}
static void DummyMutexEnter(SyMutex *pMutex) {
	SXUNUSED(pMutex);
}
static void DummyMutexLeave(SyMutex *pMutex) {
	SXUNUSED(pMutex);
}
/* Export the dummy mutex interfaces */
static const SyMutexMethods sDummyMutexMethods = {
	0, /* xGlobalInit() */
	0, /* xGlobalRelease() */
	DummyMutexNew,      /* xNew() */
	DummyMutexRelease,  /* xRelease() */
	DummyMutexEnter,    /* xEnter() */
	0,                  /* xTryEnter() */
	DummyMutexLeave     /* xLeave() */
};
PH7_PRIVATE const SyMutexMethods *SyMutexExportMethods(void) {
	return &sDummyMutexMethods;
}
#endif /* __WINNT__ */
#endif /* PH7_ENABLE_THREADS */