/*
 *          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   attr_value_visitation.cpp
 * \author Andrey Semashev
 * \date   21.01.2009
 *
 * \brief  This header contains tests for the attribute value extraction helpers.
 */

#define BOOST_TEST_MODULE attr_value_visitation

#include <string>
#include <boost/mpl/vector.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/tools/floating_point_comparison.hpp>
#include <boost/log/attributes/value_visitation.hpp>
#include <boost/log/attributes/constant.hpp>
#include <boost/log/attributes/attribute_set.hpp>
#include <boost/log/attributes/attribute_value_set.hpp>
#include "char_definitions.hpp"

namespace mpl = boost::mpl;
namespace logging = boost::log;
namespace attrs = logging::attributes;

namespace {

    // The receiver functional object that verifies the extracted attribute values
    struct my_receiver
    {
        typedef void result_type;

        enum type_expected
        {
            none_expected,
            int_expected,
            double_expected,
            string_expected
        };

        my_receiver() : m_Expected(none_expected), m_Int(0), m_Double(0.0) {}

        void set_expected()
        {
            m_Expected = none_expected;
        }
        void set_expected(int value)
        {
            m_Expected = int_expected;
            m_Int = value;
        }
        void set_expected(double value)
        {
            m_Expected = double_expected;
            m_Double = value;
        }
        void set_expected(std::string const& value)
        {
            m_Expected = string_expected;
            m_String = value;
        }

        // Implement visitation logic for all supported types
        void operator() (int const& value)
        {
            BOOST_CHECK_EQUAL(m_Expected, int_expected);
            BOOST_CHECK_EQUAL(m_Int, value);
        }
        void operator() (double const& value)
        {
            BOOST_CHECK_EQUAL(m_Expected, double_expected);
            BOOST_CHECK_CLOSE(m_Double, value, 0.001);
        }
        void operator() (std::string const& value)
        {
            BOOST_CHECK_EQUAL(m_Expected, string_expected);
            BOOST_CHECK_EQUAL(m_String, value);
        }
        void operator() (char value)
        {
            // This one should not be called
            BOOST_ERROR("The unexpected operator() has been called");
        }

    private:
        type_expected m_Expected;
        int m_Int;
        double m_Double;
        std::string m_String;
    };

} // namespace

// The test checks invokers specialized on a single attribute value type
BOOST_AUTO_TEST_CASE(single_type)
{
    typedef logging::attribute_set attr_set;
    typedef logging::attribute_value_set attr_values;
    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, set2, set3;
    set1[data::attr1()] = attr1;
    set1[data::attr2()] = attr2;

    attr_values values1(set1, set2, set3);
    values1.freeze();

    my_receiver recv;

    logging::value_visitor_invoker< int > invoker1;
    logging::value_visitor_invoker< double > invoker2;
    logging::value_visitor_invoker< std::string > invoker3;
    logging::value_visitor_invoker< char > invoker4;

    // These two extractors will find their values in the set
    recv.set_expected(10);
    BOOST_CHECK(invoker1(data::attr1(), values1, recv));

    recv.set_expected(5.5);
    BOOST_CHECK(invoker2(data::attr2(), values1, recv));

    // This one will not
    recv.set_expected();
    BOOST_CHECK(!invoker3(data::attr3(), values1, recv));

    // But it will find it in this set
    set1[data::attr3()] = attr3;

    attr_values values2(set1, set2, set3);
    values2.freeze();

    recv.set_expected("Hello, world!");
    BOOST_CHECK(invoker3(data::attr3(), values2, recv));

    // This one will find the sought attribute value, but it will have an incorrect type
    recv.set_expected();
    BOOST_CHECK(!invoker4(data::attr1(), values1, recv));

    // This one is the same, but there is a value of the requested type in the set
    BOOST_CHECK(!invoker1(data::attr2(), values1, recv));
}


// The test checks invokers specialized with type lists
BOOST_AUTO_TEST_CASE(multiple_types)
{
    typedef logging::attribute_set attr_set;
    typedef logging::attribute_value_set attr_values;
    typedef test_data< char > data;
    typedef mpl::vector< int, double, std::string, char >::type types;

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

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

    attr_values values1(set1, set2, set3);
    values1.freeze();

    my_receiver recv;

    logging::value_visitor_invoker< types > invoker;

    // These two extractors will find their values in the set
    recv.set_expected(10);
    BOOST_CHECK(invoker(data::attr1(), values1, recv));

    recv.set_expected(5.5);
    BOOST_CHECK(invoker(data::attr2(), values1, recv));

    // This one will not
    recv.set_expected();
    BOOST_CHECK(!invoker(data::attr3(), values1, recv));

    // But it will find it in this set
    set1[data::attr3()] = attr3;

    attr_values values2(set1, set2, set3);
    values2.freeze();

    recv.set_expected("Hello, world!");
    BOOST_CHECK(invoker(data::attr3(), values2, recv));
}

// The test verifies the visit function
BOOST_AUTO_TEST_CASE(visit_function)
{
    typedef logging::attribute_set attr_set;
    typedef logging::attribute_value_set attr_values;
    typedef test_data< char > data;
    typedef mpl::vector< int, double, std::string, char >::type types;

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

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

    attr_values values1(set1, set2, set3);
    values1.freeze();

    my_receiver recv;

    // These two extractors will find their values in the set
    recv.set_expected(10);
    BOOST_CHECK(logging::visit< types >(data::attr1(), values1, recv));

    recv.set_expected(5.5);
    BOOST_CHECK(logging::visit< double >(data::attr2(), values1, recv));

    // These will not
    recv.set_expected();
    BOOST_CHECK(!logging::visit< types >(data::attr3(), values1, recv));
    BOOST_CHECK(!logging::visit< char >(data::attr1(), values1, recv));

    // But it will find it in this set
    set1[data::attr3()] = attr3;

    attr_values values2(set1, set2, set3);
    values2.freeze();

    recv.set_expected("Hello, world!");
    BOOST_CHECK(logging::visit< std::string >(data::attr3(), values2, recv));
}
