/*
 * Copyright (C) 2007-2023 Xagasoft, All rights reserved.
 *
 * This file is part of the libbu++ library and is released under the
 * terms of the license contained in the file LICENSE.
 */

#ifndef BU_EVENT_H
#define BU_EVENT_H

#include <pthread.h>

#include "bu/mutex.h"
#include "bu/condition.h"

namespace Bu
{
    /**
     * Represents a true/false state that controls thread synchronization. This
     * is primarilly intended to control the synchronization state of
     * multithreaded services. For example, telling all threads when to exit.
     *
     * An Event is either set or unset. If the Event is unset then it can be
     * waited on for something to happen. As soon as the Event is set all
     * waiting threads are released and new requests to wait are ignored until
     * the Event is cleared again.
     *
     * Threads can also be woken up without setting the Event, which may be
     * handy in certain circumstances.
     *@ingroup Threading
     */
    class Event
    {
    public:
        Event() :
            bIsSet( false )
        {
        }
        
        ~Event()
        {
        }

        /**
         * Wait indefinitely for the Event to trigger. If the event is already
         * set, then return immediately.  It's important to note that this may
         * return at any time, not only when the Event is set, so examining the
         * return value is important.
         *@returns the set status of the Event.
         */
        bool wait()
        {
            cBlock.lock();
            if( bIsSet )
            {
                cBlock.unlock();
                return true;
            }
            cBlock.wait();
            bool bRet = bIsSet;
            cBlock.unlock();
            return bRet;
        }

        /**
         * Wait for up to nSec seconds and nUSec nanoseconds for the event to
         * trigger. If the Event is already set then return immediately.
         *@returns the set status of the Event.
         */
        bool wait( int nSec, int nUSec )
        {
            cBlock.lock();
            if( bIsSet )
            {
                cBlock.unlock();
                return true;
            }
            cBlock.wait( nSec, nUSec );
            bool bRet = bIsSet;
            cBlock.unlock();
            return bRet;
        }

        /**
         * Allow one of the waiting threads to unlock without updating the set
         * state of the Event.
         */
        void unblockOne()
        {
            cBlock.lock();
            cBlock.signal();
            cBlock.unlock();
        }

        /**
         * Allow all waiting threads to unlock and proceed without updating the
         * set state of the Event.
         */
        void unblockAll()
        {
            cBlock.lock();
            cBlock.broadcast();
            cBlock.unlock();
        }

        /**
         * Find out if the Event is in the set state or not.
         *@returns True if set, false otherwise.
         */
        bool isSet()
        {
            cBlock.lock();
            bool bRet = bIsSet;
            cBlock.unlock();
            return bRet;
        }

        /**
         * Sets the Event's state to true and triggers all waiting threads.
         */
        void set()
        {
            cBlock.lock();
            bIsSet = true;
            cBlock.broadcast();
            cBlock.unlock();
        }
        
        /**
         * Sets the Event's state to false. This does NOT trigger any waiting
         * threads.
         */
        void clear()
        {
            cBlock.lock();
            bIsSet = false;
            cBlock.unlock();
        }

    private:
        Condition cBlock;   /**< The condition for blocking dequeues. */
        bool bIsSet;
    };
}

#endif
