/*==================================================================================================
	CAGuard.cpp

	$Log: CAGuard.cpp,v $
	Revision 1.16  2004/11/08 21:15:50  luke
	add #include <errno.h> per Doug's request
	
	Revision 1.15  2004/08/26 08:13:33  jcm10
	finish bring up on Windows
	
	Revision 1.14  2004/01/07 22:30:19  dwyatt
	fix file name in header
	
	Revision 1.13  2003/12/17 21:00:16  dwyatt
	derive from CAMutex
	
	Revision 1.12  2003/12/17 19:49:15  dwyatt
	Windows conditionalizing
	
	Revision 1.11  2003/12/17 18:58:08  dwyatt
	renamed from CAGuard.cp
	
	Revision 1.10  2003/09/12 01:18:11  jcm10
	put in new code for Try(), but have it ifdef'd out for now
	
	Revision 1.9  2003/08/05 01:11:15  jcm10
	use pthread_equal
	
	Revision 1.8  2003/04/10 00:01:53  jcm10
	turn off logging that might happen on the IO thread
	
	Revision 1.7  2002/07/07 00:46:59  jcm10
	comment out the warning about the try-based Locker
	
	Revision 1.6  2002/06/13 06:31:47  dwyatt
	fix compile error :-)
	
	Revision 1.5  2002/06/13 04:28:55  bills
	fix compile error
	
	Revision 1.4  2002/04/09 07:57:55  bills
	fix try [jcm10]
	
	Revision 1.3  2002/04/05 23:10:43  jcm10
	move the warning to the .cp file
	
	Revision 1.2  2002/04/02 20:42:07  bills
	add Try
	
	Revision 1.1  2002/03/01 01:52:40  jcm10
	moved here from ../Utility
	
	Revision 1.17  2002/02/28 23:24:29  jcm10
	added the CA prefix to DebugMacros and LogMacros for more consistency
	
	Revision 1.16  2001/11/15 02:07:24  jcm10
	remove extraneous include of <cstdio>
	
	Revision 1.15  2001/07/04 02:00:26  jcm10
	give CAGuard a name for easier debugging
	
	Revision 1.14  2001/01/19 00:48:24  jcm10
	add more logging
	
	Revision 1.13  2001/01/18 02:50:51  jcm10
	use CoreAudio_Debug instead of DEBUG
	
	Revision 1.12  2000/12/10 22:20:08  jcm10
	more fixes for new driver model
	
	Revision 1.11  2000/12/07 02:18:40  jcm10
	turn off the logging
	
	Revision 1.10  2000/11/21 20:45:50  jcm10
	more XFiles fun
	
	Revision 1.9  2000/11/21 19:48:16  jcm10
	turn off the logging
	
	Revision 1.8  2000/10/12 00:18:11  jcm10
	use CAHostTimeBase::GetCurrentTimeInNanos()
	
	Revision 1.7  2000/10/04 19:58:55  jcm10
	Add conversion routines to CAHostTimeBase
	
	Revision 1.6  2000/10/02 18:34:29  jcm10
	XFiles work better
	
	Revision 1.5  2000/09/25 23:15:31  jcm10
	XFiles now work (almost)
	
	Revision 1.4  2000/09/24 01:12:32  jcm10
	first checked in
	
	Revision 1.3  2000/09/13 02:28:49  jcm10
	make it build again
	
	Revision 1.2  2000/08/25 01:07:26  jcm10
	update things to build the XFiles and prune the CoreAudio target to just the code necessary to run it
	
	Revision 1.1  2000/08/24 23:36:46  jcm10
	First checked in
	
	Revision 0.0  2000/01/01 12:34:56  jcm10
	created
		
	$NoKeywords: $
==================================================================================================*/

//==================================================================================================
//	Includes
//==================================================================================================

//	Self Include
#include "CAGuard.h"

#if TARGET_OS_MAC
	#include <errno.h>
#endif

//	PublicUtility Inludes
#include "CADebugMacros.h"
#include "CAException.h"
#include "CAHostTimeBase.h"

//==================================================================================================
//	Logging
//==================================================================================================

#if CoreAudio_Debug
//	#define	Log_Ownership		1
//	#define	Log_WaitOwnership	1
//	#define	Log_TimedWaits		1
//	#define Log_Latency			1
//	#define	Log_Errors			1
#endif

//#warning		Need a try-based Locker too
//==================================================================================================
//	CAGuard
//==================================================================================================

CAGuard::CAGuard(const char* inName)
:
	CAMutex(inName)
#if	Log_Average_Latency
	,mAverageLatencyAccumulator(0.0),
	mAverageLatencyCount(0)
#endif
{
#if TARGET_OS_MAC
	OSStatus theError = pthread_cond_init(&mCondVar, NULL);
	ThrowIf(theError != 0, CAException(theError), "CAGuard::CAGuard: Could not init the cond var");
#elif TARGET_OS_WIN32
	mEvent = CreateEvent(NULL, true, false, NULL);
	ThrowIfNULL(mEvent, CAException(GetLastError()), "CAGuard::CAGuard: Could not create the event");
#endif
}

