/*
 *          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 <string>
#include <sstream>
#include <iostream>
#include <stdexcept>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/phoenix.hpp>
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/attributes/attribute_name.hpp>
#include <boost/log/attributes/scoped_attribute.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/utility/value_ref.hpp>
#include <boost/log/utility/formatting_ostream.hpp>
#include <boost/log/utility/manipulators/add_value.hpp>
#include <boost/log/utility/setup/filter_parser.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/setup/console.hpp>

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

//[ example_extension_filter_parser_point_definition
struct point
{
    float m_x, m_y;

    point() : m_x(0.0f), m_y(0.0f) {}
    point(float x, float y) : m_x(x), m_y(y) {}
};

bool operator== (point const& left, point const& right);
bool operator!= (point const& left, point const& right);

template< typename CharT, typename TraitsT >
std::basic_ostream< CharT, TraitsT >& operator<< (std::basic_ostream< CharT, TraitsT >& strm, point const& p);
template< typename CharT, typename TraitsT >
std::basic_istream< CharT, TraitsT >& operator>> (std::basic_istream< CharT, TraitsT >& strm, point& p);
//]

const float epsilon = 0.0001f;

bool operator== (point const& left, point const& right)
{
    return (left.m_x - epsilon <= right.m_x && left.m_x + epsilon >= right.m_x) &&
           (left.m_y - epsilon <= right.m_y && left.m_y + epsilon >= right.m_y);
}

bool operator!= (point const& left, point const& right)
{
    return !(left == right);
}

template< typename CharT, typename TraitsT >
std::basic_ostream< CharT, TraitsT >& operator<< (std::basic_ostream< CharT, TraitsT >& strm, point const& p)
{
    if (strm.good())
        strm << "(" << p.m_x << ", " << p.m_y << ")";
    return strm;
}

template< typename CharT, typename TraitsT >
std::basic_istream< CharT, TraitsT >& operator>> (std::basic_istream< CharT, TraitsT >& strm, point& p)
{
    if (strm.good())
    {
        CharT left_brace = static_cast< CharT >(0), comma = static_cast< CharT >(0), right_brace = static_cast< CharT >(0);
        strm.setf(std::ios_base::skipws);
        strm >> left_brace >> p.m_x >> comma >> p.m_y >> right_brace;
        if (left_brace != '(' || comma != ',' || right_brace != ')')
            strm.setstate(std::ios_base::failbit);
    }
    return strm;
}

#if 0
//[ example_extension_simple_filter_factory
void init_factories()
{
    //<-
    logging::register_simple_formatter_factory< point, char >("Coordinates");
    //->
    logging::register_simple_filter_factory< point, char >("Coordinates");
}
//]
#endif

//[ example_extension_custom_filter_factory
// Custom point filter factory
class point_filter_factory :
    public logging::filter_factory< char >
{
public:
    logging::filter on_exists_test(logging::attribute_name const& name)
    {
        return expr::has_attr< point >(name);
    }

    logging::filter on_equality_relation(logging::attribute_name const& name, string_type const& arg)
    {
        return expr::attr< point >(name) == parse_argument(arg);
    }

    logging::filter on_inequality_relation(logging::attribute_name const& name, string_type const& arg)
    {
        return expr::attr< point >(name) != parse_argument(arg);
    }

private:
    static point parse_argument(string_type const& arg)
    {
        std::istringstream strm(arg);
        point val;
        strm >> val;
        if (strm.fail() || strm.bad())
            throw std::runtime_error("Failed to parse point from \"" + arg + "\"");
        return val;
    }
};

void init_factories()
{
    //<-
    logging::register_simple_formatter_factory< point, char >("Coordinates");
    //->
    logging::register_filter_factory("Coordinates", boost::make_shared< point_filter_factory >());
}
//]

void init_logging()
{
    init_factories();

    logging::add_console_log
    (
        std::clog,
        keywords::filter = "%Coordinates% = \"(10, 10)\"",
        keywords::format = "%TimeStamp% %Coordinates% %Message%"
    );

    logging::add_common_attributes();
}

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

    src::logger lg;

    // We have to use scoped attributes in order coordinates to be passed to filters
    {
        BOOST_LOG_SCOPED_LOGGER_TAG(lg, "Coordinates", point(10, 10));
        BOOST_LOG(lg) << "Hello, world with coordinates (10, 10)!";
    }
    {
        BOOST_LOG_SCOPED_LOGGER_TAG(lg, "Coordinates", point(20, 20));
        BOOST_LOG(lg) << "Hello, world with coordinates (20, 20)!"; // this message will be suppressed by filter
    }

    return 0;
}
