/*
 *          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   filt_attr.cpp
 * \author Andrey Semashev
 * \date   31.01.2009
 *
 * \brief  This header contains tests for the \c attr filter.
 */

#define BOOST_TEST_MODULE filt_attr

#include <boost/predef/os.h>

// Try including WinAPI config as soon as possible so that any other headers don't include Windows SDK headers.
// This is important to select the target Windows version, as windows.h will be included by other Boost libraries, e.g. Regex.
#if defined(BOOST_OS_WINDOWS_AVAILABLE)
#define BOOST_USE_WINDOWS_H
#include <boost/winapi/config.hpp>
#endif

#include <memory>
#include <string>
#include <algorithm>
#include <boost/regex.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/phoenix/bind.hpp>
#include <boost/test/unit_test.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/utility/type_dispatch/standard_types.hpp>
#include <boost/log/support/regex.hpp>
#include <boost/log/expressions.hpp>
#include "char_definitions.hpp"

namespace phoenix = boost::phoenix;

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

// The test checks that general conditions work
BOOST_AUTO_TEST_CASE(general_conditions)
{
    typedef logging::attribute_set attr_set;
    typedef logging::attribute_value_set attr_values;
    typedef logging::filter filter;
    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;
    set1[data::attr3()] = attr3;

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

    filter f = expr::attr< int >(data::attr1()) == 10;
    BOOST_CHECK(f(values1));

    f = expr::attr< int >(data::attr1()) < 0;
    BOOST_CHECK(!f(values1));

    f = expr::attr< float >(data::attr1()).or_throw() > 0;
    BOOST_CHECK_THROW(f(values1), logging::runtime_error);
    f = expr::attr< float >(data::attr1()) > 0;
    BOOST_CHECK(!f(values1));

    f = expr::attr< int >(data::attr4()).or_throw() >= 1;
    BOOST_CHECK_THROW(f(values1), logging::runtime_error);
    f = expr::attr< int >(data::attr4()) >= 1;
    BOOST_CHECK(!f(values1));

    f = expr::attr< int >(data::attr4()) < 1;
    BOOST_CHECK(!f(values1));

    f = expr::attr< logging::numeric_types >(data::attr2()) > 5;
    BOOST_CHECK(f(values1));

    f = expr::attr< std::string >(data::attr3()) == "Hello, world!";
    BOOST_CHECK(f(values1));

    f = expr::attr< std::string >(data::attr3()) > "AAA";
    BOOST_CHECK(f(values1));
}

// The test checks that is_in_range condition works
BOOST_AUTO_TEST_CASE(in_range_check)
{
    typedef logging::attribute_set attr_set;
    typedef logging::attribute_value_set attr_values;
    typedef logging::filter filter;
    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;
    set1[data::attr3()] = attr3;

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

    filter f = expr::is_in_range(expr::attr< int >(data::attr1()), 5, 20);
    BOOST_CHECK(f(values1));

    f = expr::is_in_range(expr::attr< int >(data::attr1()), 5, 10);
    BOOST_CHECK(!f(values1));

    f = expr::is_in_range(expr::attr< int >(data::attr1()), 10, 20);
    BOOST_CHECK(f(values1));

    f = expr::is_in_range(expr::attr< logging::numeric_types >(data::attr2()), 5, 6);
    BOOST_CHECK(f(values1));

    f = expr::is_in_range(expr::attr< std::string >(data::attr3()), "AAA", "zzz");
    BOOST_CHECK(f(values1));

    // Check that strings are saved into the filter by value
    char buf1[128];
    char buf2[128];
    std::strcpy(buf1, "AAA");
    std::strcpy(buf2, "zzz");
    f = expr::is_in_range(expr::attr< std::string >(data::attr3()), buf1, buf2);
    std::fill_n(buf1, sizeof(buf1), static_cast< char >(0));
    std::fill_n(buf2, sizeof(buf2), static_cast< char >(0));
    BOOST_CHECK(f(values1));

    std::strcpy(buf1, "AAA");
    std::strcpy(buf2, "zzz");
    f = expr::is_in_range(expr::attr< std::string >(data::attr3()),
        static_cast< const char* >(buf1), static_cast< const char* >(buf2));
    std::fill_n(buf1, sizeof(buf1), static_cast< char >(0));
    std::fill_n(buf2, sizeof(buf2), static_cast< char >(0));
    BOOST_CHECK(f(values1));
}

