/*****************************************************************************/
// 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, %d, %d)\n",
            (int) priority,
            name,
            fCounter [dng_priority_background],
            fCounter [dng_priority_low],
            fCounter [dng_priority_medium],
            fCounter [dng_priority_high]
            fCounter [dng_priority_very_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 */)
    {

    }

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