/*
 *          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   core.cpp
 * \author Andrey Semashev
 * \date   08.02.2009
 *
 * \brief  This header contains tests for the logging core.
 */

#define BOOST_TEST_MODULE core

#include <cstddef>
#include <map>
#include <string>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/move/utility_core.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/log/core/core.hpp>
#include <boost/log/attributes/constant.hpp>
#include <boost/log/attributes/attribute_set.hpp>
#include <boost/log/attributes/attribute_value_set.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks/sink.hpp>
#include <boost/log/core/record.hpp>
#ifndef BOOST_LOG_NO_THREADS
#include <thread>
#endif // BOOST_LOG_NO_THREADS
#include "char_definitions.hpp"
#include "test_sink.hpp"

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

// The test checks that message filtering works
BOOST_AUTO_TEST_CASE(filtering)
{
    typedef logging::attribute_set attr_set;
    typedef logging::core core;
    typedef logging::record record_type;
    typedef test_data< char > data;

    attrs::constant< int > attr1(10);
    attrs::constant< double > attr2(5.5);

    attr_set set1;
    set1[data::attr1()] = attr1;
    set1[data::attr2()] = attr2;

    boost::shared_ptr< core > pCore = core::get();
    boost::shared_ptr< test_sink > pSink(new test_sink());
    pCore->add_sink(pSink);

    // No filtering at all
    {
        record_type rec = pCore->open_record(set1);
        BOOST_REQUIRE(rec);
        pCore->push_record(boost::move(rec));
        BOOST_CHECK_EQUAL(pSink->m_RecordCounter, 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr1()], 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr2()], 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr3()], 0UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr4()], 0UL);
        pSink->clear();
    }

    // Core-level filtering
    {
        pCore->set_filter(expr::has_attr(data::attr3()));
        record_type rec = pCore->open_record(set1);
        BOOST_CHECK(!rec);
        BOOST_CHECK_EQUAL(pSink->m_RecordCounter, 0UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr1()], 0UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr2()], 0UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr3()], 0UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr4()], 0UL);
        pSink->clear();
    }
    {
        pCore->set_filter(expr::has_attr(data::attr2()));
        record_type rec = pCore->open_record(set1);
        BOOST_REQUIRE(rec);
        pCore->push_record(boost::move(rec));
        BOOST_CHECK_EQUAL(pSink->m_RecordCounter, 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr1()], 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr2()], 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr3()], 0UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr4()], 0UL);
        pSink->clear();
    }

    // Sink-level filtering
    {
        pCore->reset_filter();
        pSink->set_filter(expr::has_attr(data::attr2()));
        record_type rec = pCore->open_record(set1);
        BOOST_REQUIRE(rec);
        pCore->push_record(boost::move(rec));
        BOOST_CHECK_EQUAL(pSink->m_RecordCounter, 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr1()], 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr2()], 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr3()], 0UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr4()], 0UL);
        pSink->clear();
    }
    {
        pSink->set_filter(expr::has_attr(data::attr3()));
        record_type rec = pCore->open_record(set1);
        BOOST_CHECK(!rec);
        BOOST_CHECK_EQUAL(pSink->m_RecordCounter, 0UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr1()], 0UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr2()], 0UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr3()], 0UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr4()], 0UL);
        pSink->clear();
        pSink->reset_filter();
    }
    // Only one sink of the two accepts the record
    {
        pSink->set_filter(expr::has_attr(data::attr2()));

        boost::shared_ptr< test_sink > pSink2(new test_sink());
        pCore->add_sink(pSink2);
        pSink2->set_filter(expr::has_attr(data::attr3()));

        record_type rec = pCore->open_record(set1);
        BOOST_REQUIRE(rec);
        pCore->push_record(boost::move(rec));

        BOOST_CHECK_EQUAL(pSink->m_RecordCounter, 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr1()], 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr2()], 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr3()], 0UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr4()], 0UL);
        pSink->clear();

        BOOST_CHECK_EQUAL(pSink2->m_RecordCounter, 0UL);
        BOOST_CHECK_EQUAL(pSink2->m_Consumed[data::attr1()], 0UL);
        BOOST_CHECK_EQUAL(pSink2->m_Consumed[data::attr2()], 0UL);
        BOOST_CHECK_EQUAL(pSink2->m_Consumed[data::attr3()], 0UL);
        BOOST_CHECK_EQUAL(pSink2->m_Consumed[data::attr4()], 0UL);
        pCore->remove_sink(pSink2);
    }

    pCore->remove_sink(pSink);
    pCore->reset_filter();
}

#ifndef BOOST_LOG_NO_THREADS
namespace {

    //! A test routine that runs in a separate thread
    void thread_attributes_test()
    {
        typedef test_data< char > data;
        typedef logging::core core;
        typedef logging::record record_type;
        typedef logging::attribute_set attr_set;
        attrs::constant< short > attr4(255);

        boost::shared_ptr< core > pCore = core::get();
        pCore->add_thread_attribute(data::attr4(), attr4);

        attr_set set1;
        record_type rec = pCore->open_record(set1);
        BOOST_CHECK(rec);
        if (rec)
            pCore->push_record(boost::move(rec));
    }

} // namespace
#endif // BOOST_LOG_NO_THREADS

// The test checks that global and thread-specific attributes work
BOOST_AUTO_TEST_CASE(attributes)
{
    typedef logging::attribute_set attr_set;
    typedef logging::core core;
    typedef logging::record record_type;
    typedef test_data< char > data;

    attrs::constant< int > attr1(10);
    attrs::constant< double > attr2(5.5);
    attrs::constant< std::string > attr3("Hello, world!");

    attr_set set1;
    set1[data::attr1()] = attr1;

    boost::shared_ptr< core > pCore = core::get();
    boost::shared_ptr< test_sink > pSink(new test_sink());
    pCore->add_sink(pSink);

    attr_set::iterator itGlobal = pCore->add_global_attribute(data::attr2(), attr2).first;
    attr_set::iterator itThread = pCore->add_thread_attribute(data::attr3(), attr3).first;

    {
        attr_set glob = pCore->get_global_attributes();
        BOOST_CHECK_EQUAL(glob.size(), 1UL);
        BOOST_CHECK_EQUAL(glob.count(data::attr2()), 1UL);

        attr_set thr = pCore->get_thread_attributes();
        BOOST_CHECK_EQUAL(thr.size(), 1UL);
        BOOST_CHECK_EQUAL(thr.count(data::attr3()), 1UL);
    }
    {
        record_type rec = pCore->open_record(set1);
        BOOST_REQUIRE(rec);
        pCore->push_record(boost::move(rec));
        BOOST_CHECK_EQUAL(pSink->m_RecordCounter, 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr1()], 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr2()], 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr3()], 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr4()], 0UL);
        pSink->clear();
    }
#ifndef BOOST_LOG_NO_THREADS
    {
        std::thread th(&thread_attributes_test);
        th.join();
        BOOST_CHECK_EQUAL(pSink->m_RecordCounter, 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr1()], 0UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr2()], 1UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr3()], 0UL);
        BOOST_CHECK_EQUAL(pSink->m_Consumed[data::attr4()], 1UL);
        pSink->clear();

        // Thread-specific attributes must not interfere
        attr_set thr = pCore->get_thread_attributes();
        BOOST_CHECK_EQUAL(thr.size(), 1UL);
        BOOST_CHECK_EQUAL(thr.count(data::attr3()), 1UL);
    }
#endif // BOOST_LOG_NO_THREADS

    pCore->remove_global_attribute(itGlobal);
    pCore->remove_thread_attribute(itThread);
    pCore->remove_sink(pSink);
}
