/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/

/* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_mutex.cpp#3 $ */ 
/* $DateTime: 2012/09/05 12:31:51 $ */
/* $Change: 847652 $ */
/* $Author: tknoll $ */

#include "dng_mutex.h"

#include "dng_assertions.h"
#include "dng_exceptions.h"

#include <stdlib.h>

/*****************************************************************************/

#if qDNGThreadSafe

namespace
	{

	class InnermostMutexHolder
		{
		
		private:

			pthread_key_t fInnermostMutexKey;

		public:

			InnermostMutexHolder ()
			
				:	fInnermostMutexKey ()
				
				{

				int result = pthread_key_create (&fInnermostMutexKey, NULL);

				DNG_ASSERT (result == 0, "pthread_key_create failed.");

				if (result != 0)
					ThrowProgramError ();

				}

			~InnermostMutexHolder ()
				{
				
				pthread_key_delete (fInnermostMutexKey);
				
				}

			void SetInnermostMutex (dng_mutex *mutex)
				{

				int result;

				result = pthread_setspecific (fInnermostMutexKey, (void *)mutex);

				DNG_ASSERT (result == 0, "pthread_setspecific failed.");

				#if 0		// Hard failure here was causing crash on quit.
				
				if (result != 0)
					ThrowProgramError ();
					
				#endif

				}

			dng_mutex *GetInnermostMutex ()
				{

				void *result = pthread_getspecific (fInnermostMutexKey);

				return reinterpret_cast<dng_mutex *> (result);

				}

		};

	InnermostMutexHolder gInnermostMutexHolder;
	
	}

#endif

/*****************************************************************************/

dng_mutex::dng_mutex (const char *mutexName, uint32 mutexLevel)

	#if qDNGThreadSafe
	
	:	fPthreadMutex		()
	,	fMutexLevel			(mutexLevel)
	,	fRecursiveLockCount (0)
	,	fPrevHeldMutex		(NULL)
	,	fMutexName			(mutexName)
	
	#endif
	
	{
	
	#if qDNGThreadSafe

	if (pthread_mutex_init (&fPthreadMutex, NULL) != 0)
		{
		ThrowMemoryFull ();
		}
	
	#endif
	
	}

/*****************************************************************************/

dng_mutex::~dng_mutex ()
	{
	
	#if qDNGThreadSafe

	pthread_mutex_destroy (&fPthreadMutex);

	#endif
	
	}

/*****************************************************************************/

void dng_mutex::Lock ()
	{
	
	#if qDNGThreadSafe

	dng_mutex *innermostMutex = gInnermostMutexHolder.GetInnermostMutex ();

	if (innermostMutex != NULL)
		{

		if (innermostMutex == this)
			{

			fRecursiveLockCount++;

			return;

			}

		bool lockOrderPreserved = fMutexLevel > innermostMutex->fMutexLevel /* ||
								  (fMutexLevel == innermostMutex->fMutexLevel && innermostMutex < this) */;

		if (!lockOrderPreserved)
			{
			
			DNG_REPORT ("Lock ordering violation.");
			
			#if qDNGDebug
			
			dng_show_message_f ("This mutex: %s v Innermost mutex: %s",
								this->MutexName (),
								innermostMutex->MutexName ());
			
			#endif

			}

		}

	pthread_mutex_lock (&fPthreadMutex);

	fPrevHeldMutex = innermostMutex;

	gInnermostMutexHolder.SetInnermostMutex (this);

	#endif
	
	}

/*****************************************************************************/

void dng_mutex::Unlock ()
	{
	
	#if qDNGThreadSafe
	
	DNG_ASSERT (gInnermostMutexHolder.GetInnermostMutex () == this, "Mutexes unlocked out of order!!!");

	if (fRecursiveLockCount > 0)
		{

		fRecursiveLockCount--;

		return;

		}

	gInnermostMutexHolder.SetInnermostMutex (fPrevHeldMutex);

	fPrevHeldMutex = NULL;

	pthread_mutex_unlock (&fPthreadMutex);
	
	#endif
	
	}

/*****************************************************************************/

const char *dng_mutex::MutexName () const
	{
	
	#if qDNGThreadSafe
	
	if (fMutexName)
		return fMutexName;
	
	#endif
	
	return "< unknown >";
	
	}

/*****************************************************************************/

dng_lock_mutex::dng_lock_mutex (dng_mutex *mutex)

	:	fMutex (mutex)
	
	{
	
	if (fMutex)
		fMutex->Lock ();
		
	}

/*****************************************************************************/

dng_lock_mutex::~dng_lock_mutex ()
	{
	
	if (fMutex)
		fMutex->Unlock ();
		
	}

/*****************************************************************************/

dng_unlock_mutex::dng_unlock_mutex (dng_mutex *mutex)

	:	fMutex (mutex)
	
	{
	
	if (fMutex)
		fMutex->Unlock ();
		
	}

/*****************************************************************************/

dng_unlock_mutex::~dng_unlock_mutex ()
	{
	
	if (fMutex)
		fMutex->Lock ();
		
	}

/*****************************************************************************/

#if qDNGThreadSafe

/*****************************************************************************/

dng_condition::dng_condition ()

	:	fPthreadCondition ()

	{

	int result;

	result = pthread_cond_init (&fPthreadCondition, NULL);

	DNG_ASSERT (result == 0, "pthread_cond_init failed.");

	if (result != 0)
		{
		ThrowProgramError ();
		}

	}

/*****************************************************************************/

dng_condition::~dng_condition ()
	{
	
	pthread_cond_destroy (&fPthreadCondition);
	
	}

/*****************************************************************************/

bool dng_condition::Wait (dng_mutex &mutex, double timeoutSecs)
	{

	bool timedOut = false;

	dng_mutex *innermostMutex = gInnermostMutexHolder.GetInnermostMutex ();

	DNG_ASSERT (innermostMutex == &mutex, "Attempt to wait on non-innermost mutex.");

	innermostMutex = mutex.fPrevHeldMutex;

	gInnermostMutexHolder.SetInnermostMutex (innermostMutex);

	mutex.fPrevHeldMutex = NULL;

	if (timeoutSecs < 0)
		{
		
		pthread_cond_wait (&fPthreadCondition, &mutex.fPthreadMutex);
		
		}
		
	else
		{
		
		struct timespec now;

		dng_pthread_now (&now);

		timeoutSecs += now.tv_sec;
		timeoutSecs += now.tv_nsec / 1000000000.0;

		now.tv_sec  = (long) timeoutSecs;
		now.tv_nsec = (long) ((timeoutSecs - now.tv_sec) * 1000000000);

		timedOut = (pthread_cond_timedwait (&fPthreadCondition, &mutex.fPthreadMutex, &now) == ETIMEDOUT);
		
		}

	mutex.fPrevHeldMutex = innermostMutex;

	gInnermostMutexHolder.SetInnermostMutex (&mutex);

	return !timedOut;

	}

/*****************************************************************************/

void dng_condition::Signal ()
	{

	int result;

	result = pthread_cond_signal (&fPthreadCondition);

	DNG_ASSERT (result == 0, "pthread_cond_signal failed.");

	if (result != 0)
		ThrowProgramError ();

	}

/*****************************************************************************/

void dng_condition::Broadcast ()
	{

	int result;

	result = pthread_cond_broadcast (&fPthreadCondition);

	DNG_ASSERT (result == 0, "pthread_cond_broadcast failed.");

	if (result != 0)
		ThrowProgramError ();

	}

/*****************************************************************************/

#endif // qDNGThreadSafe

/*****************************************************************************/