//---------------------------------------------------------
// For conditions of distribution and use, see
// https://github.com/preshing/cpp11-on-multicore/blob/master/LICENSE
//---------------------------------------------------------

#ifndef __CPP11OM_AUTO_RESET_EVENT_H__
#define __CPP11OM_AUTO_RESET_EVENT_H__

#include <cassert>
#include <thread>
#include <dsn/utility/hpc_locks/sema.h>

//---------------------------------------------------------
// AutoResetEvent
//---------------------------------------------------------
class AutoResetEvent
{
private:
    // m_status == 1: Event object is signaled.
    // m_status == 0: Event object is reset and no threads are waiting.
    // m_status == -N: Event object is reset and N threads are waiting.
    std::atomic<int> m_status;
    DefaultSemaphoreType m_sema;

public:
    AutoResetEvent(int initialStatus = 0) : m_status(initialStatus)
    {
        assert(initialStatus >= 0 && initialStatus <= 1);
    }

    void signal()
    {
        int oldStatus = m_status.load(std::memory_order_relaxed);
        for (;;) // Increment m_status atomically via CAS loop.
        {
            assert(oldStatus <= 1);
            int newStatus = oldStatus < 1 ? oldStatus + 1 : 1;
            if (m_status.compare_exchange_weak(
                    oldStatus, newStatus, std::memory_order_release, std::memory_order_relaxed))
                break;
            // The compare-exchange failed, likely because another thread changed m_status.
            // oldStatus has been updated. Retry the CAS loop.
        }
        if (oldStatus < 0)
            m_sema.signal(); // Release one waiting thread.
    }

    void wait()
    {
        int oldStatus = m_status.fetch_sub(1, std::memory_order_acquire);
        assert(oldStatus <= 1);
        if (oldStatus < 1) {
            m_sema.wait();
        }
    }

    bool wait(int timeout_milliseconds)
    {
        int oldStatus = m_status.fetch_sub(1, std::memory_order_acquire);
        assert(oldStatus <= 1);
        if (oldStatus < 1) {
            return m_sema.wait(timeout_milliseconds);
        } else
            return true;
    }
};

#endif // __CPP11OM_AUTO_RESET_EVENT_H__
