/*****************************************************************************/
// Copyright 2006-2019 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.
/*****************************************************************************/

#include "dng_abort_sniffer.h"
#include "dng_assertions.h"

#include "dng_mutex.h"

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

#if qDNGThreadSafe

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

// TO DO: This priority-based wait mechanism is not compatible with thread
// pools. Putting worker threads to sleep may result in deadlock because
// higher priority work may not make progress (the pool may not be able to
// spin up any new threads).

class dng_priority_manager
	{

	private:

		// Use lower-level mutex and condition_variable for priority manager
		// since we don't want to include these in our priority tracking.

		dng_mutex fMutex;

		dng_condition fCondition;

		uint32 fCounter [dng_priority_count];

	public:

		dng_priority_manager ();

		void Increment (dng_priority priority,
						const char *name);

		void Decrement (dng_priority priority,
						const char *name);

		void Wait (dng_abort_sniffer *sniffer);

	private:

		dng_priority MinPriority ()
			{

			// Assumes mutex is locked.

			for (uint32 level = dng_priority_maximum;
				 level > dng_priority_minimum;
				 level--)
				{

				if (fCounter [level])
					{
					return (dng_priority) level;
					}

				}

			return dng_priority_minimum;

			}

	};

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

dng_priority_manager::dng_priority_manager ()

	:	fMutex	   ("dng_priority_manager::fMutex")
	,	fCondition ()

	{

	for (uint32 level = dng_priority_minimum;
		 level <= dng_priority_maximum;
		 level++)
		{

		fCounter [level] = 0;

		}

	}

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

void dng_priority_manager::Increment (dng_priority priority,
									  const char *name)
	{

	dng_lock_mutex lock (&fMutex);

	fCounter [priority] += 1;

	#if 0

	printf ("increment priority %d (%s) (%d, %d, %d)\n",
			(int) priority,
			name,
			fCounter [dng_priority_low],
			fCounter [dng_priority_medium],
			fCounter [dng_priority_high]);

	#else

	(void) name;

	#endif

	}

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

void dng_priority_manager::Decrement (dng_priority priority,
									  const char *name)
	{

	dng_priority oldMin = dng_priority_minimum;
	dng_priority newMin = dng_priority_minimum;

		{

		dng_lock_mutex lock (&fMutex);

		oldMin = MinPriority ();

		fCounter [priority] -= 1;

		newMin = MinPriority ();

		#if 0

		printf ("decrement priority %d (%s) (%d, %d, %d)\n",
				(int) priority,
				name,
				fCounter [dng_priority_low],
				fCounter [dng_priority_medium],
				fCounter [dng_priority_high]);

		#else

		(void) name;

		#endif

		}

	if (newMin < oldMin)
		{

		fCondition.Broadcast ();

		}

	}

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

void dng_priority_manager::Wait (dng_abort_sniffer *sniffer)
	{

	if (!sniffer)
		{
		return;
		}

	const dng_priority priority = sniffer->Priority ();

	if (priority < dng_priority_maximum)
		{

		dng_lock_mutex lock (&fMutex);

		while (priority < MinPriority ())
			{

			fCondition.Wait (fMutex);

			}

		}

	}

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

static dng_priority_manager gPriorityManager;

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

#endif	// qDNGThreadSafe

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

dng_set_minimum_priority::dng_set_minimum_priority (dng_priority priority,
													const char *name)

	:	fPriority (priority)

	{

	#if qDNGThreadSafe

	gPriorityManager.Increment (fPriority, name);

	#endif

	fName.Set (name);

	}

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

dng_set_minimum_priority::~dng_set_minimum_priority ()
	{

	#if qDNGThreadSafe

	gPriorityManager.Decrement (fPriority, fName.Get ());

	#endif

	}

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

dng_abort_sniffer::dng_abort_sniffer ()

	:	fPriority (dng_priority_maximum)

	{

	}

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

dng_abort_sniffer::~dng_abort_sniffer ()
	{

	}

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

void dng_abort_sniffer::SetPriority (dng_priority priority)
	{

	fPriority = priority;

	}

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

bool dng_abort_sniffer::SupportsPriorityWait () const
	{
	return false;
	}

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

void dng_abort_sniffer::SniffForAbort (dng_abort_sniffer *sniffer)
	{

	if (sniffer)
		{

		#if qDNGThreadSafe

		if (sniffer->SupportsPriorityWait ())
			{

			gPriorityManager.Wait (sniffer);

			}

		#endif

		sniffer->Sniff ();

		}

	}

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

void dng_abort_sniffer::StartTask (const char * /* name */,
								   real64 /* fract */)
	{

	}

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

void dng_abort_sniffer::EndTask ()
	{

	}

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

void dng_abort_sniffer::UpdateProgress (real64 /* fract */)
	{

	}

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