/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
***************************************************************************/

#pragma once
#ifndef GLMSEMAPHORE_H
#define GLMSEMAPHORE_H

#if GLM_IS_PLATFORM_WINDOWS
	#define GLM_SEMAPHORE_USE_WINDOWS	1
	#define GLM_SEMAPHORE_USE_GCC		0
	#define GLM_SEMAPHORE_USE_DARWIN	0
#elif GLM_IS_PLATFORM_LINUX
	#define GLM_SEMAPHORE_USE_WINDOWS	0
	#define GLM_SEMAPHORE_USE_GCC		1
	#define GLM_SEMAPHORE_USE_DARWIN	0
#elif GLM_IS_PLATFORM_MACOSX
	#define GLM_SEMAPHORE_USE_WINDOWS	0
	#define GLM_SEMAPHORE_USE_GCC		0
	#define GLM_SEMAPHORE_USE_DARWIN	1
#endif

namespace glm
{
	struct semaphoredef_t
	{
	#if GLM_SEMAPHORE_USE_WINDOWS
		HANDLE _sem;
	#elif GLM_SEMAPHORE_USE_GCC
		sem_t _sem;
	#elif GLM_SEMAPHORE_USE_DARWIN
		semaphore_t _sem;
	#endif
	};

	bool GLM_CORE_API createSemaphore(semaphoredef_t& semaphore, uint32_t startCount)
	{
	#if GLM_SEMAPHORE_USE_WINDOWS
		semaphore._sem = CreateSemaphoreEx( NULL, static_cast<LONG>(startCount), LONG_MAX, NULL, 0, SEMAPHORE_ALL_ACCESS );
		const bool created  = ( semaphore._sem != NULL );

	#elif GLM_SEMAPHORE_USE_GCC
		const int pshared = 0; //NOTE: means semaphore is shared between threads
		const int returnCode = sem_init( &semaphore._sem, pshared, startCount );
		const bool created = ( returnCode == 0 );

	#elif GLM_SEMAPHORE_USE_DARWIN
		const kern_return_t returnCode = semaphore_create( mach_task_self(), &semaphore._sem, SYNC_POLICY_FIFO, startCount );
		const bool created = ( returnCode == err_none );

	#endif
		return created;
	}

	void GLM_CORE_API destroySemaphore(semaphoredef_t& semaphore)
	{
	#if GLM_SEMAPHORE_USE_WINDOWS
		const BOOL closed = CloseHandle( semaphore._sem );
		GLM_ASSERT( closed != 0 );

	#elif GLM_SEMAPHORE_USE_GCC
		const int returnCode = sem_destroy( &semaphore._sem );
		GLM_ASSERT( returnCode == 0 );

	#elif GLM_SEMAPHORE_USE_DARWIN
		const kern_return_t returnCode = semaphore_destroy( mach_task_self(), semaphore._sem );
		GLM_ASSERT( returnCode == err_none );

	#endif
	}

	void GLM_CORE_API waitOnSemaphore(semaphoredef_t& semaphore)
	{
	#if GLM_SEMAPHORE_USE_WINDOWS
		WaitForSingleObjectEx( semaphore._sem, INFINITE, FALSE );

	#elif GLM_SEMAPHORE_USE_GCC
		int returnCode = 0;
		do
		{
			// When debugging on iOS 6.0 with Xcode 4.5, breaking into the debugger can cause
			// sem_wait() to return a failure code with errno set to EINTR, leading to synchronization
			// bugs in the calling code, which typically doesn't check for EINTR.
			// Indeed, this is reported to happen on Linux too:
			// http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error
			// Let's assume the caller does not want to take any action as a result of EINTR,
			// and consume all such errors here.
			returnCode = sem_wait( &semaphore._sem );

		}
		while ( returnCode == -1 && errno == EINTR );
		GLM_ASSERT( returnCode == 0 );

	#elif GLM_SEMAPHORE_USE_DARWIN
		kern_return_t returnCode;
		do {
			returnCode = semaphore_wait( semaphore._sem );
		}
		while( returnCode == KERN_ABORTED );
		GLM_ASSERT( returnCode == KERN_SUCCESS );

	#endif
	}

	void signalSemaphore(semaphoredef_t& semaphore)
	{
	#if GLM_SEMAPHORE_USE_WINDOWS
		const BOOL released = ReleaseSemaphore( semaphore._sem, 1, NULL );
		GLM_ASSERT( released != 0 );

	#elif GLM_SEMAPHORE_USE_GCC
		const int returnCode = sem_post( &semaphore._sem );
		GLM_ASSERT( returnCode == 0 );

	#elif GLM_SEMAPHORE_USE_DARWIN
		const kern_return_t returnCode = semaphore_signal( semaphore._sem );
		GLM_ASSERT( returnCode == KERN_SUCCESS );

	#endif
	}
}
#endif // GLMSEMAPHORE_H
