//===-- PThreadEvent.cpp ----------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
//  Created by Greg Clayton on 6/16/07.
//
//===----------------------------------------------------------------------===//

#include "PThreadEvent.h"
#include "errno.h"
#include "DNBLog.h"

PThreadEvent::PThreadEvent(uint32_t bits, uint32_t validBits) :
    m_mutex(),
    m_set_condition(),
    m_reset_condition(),
    m_bits(bits),
    m_validBits(validBits),
    m_reset_ack_mask(0)
{
    // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x, 0x%8.8x)", this, __FUNCTION__, bits, validBits);
}

PThreadEvent::~PThreadEvent()
{
    // DNBLogThreadedIf(LOG_EVENTS, "%p %s", this, __PRETTY_FUNCTION__);
}


uint32_t
PThreadEvent::NewEventBit()
{
    // DNBLogThreadedIf(LOG_EVENTS, "%p %s", this, __PRETTY_FUNCTION__);
    PTHREAD_MUTEX_LOCKER (locker, m_mutex);
    uint32_t mask = 1;
    while (mask & m_validBits)
        mask <<= 1;
    m_validBits |= mask;
    return mask;
}

void
PThreadEvent::FreeEventBits(const uint32_t mask)
{
    // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x)", this, __FUNCTION__, mask);
    if (mask)
    {
        PTHREAD_MUTEX_LOCKER (locker, m_mutex);
        m_bits &= ~mask;
        m_validBits &= ~mask;
    }
}


uint32_t
PThreadEvent::GetEventBits() const
{
    // DNBLogThreadedIf(LOG_EVENTS, "%p %s", this, __PRETTY_FUNCTION__);
    PTHREAD_MUTEX_LOCKER (locker, m_mutex);
    uint32_t bits = m_bits;
    return bits;
}

// Replace the event bits with a new bitmask value
void
PThreadEvent::ReplaceEventBits(const uint32_t bits)
{
    // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x)", this, __FUNCTION__, bits);
    PTHREAD_MUTEX_LOCKER (locker, m_mutex);
    // Make sure we have some bits and that they aren't already set...
    if (m_bits != bits)
    {
        // Figure out which bits are changing
        uint32_t changed_bits = m_bits ^ bits;
        // Set the new bit values
        m_bits = bits;
        // If any new bits are set, then broadcast
        if (changed_bits & m_bits)
            m_set_condition.Broadcast();
    }
}

// Set one or more event bits and broadcast if any new event bits get set
// that weren't already set.

void
PThreadEvent::SetEvents(const uint32_t mask)
{
    // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x)", this, __FUNCTION__, mask);
    // Make sure we have some bits to set
    if (mask)
    {
        PTHREAD_MUTEX_LOCKER (locker, m_mutex);
        // Save the old event bit state so we can tell if things change
        uint32_t old = m_bits;
        // Set the all event bits that are set in 'mask'
        m_bits |= mask;
        // Broadcast only if any extra bits got set.
        if (old != m_bits)
            m_set_condition.Broadcast();
    }
}

// Reset one or more event bits
void
PThreadEvent::ResetEvents(const uint32_t mask)
{
    // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x)", this, __FUNCTION__, mask);
    if (mask)
    {
        PTHREAD_MUTEX_LOCKER (locker, m_mutex);

        // Save the old event bit state so we can tell if things change
        uint32_t old = m_bits;
        // Clear the all event bits that are set in 'mask'
        m_bits &= ~mask;
        // Broadcast only if any extra bits got reset.
        if (old != m_bits)
            m_reset_condition.Broadcast();
    }
}

//----------------------------------------------------------------------
// Wait until 'timeout_abstime' for any events that are set in
// 'mask'. If 'timeout_abstime' is NULL, then wait forever.
//----------------------------------------------------------------------
uint32_t
PThreadEvent::WaitForSetEvents(const uint32_t mask, const struct timespec *timeout_abstime) const
{
    // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x, %p)", this, __FUNCTION__, mask, timeout_abstime);
    int err = 0;
    // pthread_cond_timedwait() or pthread_cond_wait() will atomically
    // unlock the mutex and wait for the condition to be set. When either
    // function returns, they will re-lock the mutex. We use an auto lock/unlock
    // class (PThreadMutex::Locker) to allow us to return at any point in this
    // function and not have to worry about unlocking the mutex.
    PTHREAD_MUTEX_LOCKER (locker, m_mutex);
    do
    {
        // Check our predicate (event bits) in case any are already set
        if (mask & m_bits)
        {
            uint32_t bits_set = mask & m_bits;
            // Our PThreadMutex::Locker will automatically unlock our mutex
            return bits_set;
        }
        if (timeout_abstime)
        {
            // Wait for condition to get broadcast, or for a timeout. If we get
            // a timeout we will drop out of the do loop and return false which
            // is what we want.
            err = ::pthread_cond_timedwait (m_set_condition.Condition(), m_mutex.Mutex(), timeout_abstime);
            // Retest our predicate in case of a race condition right at the end
            // of the timeout.
            if (err == ETIMEDOUT)
            {
                uint32_t bits_set = mask & m_bits;
                return bits_set;
            }
        }
        else
        {
            // Wait for condition to get broadcast. The only error this function
            // should return is if
            err = ::pthread_cond_wait (m_set_condition.Condition(), m_mutex.Mutex());
        }
    } while (err == 0);
    return 0;
}

//----------------------------------------------------------------------
// Wait until 'timeout_abstime' for any events in 'mask' to reset.
// If 'timeout_abstime' is NULL, then wait forever.
//----------------------------------------------------------------------
uint32_t
PThreadEvent::WaitForEventsToReset(const uint32_t mask, const struct timespec *timeout_abstime) const
{
    // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x, %p)", this, __FUNCTION__, mask, timeout_abstime);
    int err = 0;
    // pthread_cond_timedwait() or pthread_cond_wait() will atomically
    // unlock the mutex and wait for the condition to be set. When either
    // function returns, they will re-lock the mutex. We use an auto lock/unlock
    // class (PThreadMutex::Locker) to allow us to return at any point in this
    // function and not have to worry about unlocking the mutex.
    PTHREAD_MUTEX_LOCKER (locker, m_mutex);
    do
    {
        // Check our predicate (event bits) each time through this do loop
        if ((mask & m_bits) == 0)
        {
            // All the bits requested have been reset, return zero indicating
            // which bits from the mask were still set (none of them)
            return 0;
        }
        if (timeout_abstime)
        {
            // Wait for condition to get broadcast, or for a timeout. If we get
            // a timeout we will drop out of the do loop and return false which
            // is what we want.
            err = ::pthread_cond_timedwait (m_reset_condition.Condition(), m_mutex.Mutex(), timeout_abstime);
        }
        else
        {
            // Wait for condition to get broadcast. The only error this function
            // should return is if
            err = ::pthread_cond_wait (m_reset_condition.Condition(), m_mutex.Mutex());
        }
    } while (err == 0);
    // Return a mask indicating which bits (if any) were still set
    return mask & m_bits;
}

uint32_t
PThreadEvent::WaitForResetAck (const uint32_t mask, const struct timespec *timeout_abstime) const
{
    if (mask & m_reset_ack_mask)
    {
        // DNBLogThreadedIf(LOG_EVENTS, "%p PThreadEvent::%s (0x%8.8x, %p)", this, __FUNCTION__, mask, timeout_abstime);
        return WaitForEventsToReset (mask & m_reset_ack_mask, timeout_abstime);
    }
    return 0;
}
