///////////////////////////////////////////////////////////////////////////////
// proto_fusion.cpp
//
//  Copyright 2008 Eric Niebler. 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 <boost/proto/core.hpp>
#include <boost/proto/fusion.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/utility/addressof.hpp>
#include <sstream>

boost::proto::terminal<char>::type a_ = {'a'};
boost::proto::terminal<char>::type b_ = {'b'};
boost::proto::terminal<char>::type c_ = {'c'};
boost::proto::terminal<char>::type d_ = {'d'};
boost::proto::terminal<char>::type e_ = {'e'};
boost::proto::terminal<char>::type f_ = {'f'};
boost::proto::terminal<char>::type g_ = {'g'};
boost::proto::terminal<char>::type h_ = {'h'};
boost::proto::terminal<char>::type i_ = {'i'};

std::ostream &operator <<(std::ostream &sout, boost::proto::tag::shift_right)
{
    return sout << ">>";
}

std::ostream &operator <<(std::ostream &sout, boost::proto::tag::bitwise_or)
{
    return sout << "|";
}

template<typename Args>
std::ostream &operator <<(std::ostream &sout, boost::proto::expr<boost::proto::tag::terminal, Args, 0> const *op)
{
    return sout << boost::proto::value(*op);
}

template<typename Tag, typename Args>
std::ostream &operator <<(std::ostream &sout, boost::proto::expr<Tag, Args, 1> const *op)
{
    return sout << Tag() << boost::addressof(boost::proto::child(*op));
}

template<typename Tag, typename Args>
std::ostream &operator <<(std::ostream &sout, boost::proto::expr<Tag, Args, 2> const *op)
{
    return sout << boost::addressof(boost::proto::left(*op)) << Tag() << boost::addressof(boost::proto::right(*op));
}

///////////////////////////////////////////////////////////////////////////////
// to_string
//
struct to_string
{
    to_string(std::ostream &sout)
      : sout_(sout)
    {}

    template<typename Op>
    void operator ()(Op const &op) const
    {
        this->sout_ << '(' << boost::addressof(op) << ')';
    }
private:
    std::ostream &sout_;
};

void test1()
{
    using boost::proto::flatten;

    std::stringstream sout;

    // Test for 1-way branching "tree"
    sout.str("");
    boost::fusion::for_each(flatten(!!!!(a_ >> b_)), to_string(sout));
    BOOST_CHECK_EQUAL("(a>>b)", sout.str());

    // Tests for 2-way branching trees
    sout.str("");
    boost::fusion::for_each(flatten(a_ >> b_ >> c_), to_string(sout));
    BOOST_CHECK_EQUAL("(a)(b)(c)", sout.str());

    sout.str("");
    boost::fusion::for_each(flatten(a_ | b_ | c_), to_string(sout));
    BOOST_CHECK_EQUAL("(a)(b)(c)", sout.str());

    sout.str("");
    boost::fusion::for_each(flatten(a_ >> b_ | c_ >> d_), to_string(sout));
    BOOST_CHECK_EQUAL("(a>>b)(c>>d)", sout.str());

    sout.str("");
    boost::fusion::for_each(flatten(a_ | b_ >> c_ | d_), to_string(sout));
    BOOST_CHECK_EQUAL("(a)(b>>c)(d)", sout.str());

    sout.str("");
    boost::fusion::for_each(flatten(a_ >> b_ | c_ >> d_ | e_ >> f_ >> g_), to_string(sout));
    BOOST_CHECK_EQUAL("(a>>b)(c>>d)(e>>f>>g)", sout.str());

    sout.str("");
    boost::fusion::for_each(flatten(a_ >> b_ | c_ >> d_ | e_ >> (f_ | g_) >> h_), to_string(sout));
    BOOST_CHECK_EQUAL("(a>>b)(c>>d)(e>>f|g>>h)", sout.str());

    // Test for n-way branching tree
    sout.str("");
    boost::fusion::for_each(flatten(a_(b_(c_ >> d_, e_ | f_), g_ >> h_)(i_)), to_string(sout));
    BOOST_CHECK_EQUAL("(a)(b)(c>>d)(e|f)(g>>h)(i)", sout.str());
}

using namespace boost::unit_test;
///////////////////////////////////////////////////////////////////////////////
// init_unit_test_suite
//
test_suite* init_unit_test_suite( int argc, char* argv[] )
{
    test_suite *test = BOOST_TEST_SUITE("test proto and fusion integration");

    test->add(BOOST_TEST_CASE(&test1));

    return test;
}