CAGuard::~CAGuard()
{
#if TARGET_OS_MAC
	pthread_cond_destroy(&mCondVar);
#elif TARGET_OS_WIN32
	if(mEvent != NULL)
	{
		CloseHandle(mEvent);
	}
#endif
}

void	CAGuard::Wait()
{
#if TARGET_OS_MAC
	ThrowIf(!pthread_equal(pthread_self(), mOwner), CAException(1), "CAGuard::Wait: A thread has to have locked a guard before it can wait");

	mOwner = 0;

	#if	Log_WaitOwnership
		DebugPrintfRtn(DebugPrintfFile, "%p %.4f: CAGuard::Wait: thread %p is waiting on %s, owner: %p\n", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);
	#endif

	OSStatus theError = pthread_cond_wait(&mCondVar, &mMutex);
	ThrowIf(theError != 0, CAException(theError), "CAGuard::Wait: Could not wait for a signal");
	mOwner = pthread_self();

	#if	Log_WaitOwnership
		DebugPrintfRtn(DebugPrintfFile, "%p %.4f: CAGuard::Wait: thread %p waited on %s, owner: %p\n", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);
	#endif
#elif TARGET_OS_WIN32
	ThrowIf(GetCurrentThreadId() != mOwner, CAException(1), "CAGuard::Wait: A thread has to have locked a guard before it can wait");

	mOwner = 0;

	#if	Log_WaitOwnership
		DebugPrintfRtn(DebugPrintfFile, "%lu %.4f: CAGuard::Wait: thread %lu is waiting on %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);
	#endif

	ReleaseMutex(mMutex);
	HANDLE theHandles[] = { mMutex, mEvent };
	OSStatus theError = WaitForMultipleObjects(2, theHandles, true, INFINITE);
	ThrowIfError(theError, CAException(GetLastError()), "CAGuard::Wait: Could not wait for the signal");
	mOwner = GetCurrentThreadId();

	#if	Log_WaitOwnership
		DebugPrintfRtn(DebugPrintfFile, "%lu %.4f: CAGuard::Wait: thread %lu waited on %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);
	#endif
#endif
}

bool	CAGuard::WaitFor(UInt64 inNanos)
{
	bool theAnswer = false;

#if TARGET_OS_MAC
	ThrowIf(!pthread_equal(pthread_self(), mOwner), CAException(1), "CAGuard::WaitFor: A thread has to have locked a guard be for it can wait");

	#if	Log_TimedWaits
		DebugMessageN1("CAGuard::WaitFor: waiting %.0f", (Float64)inNanos);
	#endif

	struct timespec	theTimeSpec;
	static const UInt64	kNanosPerSecond = 1000000000ULL;
	if(inNanos > kNanosPerSecond)
	{
		theTimeSpec.tv_sec = inNanos / kNanosPerSecond;
		theTimeSpec.tv_nsec = inNanos % kNanosPerSecond;
	}
	else
	{
		theTimeSpec.tv_sec = 0;
		theTimeSpec.tv_nsec = inNanos;
	}
	
	#if	Log_TimedWaits || Log_Latency || Log_Average_Latency
		UInt64	theStartNanos = CAHostTimeBase::GetCurrentTimeInNanos();
	#endif

	mOwner = 0;

	#if	Log_WaitOwnership
		DebugPrintfRtn(DebugPrintfFile, "%p %.4f: CAGuard::WaitFor: thread %p is waiting on %s, owner: %p\n", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);
	#endif

	OSStatus theError = pthread_cond_timedwait_relative_np(&mCondVar, &mMutex, &theTimeSpec);
	ThrowIf((theError != 0) && (theError != ETIMEDOUT), CAException(theError), "CAGuard::WaitFor: Wait got an error");
	mOwner = pthread_self();
	
	#if	Log_TimedWaits || Log_Latency || Log_Average_Latency
		UInt64	theEndNanos = CAHostTimeBase::GetCurrentTimeInNanos();
	#endif
	
	#if	Log_TimedWaits
		DebugMessageN1("CAGuard::WaitFor: waited  %.0f", (Float64)(theEndNanos - theStartNanos));
	#endif
	
	#if	Log_Latency
		DebugMessageN1("CAGuard::WaitFor: latency  %.0f", (Float64)((theEndNanos - theStartNanos) - inNanos));
	#endif
	
	#if	Log_Average_Latency
		++mAverageLatencyCount;
		mAverageLatencyAccumulator += (theEndNanos - theStartNanos) - inNanos;
		if(mAverageLatencyCount >= 50)
		{
			DebugMessageN2("CAGuard::WaitFor: average latency  %.3f ns over %ld waits", mAverageLatencyAccumulator / mAverageLatencyCount, mAverageLatencyCount);
			mAverageLatencyCount = 0;
			mAverageLatencyAccumulator = 0.0;
		}
	#endif

	#if	Log_WaitOwnership
		DebugPrintfRtn(DebugPrintfFile, "%p %.4f: CAGuard::WaitFor: thread %p waited on %s, owner: %p\n", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);
	#endif

	theAnswer = theError == ETIMEDOUT;
#elif TARGET_OS_WIN32
	ThrowIf(GetCurrentThreadId() != mOwner, CAException(1), "CAGuard::WaitFor: A thread has to have locked a guard be for it can wait");

	#if	Log_TimedWaits
		DebugMessageN1("CAGuard::WaitFor: waiting %.0f", (Float64)inNanos);
	#endif

	//	the time out is specified in milliseconds(!)
	UInt32 theWaitTime = static_cast<UInt32>(inNanos / 1000000ULL);

	#if	Log_TimedWaits || Log_Latency || Log_Average_Latency
		UInt64	theStartNanos = CAHostTimeBase::GetCurrentTimeInNanos();
	#endif

	mOwner = 0;

	#if	Log_WaitOwnership
		DebugPrintfRtn(DebugPrintfFile, "%lu %.4f: CAGuard::WaitFor: thread %lu is waiting on %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);
	#endif
	
	ReleaseMutex(mMutex);
	HANDLE theHandles[] = { mMutex, mEvent };
	OSStatus theError = WaitForMultipleObjects(2, theHandles, true, theWaitTime);
	ThrowIf((theError != WAIT_OBJECT_0) && (theError != WAIT_TIMEOUT), CAException(GetLastError()), "CAGuard::WaitFor: Wait got an error");
	mOwner = GetCurrentThreadId();
	
	#if	Log_TimedWaits || Log_Latency || Log_Average_Latency
		UInt64	theEndNanos = CAHostTimeBase::GetCurrentTimeInNanos();
	#endif
	
	#if	Log_TimedWaits
		DebugMessageN1("CAGuard::WaitFor: waited  %.0f", (Float64)(theEndNanos - theStartNanos));
	#endif
	
	#if	Log_Latency
		DebugMessageN1("CAGuard::WaitFor: latency  %.0f", (Float64)((theEndNanos - theStartNanos) - inNanos));
	#endif
	
	#if	Log_Average_Latency
		++mAverageLatencyCount;
		mAverageLatencyAccumulator += (theEndNanos - theStartNanos) - inNanos;
		if(mAverageLatencyCount >= 50)
		{
			DebugMessageN2("CAGuard::WaitFor: average latency  %.3f ns over %ld waits", mAverageLatencyAccumulator / mAverageLatencyCount, mAverageLatencyCount);
			mAverageLatencyCount = 0;
			mAverageLatencyAccumulator = 0.0;
		}
	#endif

	#if	Log_WaitOwnership
		DebugPrintfRtn(DebugPrintfFile, "%lu %.4f: CAGuard::WaitFor: thread %lu waited on %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);
	#endif

	theAnswer = theError == WAIT_TIMEOUT;
#endif

	return theAnswer;
}

bool	CAGuard::WaitUntil(UInt64 inNanos)
{
	bool	theAnswer = false;
	UInt64	theCurrentNanos = CAHostTimeBase::GetCurrentTimeInNanos();
	
#if	Log_TimedWaits
	DebugMessageN2("CAGuard::WaitUntil: now: %.0f, requested: %.0f", (double)theCurrentNanos, (double)inNanos);
#endif
	
	if(inNanos > theCurrentNanos)
	{
#if Log_Errors
		if((inNanos - theCurrentNanos) > 1000000000ULL)
		{
			DebugMessage("CAGuard::WaitUntil: about to wait for more than a second");
		}
#endif
		theAnswer = WaitFor(inNanos - theCurrentNanos);
	}
#if	Log_Errors
	else
	{
		DebugMessageN2("CAGuard::WaitUntil: Time has expired before waiting, now: %.0f, requested: %.0f", (double)theCurrentNanos, (double)inNanos);
	}
#endif

	return theAnswer;
}

void	CAGuard::Notify()
{
#if TARGET_OS_MAC
	#if	Log_WaitOwnership
		DebugPrintfRtn(DebugPrintfFile, "%p %.4f: CAGuard::Notify: thread %p is notifying %s, owner: %p\n", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);
	#endif

	OSStatus theError = pthread_cond_signal(&mCondVar);
	ThrowIf(theError != 0, CAException(theError), "CAGuard::Notify: failed");
#elif TARGET_OS_WIN32
	#if	Log_WaitOwnership
		DebugPrintfRtn(DebugPrintfFile, "%lu %.4f: CAGuard::Notify: thread %lu is notifying %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);
	#endif
	
	PulseEvent(mEvent);
#endif
}

void	CAGuard::NotifyAll()
{
#if TARGET_OS_MAC
	#if	Log_WaitOwnership
		DebugPrintfRtn(DebugPrintfFile, "%p %.4f: CAGuard::NotifyAll: thread %p is notifying %s, owner: %p\n", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);
	#endif

	OSStatus theError = pthread_cond_broadcast(&mCondVar);
	ThrowIf(theError != 0, CAException(theError), "CAGuard::NotifyAll: failed");
#elif TARGET_OS_WIN32
	#if	Log_WaitOwnership
		DebugPrintfRtn(DebugPrintfFile, "%lu %.4f: CAGuard::NotifyAll: thread %lu is notifying %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);
	#endif
	
	PulseEvent(mEvent);
#endif
}
