/*
 *          Copyright Andrey Semashev 2007 - 2015.
 * Distributed under the Boost Software License, Version 1.0.
 *    (See accompanying file LICENSE_1_0.txt or copy at
 *          http://www.boost.org/LICENSE_1_0.txt)
 */
/*!
 * \file   event.cpp
 * \author Andrey Semashev
 * \date   24.07.2011
 *
 * \brief  This header is the Boost.Log library implementation, see the library documentation
 *         at http://www.boost.org/doc/libs/release/libs/log/doc/html/index.html.
 */

#include <boost/log/detail/config.hpp>

#ifndef BOOST_LOG_NO_THREADS

#include <boost/assert.hpp>
#include <boost/cstdint.hpp>
#include <boost/throw_exception.hpp>
#include <boost/system/error_code.hpp>
#include <boost/system/system_error.hpp>
#include <boost/log/detail/event.hpp>

#if defined(BOOST_LOG_EVENT_USE_POSIX_SEMAPHORE)

#if defined(__GNUC__) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4)
#define BOOST_LOG_EVENT_TRY_SET(ref) (__sync_lock_test_and_set(&ref, 1U) == 0U)
#define BOOST_LOG_EVENT_RESET(ref) __sync_lock_release(&ref)
#else
#error Boost.Log internal error: BOOST_LOG_EVENT_USE_POSIX_SEMAPHORE must only be defined when atomic ops are available
#endif
#include <errno.h>
#include <semaphore.h>

#elif defined(BOOST_LOG_EVENT_USE_WINAPI)

#include "windows_version.hpp"
#include <windows.h>
#include <boost/detail/interlocked.hpp>

#else

#include <boost/thread/locks.hpp>

#endif

#include <boost/log/detail/header.hpp>

namespace boost {

BOOST_LOG_OPEN_NAMESPACE

namespace aux {

#if defined(BOOST_LOG_EVENT_USE_POSIX_SEMAPHORE)

//! Default constructor
BOOST_LOG_API sem_based_event::sem_based_event() : m_state(0U)
{
    if (sem_init(&m_semaphore, 0, 0) != 0)
    {
        const int err = errno;
        BOOST_THROW_EXCEPTION(system::system_error(
            err, system::system_category(), "Failed to initialize semaphore"));
    }
}

//! Destructor
BOOST_LOG_API sem_based_event::~sem_based_event()
{
    BOOST_VERIFY(sem_destroy(&m_semaphore) == 0);
}

//! Waits for the object to become signalled
BOOST_LOG_API void sem_based_event::wait()
{
    while (true)
    {
        if (sem_wait(&m_semaphore) != 0)
        {
            const int err = errno;
            if (err != EINTR)
            {
                BOOST_THROW_EXCEPTION(system::system_error(
                    err, system::system_category(), "Failed to block on the semaphore"));
            }
        }
        else
            break;
    }
    BOOST_LOG_EVENT_RESET(m_state);
}

//! Sets the object to a signalled state
BOOST_LOG_API void sem_based_event::set_signalled()
{
    if (BOOST_LOG_EVENT_TRY_SET(m_state))
    {
        if (sem_post(&m_semaphore) != 0)
        {
            const int err = errno;
            BOOST_LOG_EVENT_RESET(m_state);
            BOOST_THROW_EXCEPTION(system::system_error(
                err, system::system_category(), "Failed to wake the blocked thread"));
        }
    }
}

#elif defined(BOOST_LOG_EVENT_USE_WINAPI)

//! Default constructor
BOOST_LOG_API winapi_based_event::winapi_based_event() :
    m_state(0),
    m_event(CreateEventA(NULL, false, false, NULL))
{
    if (!m_event)
    {
        const DWORD err = GetLastError();
        BOOST_THROW_EXCEPTION(system::system_error(
            err, system::system_category(), "Failed to create Windows event"));
    }
}

//! Destructor
BOOST_LOG_API winapi_based_event::~winapi_based_event()
{
    BOOST_VERIFY(CloseHandle(m_event) != 0);
}

//! Waits for the object to become signalled
BOOST_LOG_API void winapi_based_event::wait()
{
    // On Windows we assume that memory view is always actual (Intel x86 and x86_64 arch)
    if (const_cast< volatile boost::uint32_t& >(m_state) == 0)
    {
        if (WaitForSingleObject(m_event, INFINITE) != 0)
        {
            const DWORD err = GetLastError();
            BOOST_THROW_EXCEPTION(system::system_error(
                err, system::system_category(), "Failed to block on Windows event"));
        }
    }
    const_cast< volatile boost::uint32_t& >(m_state) = 0;
}

//! Sets the object to a signalled state
BOOST_LOG_API void winapi_based_event::set_signalled()
{
    if (BOOST_INTERLOCKED_COMPARE_EXCHANGE(reinterpret_cast< long* >(&m_state), 1, 0) == 0)
    {
        if (SetEvent(m_event) == 0)
        {
            const DWORD err = GetLastError();
            const_cast< volatile boost::uint32_t& >(m_state) = 0;
            BOOST_THROW_EXCEPTION(system::system_error(
                err, system::system_category(), "Failed to wake the blocked thread"));
        }
    }
}

#else

//! Default constructor
BOOST_LOG_API generic_event::generic_event() : m_state(false)
{
}

//! Destructor
BOOST_LOG_API generic_event::~generic_event()
{
}

//! Waits for the object to become signalled
BOOST_LOG_API void generic_event::wait()
{
    boost::unique_lock< boost::mutex > lock(m_mutex);
    while (!m_state)
    {
        m_cond.wait(lock);
    }
    m_state = false;
}

//! Sets the object to a signalled state
BOOST_LOG_API void generic_event::set_signalled()
{
    boost::lock_guard< boost::mutex > lock(m_mutex);
    if (!m_state)
    {
        m_state = true;
        m_cond.notify_one();
    }
}

#endif

} // namespace aux

BOOST_LOG_CLOSE_NAMESPACE // namespace log

} // namespace boost

#include <boost/log/detail/footer.hpp>

#endif // BOOST_LOG_NO_THREADS
