/*
 *          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)
 */

#include <mutex>
#include <string>
#include <ostream>
#include <fstream>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/scope_exit.hpp>
#include <boost/mpl/quote.hpp>
#include <boost/parameter/keyword.hpp>
#include <boost/move/utility_core.hpp>
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/attributes/constant.hpp>
#include <boost/log/sources/features.hpp>
#include <boost/log/sources/basic_logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/utility/strictest_lock.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>

namespace logging = boost::log;
namespace src = boost::log::sources;
namespace expr = boost::log::expressions;
namespace sinks = boost::log::sinks;
namespace attrs = boost::log::attributes;
namespace keywords = boost::log::keywords;

//[ example_extension_record_tagger_keyword
namespace my_keywords {

    BOOST_PARAMETER_KEYWORD(tag_ns, tag)

}
//]

//[ example_extension_record_tagger_declaration
template< typename BaseT >
class record_tagger_feature :
    public BaseT                        /*< the feature should derive from other features or the basic_logger class >*/
{
public:
    // Let's import some types that we will need. These imports should be public,
    // in order to allow other features that may derive from record_tagger to do the same.
    typedef typename BaseT::char_type char_type;
    typedef typename BaseT::threading_model threading_model;

public:
    // Default constructor. Initializes m_Tag to an invalid value.
    record_tagger_feature();
    // Copy constructor. Initializes m_Tag to a value, equivalent to that.m_Tag.
    record_tagger_feature(record_tagger_feature const& that);
    // Forwarding constructor with named parameters
    template< typename ArgsT >
    record_tagger_feature(ArgsT const& args);

    // The method will require locking, so we have to define locking requirements for it.
    // We use the strictest_lock trait in order to choose the most restricting lock type.
    typedef typename logging::strictest_lock<
        std::lock_guard< threading_model >,
        typename BaseT::open_record_lock,
        typename BaseT::add_attribute_lock,
        typename BaseT::remove_attribute_lock
    >::type open_record_lock;

protected:
    // Lock-less implementation of operations
    template< typename ArgsT >
    logging::record open_record_unlocked(ArgsT const& args);
};

// A convenience metafunction to specify the feature
// in the list of features of the final logger later
struct record_tagger :
    public boost::mpl::quote1< record_tagger_feature >
{
};
//]

//[ example_extension_record_tagger_structors
template< typename BaseT >
record_tagger_feature< BaseT >::record_tagger_feature()
{
}

template< typename BaseT >
record_tagger_feature< BaseT >::record_tagger_feature(record_tagger_feature const& that) :
    BaseT(static_cast< BaseT const& >(that))
{
}

template< typename BaseT >
template< typename ArgsT >
record_tagger_feature< BaseT >::record_tagger_feature(ArgsT const& args) : BaseT(args)
{
}
//]

//[ example_extension_record_tagger_open_record
template< typename BaseT >
template< typename ArgsT >
logging::record record_tagger_feature< BaseT >::open_record_unlocked(ArgsT const& args)
{
    // Extract the named argument from the parameters pack
    std::string tag_value = args[my_keywords::tag | std::string()];

    logging::attribute_set& attrs = BaseT::attributes();
    logging::attribute_set::iterator tag = attrs.end();
    if (!tag_value.empty())
    {
        // Add the tag as a new attribute
        std::pair<
            logging::attribute_set::iterator,
            bool
        > res = BaseT::add_attribute_unlocked("Tag",
            attrs::constant< std::string >(tag_value));
        if (res.second)
            tag = res.first;
    }

    // In any case, after opening a record remove the tag from the attributes
    BOOST_SCOPE_EXIT_TPL((&tag)(&attrs))
    {
        if (tag != attrs.end())
            attrs.erase(tag);
    }
    BOOST_SCOPE_EXIT_END

    // Forward the call to the base feature
    return BaseT::open_record_unlocked(args);
}
//]

//[ example_extension_record_tagger_my_logger
template< typename LevelT = int >
class my_logger :
    public src::basic_composite_logger<
        char,                           /*< character type for the logger >*/
        my_logger< LevelT >,            /*< final logger type >*/
        src::single_thread_model,       /*< the logger does not perform thread synchronization; use `multi_thread_model` to declare a thread-safe logger >*/
        src::features<                  /*< the list of features we want to combine >*/
            src::severity< LevelT >,
            record_tagger
        >
    >
{
    // The following line will automatically generate forwarding constructors that
    // will call to the corresponding constructors of the base class
    BOOST_LOG_FORWARD_LOGGER_MEMBERS_TEMPLATE(my_logger)
};
//]

//[ example_extension_record_tagger_severity
enum severity_level
{
    normal,
    warning,
    error
};
//]

inline std::ostream& operator<< (std::ostream& strm, severity_level level)
{
    const char* levels[] =
    {
        "normal",
        "warning",
        "error"
    };

    if (static_cast< std::size_t >(level) < sizeof(levels) / sizeof(*levels))
        strm << levels[level];
    else
        strm << static_cast< int >(level);

    return strm;
}

//[ example_extension_record_tagger_manual_logging
void manual_logging()
{
    my_logger< severity_level > logger;

    logging::record rec = logger.open_record((keywords::severity = normal, my_keywords::tag = "GUI"));
    if (rec)
    {
        logging::record_ostream strm(rec);
        strm << "The user has confirmed his choice";
        strm.flush();
        logger.push_record(boost::move(rec));
    }
}
//]

//[ example_extension_record_tagger_macro_logging
#define LOG_WITH_TAG(lg, sev, tg) \
    BOOST_LOG_WITH_PARAMS((lg), (keywords::severity = (sev))(my_keywords::tag = (tg)))

void logging_function()
{
    my_logger< severity_level > logger;

    LOG_WITH_TAG(logger, normal, "GUI") << "The user has confirmed his choice";
}
//]

void init()
{
    typedef sinks::synchronous_sink< sinks::text_ostream_backend > text_sink;
    boost::shared_ptr< text_sink > sink = boost::make_shared< text_sink >();

    sink->locked_backend()->add_stream(
        boost::make_shared< std::ofstream >("sample.log"));

    sink->set_formatter
    (
        expr::stream
            << expr::attr< unsigned int >("LineID")
            << ": <" << expr::attr< severity_level >("Severity")
            << "> [" << expr::attr< std::string >("Tag") << "]\t"
            << expr::smessage
    );

    logging::core::get()->add_sink(sink);

    // Add attributes
    logging::add_common_attributes();
}


int main(int, char*[])
{
    init();

    logging_function();
    manual_logging();

    return 0;
}
