#include <boost/variant.hpp>
#include <boost/mpl/vector.hpp>

#include <boost/utility/enable_if.hpp>
#include <boost/range/has_range_iterator.hpp>
#include <boost/mpl/and.hpp>
#include <boost/mpl/bool.hpp>

#include <vector>
#include <iostream>

namespace client {

    struct A { };
    struct B { };
    struct C { };

    typedef boost::make_variant_over<
        boost::mpl::vector<
            A,
            B,
            C
        >
    >::type component;

    typedef std::vector<component> components_type;

    struct foo {
        components_type     components;
        A                   a;
        std::vector<int>    pod_vec;
    };
}

namespace tools {
    namespace mpl = boost::mpl;

    // no idea, why there is no is_variant in boost
    template<typename T>
    struct is_variant : boost::mpl::false_
    { };

    template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
    struct is_variant<boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> >
    : boost::mpl::true_
    { };


    // void visit(visitor, variant<T>)
    template<typename VisitorT, typename VisitableT>
    inline
    typename boost::enable_if<
        mpl::and_<
            is_variant<VisitableT>,
            boost::is_void<typename VisitorT::result_type>
        >,
        void
    >::type
    visit(VisitorT& visitor, VisitableT const& visitable)
    {
        std::cout << "\nvisit::variant\n";
        boost::apply_visitor(visitor, visitable);
    }


    // void visit(visitor, T)
    template<typename VisitorT, typename VisitableT>
    inline
    typename boost::enable_if<
        mpl::and_<
            mpl::not_<is_variant<VisitableT> >,
            mpl::not_<boost::has_range_const_iterator<VisitableT> >,
            boost::is_void<typename VisitorT::result_type>
        >,
        void
    >::type
    visit(VisitorT& visitor, VisitableT const& visitable)
    {
        std::cout << "\nvisit::T\n";
        visitor(visitable);
    }


    // void visit(visitor, range<T>)
    template<typename VisitorT, typename VisitableT>
    inline
    typename boost::enable_if<
        mpl::and_<
            boost::has_range_const_iterator<VisitableT>,
            mpl::not_<is_variant<VisitableT> >,
            boost::is_void<typename VisitorT::result_type>
        >,
        void
    >::type
    visit(VisitorT& visitor, VisitableT const& visitable)
    {
        std::cout << "\nvisit::range<T>\n";
        for(auto const& obj : visitable) {
            visitor(obj);
        }
    }

    // void visit(visitor, range<variant<T>>)
    template<typename VisitorT, typename VisitableT>
    inline
    typename boost::enable_if<
        mpl::and_<
            boost::has_range_const_iterator<VisitableT>,
            is_variant<VisitableT>,
            boost::is_void<typename VisitorT::result_type>
        >,
        void
    >::type
    visit(VisitorT& visitor, VisitableT const& visitable)
    {
        std::cout << "\nvisit::range<variant>\n";
        for(auto const& obj : visitable) {
            boost::apply_visitor(visitor, obj);
        }
    }

}

template<typename OStreamT>
struct visitor : boost::static_visitor<void>
{
    visitor(OStreamT& os) : os(os) { }

    void operator()(client::foo const& foo) const {
        os << "Foo:";
        tools::visit(*this, foo.components);
        tools::visit(*this, foo.a);
        tools::visit(*this, foo.pod_vec);
        os << "\n";
    }

    void operator()(client::A const&) const {
        os << 'A';
    }

    void operator()(client::B const&) const {
        os << 'B';
    }

    void operator()(client::C const&) const {
        os << 'C';
    }

    void operator()(int) const {
        os << 'i';
    }

    OStreamT& os;
};

void print_all(client::foo const& foo) {

    visitor<std::ostream> v(std::cout);

    v(foo);
}

int main()
{
    client::foo foo;
    foo.components.push_back(client::A());
    foo.components.push_back(client::B());
    foo.components.push_back(client::C());

    foo.pod_vec.push_back(1);
    foo.pod_vec.push_back(2);
    foo.pod_vec.push_back(3);

    print_all(foo);

    std::cout << '\n';
}