namespace {

    struct predicate
    {
        typedef bool result_type;

        explicit predicate(unsigned int& present_counter, bool& result) :
            m_PresentCounter(present_counter),
            m_Result(result)
        {
        }

        template< typename T, typename TagT >
        result_type operator() (logging::value_ref< T, TagT > const& val) const
        {
            m_PresentCounter += !val.empty();
            return m_Result;
        }

    private:
        unsigned int& m_PresentCounter;
        bool& m_Result;
    };

} // namespace

// The test checks that phoenix::bind interaction works
BOOST_AUTO_TEST_CASE(bind_support_check)
{
    typedef logging::attribute_set attr_set;
    typedef logging::attribute_value_set attr_values;
    typedef logging::filter filter;
    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;
    set1[data::attr3()] = attr3;

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

    unsigned int present_counter = 0;
    bool predicate_result = false;

    filter f = phoenix::bind(predicate(present_counter, predicate_result), expr::attr< int >(data::attr1()));
    BOOST_CHECK_EQUAL(f(values1), predicate_result);
    BOOST_CHECK_EQUAL(present_counter, 1U);

    predicate_result = true;
    BOOST_CHECK_EQUAL(f(values1), predicate_result);
    BOOST_CHECK_EQUAL(present_counter, 2U);

    f = phoenix::bind(predicate(present_counter, predicate_result), expr::attr< logging::numeric_types >(data::attr2()));
    BOOST_CHECK_EQUAL(f(values1), predicate_result);
    BOOST_CHECK_EQUAL(present_counter, 3U);

    f = phoenix::bind(predicate(present_counter, predicate_result), expr::attr< int >(data::attr2()).or_throw());
    BOOST_CHECK_THROW(f(values1), logging::runtime_error);
    f = phoenix::bind(predicate(present_counter, predicate_result), expr::attr< int >(data::attr2()));
    BOOST_CHECK_EQUAL(f(values1), true);
    BOOST_CHECK_EQUAL(present_counter, 3U);

    f = phoenix::bind(predicate(present_counter, predicate_result), expr::attr< int >(data::attr4()).or_throw());
    BOOST_CHECK_THROW(f(values1), logging::runtime_error);
    f = phoenix::bind(predicate(present_counter, predicate_result), expr::attr< int >(data::attr4()));
    BOOST_CHECK_EQUAL(f(values1), true);
    BOOST_CHECK_EQUAL(present_counter, 3U);
}

// The test checks that begins_with condition works
BOOST_AUTO_TEST_CASE(begins_with_check)
{
    typedef logging::attribute_set attr_set;
    typedef logging::attribute_value_set attr_values;
    typedef logging::filter filter;
    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;
    set1[data::attr3()] = attr3;

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

    filter f = expr::begins_with(expr::attr< std::string >(data::attr3()), "Hello");
    BOOST_CHECK(f(values1));

    f = expr::begins_with(expr::attr< std::string >(data::attr3()), "hello");
    BOOST_CHECK(!f(values1));

    f = expr::begins_with(expr::attr< std::string >(data::attr3()).or_throw(), "Bye");
    BOOST_CHECK(!f(values1));

    f = expr::begins_with(expr::attr< std::string >(data::attr3()).or_throw(), "world!");
    BOOST_CHECK(!f(values1));

    f = expr::begins_with(expr::attr< std::string >(data::attr2()), "Hello");
    BOOST_CHECK(!f(values1));

    f = expr::begins_with(expr::attr< std::string >(data::attr4()), "Hello");
    BOOST_CHECK(!f(values1));
}

