/*
 *          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   scoped_attribute.hpp
 * \author Andrey Semashev
 * \date   13.05.2007
 *
 * The header contains definition of facilities to define scoped attributes.
 */

#ifndef BOOST_LOG_ATTRIBUTES_SCOPED_ATTRIBUTE_HPP_INCLUDED_
#define BOOST_LOG_ATTRIBUTES_SCOPED_ATTRIBUTE_HPP_INCLUDED_

#include <utility>
#include <boost/move/core.hpp>
#include <boost/move/utility.hpp>
#include <boost/utility/addressof.hpp>
#include <boost/log/detail/config.hpp>
#include <boost/log/core/core.hpp>
#include <boost/log/sources/basic_logger.hpp>
#include <boost/log/attributes/attribute.hpp>
#include <boost/log/attributes/attribute_set.hpp>
#include <boost/log/attributes/attribute_name.hpp>
#include <boost/log/attributes/constant.hpp>
#include <boost/log/utility/unused_variable.hpp>
#include <boost/log/utility/unique_identifier_name.hpp>
#include <boost/log/detail/header.hpp>

#ifdef BOOST_HAS_PRAGMA_ONCE
#pragma once
#endif

namespace boost {

BOOST_LOG_OPEN_NAMESPACE

namespace aux {

//! A base class for all scoped attribute guards
class attribute_scope_guard
{
};

} // namespace aux

//! Scoped attribute guard type
typedef aux::attribute_scope_guard const& scoped_attribute;

namespace aux {

//! A scoped logger attribute guard
template< typename LoggerT >
class scoped_logger_attribute :
    public attribute_scope_guard
{
    BOOST_COPYABLE_AND_MOVABLE_ALT(scoped_logger_attribute)

private:
    //! Logger type
    typedef LoggerT logger_type;

private:
    //! A reference to the logger
    logger_type* m_pLogger;
    //! An iterator to the added attribute
    attribute_set::iterator m_itAttribute;

public:
    //! Constructor
    scoped_logger_attribute(logger_type& l, attribute_name const& name, attribute const& attr) :
        m_pLogger(boost::addressof(l))
    {
        std::pair<
            attribute_set::iterator,
            bool
        > res = l.add_attribute(name, attr);
        if (res.second)
            m_itAttribute = res.first;
        else
            m_pLogger = 0; // if there already is a same-named attribute, don't register anything
    }
    //! Move constructor
    scoped_logger_attribute(BOOST_RV_REF(scoped_logger_attribute) that) :
        m_pLogger(that.m_pLogger),
        m_itAttribute(that.m_itAttribute)
    {
        that.m_pLogger = 0;
    }

    //! Destructor
    ~scoped_logger_attribute()
    {
        if (m_pLogger)
            m_pLogger->remove_attribute(m_itAttribute);
    }

#ifndef BOOST_LOG_BROKEN_REFERENCE_FROM_RVALUE_INIT
    BOOST_DELETED_FUNCTION(scoped_logger_attribute(scoped_logger_attribute const&))
#else // BOOST_LOG_BROKEN_REFERENCE_FROM_RVALUE_INIT
    scoped_logger_attribute(scoped_logger_attribute const& that) : m_pLogger(that.m_pLogger), m_itAttribute(that.m_itAttribute)
    {
        const_cast< scoped_logger_attribute& >(that).m_pLogger = 0;
    }
#endif // BOOST_LOG_BROKEN_REFERENCE_FROM_RVALUE_INIT

    BOOST_DELETED_FUNCTION(scoped_logger_attribute& operator= (scoped_logger_attribute const&))
};

} // namespace aux

//  Generator helper functions
/*!
 * Registers an attribute in the logger
 *
 * \param l Logger to register the attribute in
 * \param name Attribute name
 * \param attr The attribute. Must not be NULL.
 * \return An unspecified guard object which may be used to initialize a \c scoped_attribute variable.
 */
template< typename LoggerT >
BOOST_FORCEINLINE aux::scoped_logger_attribute< LoggerT > add_scoped_logger_attribute(LoggerT& l, attribute_name const& name, attribute const& attr)
{
#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
    return aux::scoped_logger_attribute< LoggerT >(l, name, attr);
#else
    aux::scoped_logger_attribute< LoggerT > guard(l, name, attr);
    return boost::move(guard);
#endif
}

#ifndef BOOST_LOG_DOXYGEN_PASS

#define BOOST_LOG_SCOPED_LOGGER_ATTR_INTERNAL(logger, attr_name, attr, sentry_var_name)\
    BOOST_LOG_UNUSED_VARIABLE(::boost::log::scoped_attribute, sentry_var_name,\
        = ::boost::log::add_scoped_logger_attribute(logger, attr_name, (attr)));

#endif // BOOST_LOG_DOXYGEN_PASS

//! The macro sets a scoped logger-wide attribute in a more compact way
#define BOOST_LOG_SCOPED_LOGGER_ATTR(logger, attr_name, attr)\
    BOOST_LOG_SCOPED_LOGGER_ATTR_INTERNAL(\
        logger,\
        attr_name,\
        attr,\
        BOOST_LOG_UNIQUE_IDENTIFIER_NAME(_boost_log_scoped_logger_attr_sentry_))

//! The macro sets a scoped logger-wide tag in a more compact way
#define BOOST_LOG_SCOPED_LOGGER_TAG(logger, attr_name, attr_value)\
    BOOST_LOG_SCOPED_LOGGER_ATTR(logger, attr_name, ::boost::log::attributes::make_constant(attr_value))

namespace aux {

//! A scoped thread-specific attribute guard
class scoped_thread_attribute :
    public attribute_scope_guard
{
    BOOST_COPYABLE_AND_MOVABLE_ALT(scoped_thread_attribute)

private:
    //! A pointer to the logging core
    core_ptr m_pCore;
    //! An iterator to the added attribute
    attribute_set::iterator m_itAttribute;

public:
    //! Constructor
    scoped_thread_attribute(attribute_name const& name, attribute const& attr) :
        m_pCore(core::get())
    {
        std::pair<
            attribute_set::iterator,
            bool
        > res = m_pCore->add_thread_attribute(name, attr);
        if (res.second)
            m_itAttribute = res.first;
        else
            m_pCore.reset(); // if there already is a same-named attribute, don't register anything
    }
    //! Move constructor
    scoped_thread_attribute(BOOST_RV_REF(scoped_thread_attribute) that) : m_itAttribute(that.m_itAttribute)
    {
        m_pCore.swap(that.m_pCore);
    }

    //! Destructor
    ~scoped_thread_attribute()
    {
        if (!!m_pCore)
            m_pCore->remove_thread_attribute(m_itAttribute);
    }

#ifndef BOOST_LOG_BROKEN_REFERENCE_FROM_RVALUE_INIT
    BOOST_DELETED_FUNCTION(scoped_thread_attribute(scoped_thread_attribute const&))
#else // BOOST_LOG_BROKEN_REFERENCE_FROM_RVALUE_INIT
    scoped_thread_attribute(scoped_thread_attribute const& that) : m_itAttribute(that.m_itAttribute)
    {
        m_pCore.swap(const_cast< scoped_thread_attribute& >(that).m_pCore);
    }
#endif // BOOST_LOG_BROKEN_REFERENCE_FROM_RVALUE_INIT

    BOOST_DELETED_FUNCTION(scoped_thread_attribute& operator= (scoped_thread_attribute const&))
};

} // namespace aux

//  Generator helper functions
/*!
 * Registers a thread-specific attribute
 *
 * \param name Attribute name
 * \param attr The attribute. Must not be NULL.
 * \return An unspecified guard object which may be used to initialize a \c scoped_attribute variable.
 */
BOOST_FORCEINLINE aux::scoped_thread_attribute add_scoped_thread_attribute(attribute_name const& name, attribute const& attr)
{
#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
    return aux::scoped_thread_attribute(name, attr);
#else
    aux::scoped_thread_attribute guard(name, attr);
    return boost::move(guard);
#endif
}

#ifndef BOOST_LOG_DOXYGEN_PASS

#define BOOST_LOG_SCOPED_THREAD_ATTR_INTERNAL(attr_name, attr, sentry_var_name)\
    BOOST_LOG_UNUSED_VARIABLE(::boost::log::scoped_attribute, sentry_var_name,\
        = ::boost::log::add_scoped_thread_attribute(attr_name, (attr)));

#endif // BOOST_LOG_DOXYGEN_PASS

//! The macro sets a scoped thread-wide attribute in a more compact way
#define BOOST_LOG_SCOPED_THREAD_ATTR(attr_name, attr)\
    BOOST_LOG_SCOPED_THREAD_ATTR_INTERNAL(\
        attr_name,\
        attr,\
        BOOST_LOG_UNIQUE_IDENTIFIER_NAME(_boost_log_scoped_thread_attr_sentry_))

//! The macro sets a scoped thread-wide tag in a more compact way
#define BOOST_LOG_SCOPED_THREAD_TAG(attr_name, attr_value)\
    BOOST_LOG_SCOPED_THREAD_ATTR(attr_name, ::boost::log::attributes::make_constant(attr_value))

BOOST_LOG_CLOSE_NAMESPACE // namespace log

} // namespace boost

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

#endif // BOOST_LOG_ATTRIBUTES_SCOPED_ATTRIBUTE_HPP_INCLUDED_