// The test checks that ends_with condition works
BOOST_AUTO_TEST_CASE(ends_with_check)
{
    typedef logging::attribute_set attr_set;
    typedef logging::attribute_value_set attr_values;
    typedef logging::filter filter;
    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;
    set1[data::attr3()] = attr3;

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

    filter f = expr::ends_with(expr::attr< std::string >(data::attr3()), "world!");
    BOOST_CHECK(f(values1));

    f = expr::ends_with(expr::attr< std::string >(data::attr3()), "World!");
    BOOST_CHECK(!f(values1));

    f = expr::ends_with(expr::attr< std::string >(data::attr3()).or_throw(), "Bye");
    BOOST_CHECK(!f(values1));

    f = expr::ends_with(expr::attr< std::string >(data::attr3()).or_throw(), "Hello");
    BOOST_CHECK(!f(values1));

    f = expr::ends_with(expr::attr< std::string >(data::attr2()), "world!");
    BOOST_CHECK(!f(values1));

    f = expr::ends_with(expr::attr< std::string >(data::attr4()), "world!");
    BOOST_CHECK(!f(values1));
}

// The test checks that contains condition works
BOOST_AUTO_TEST_CASE(contains_check)
{
    typedef logging::attribute_set attr_set;
    typedef logging::attribute_value_set attr_values;
    typedef logging::filter filter;
    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;
    set1[data::attr3()] = attr3;

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

    filter f = expr::contains(expr::attr< std::string >(data::attr3()), "Hello");
    BOOST_CHECK(f(values1));

    f = expr::contains(expr::attr< std::string >(data::attr3()), "hello");
    BOOST_CHECK(!f(values1));

    f = expr::contains(expr::attr< std::string >(data::attr3()).or_throw(), "o, w");
    BOOST_CHECK(f(values1));

    f = expr::contains(expr::attr< std::string >(data::attr3()).or_throw(), "world!");
    BOOST_CHECK(f(values1));

    f = expr::contains(expr::attr< std::string >(data::attr2()), "Hello");
    BOOST_CHECK(!f(values1));

    f = expr::contains(expr::attr< std::string >(data::attr4()), "Hello");
    BOOST_CHECK(!f(values1));
}

// The test checks that matches condition works
BOOST_AUTO_TEST_CASE(matches_check)
{
    typedef logging::attribute_set attr_set;
    typedef logging::attribute_value_set attr_values;
    typedef logging::filter filter;
    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;
    set1[data::attr3()] = attr3;

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

    boost::regex rex("hello");
    filter f = expr::matches(expr::attr< std::string >(data::attr3()), rex);
    BOOST_CHECK(!f(values1));

    rex = ".*world.*";
    f = expr::matches(expr::attr< std::string >(data::attr3()).or_throw(), rex);
    BOOST_CHECK(f(values1));

    rex = ".*";
    f = expr::matches(expr::attr< std::string >(data::attr2()), rex);
    BOOST_CHECK(!f(values1));

    f = expr::matches(expr::attr< std::string >(data::attr4()), rex);
    BOOST_CHECK(!f(values1));
}

// The test checks that the filter composition works
BOOST_AUTO_TEST_CASE(composition_check)
{
    typedef logging::attribute_set attr_set;
    typedef logging::attribute_value_set attr_values;
    typedef logging::filter filter;
    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;
    attr_values values1(set1, set2, set3);
    values1.freeze();
    set1[data::attr2()] = attr2;
    attr_values values2(set1, set2, set3);
    values2.freeze();
    set1[data::attr3()] = attr3;
    set1[data::attr1()] = attr1;
    attr_values values3(set1, set2, set3);
    values3.freeze();

    filter f =
        expr::attr< int >(data::attr1()) <= 10 ||
        expr::is_in_range(expr::attr< double >(data::attr2()), 2.2, 7.7);
    BOOST_CHECK(!f(values1));
    BOOST_CHECK(f(values2));
    BOOST_CHECK(f(values3));

    f = expr::attr< int >(data::attr1()) == 10 &&
        expr::begins_with(expr::attr< std::string >(data::attr3()), "Hello");
    BOOST_CHECK(!f(values1));
    BOOST_CHECK(!f(values2));
    BOOST_CHECK(f(values3));
}
