/*=============================================================================
    Copyright (c) 2006-2007 Tobias Schwinger
  
    Use modification and distribution are subject to 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).

    Problem:

    How to "do the Bind?"

    This recipe shows how to implement a function binder, similar to 
    Boost.Bind based on the Functional module of Fusion.

    It works as follows:

    'bind' is a global, stateless function object. It is implemented in
    fused form (fused_binder) and transformed into a variadic function 
    object. When called, 'bind' returns another function object, which
    holds the arguments of the call to 'bind'. It is, again, implemented 
    in fused form (fused_bound_function) and transformed into unfused
    form. 
==============================================================================*/


#include <boost/fusion/functional/invocation/invoke.hpp>
#include <boost/fusion/functional/adapter/unfused.hpp>
#include <boost/fusion/support/deduce_sequence.hpp>

#include <boost/fusion/sequence/intrinsic/at.hpp>
#include <boost/fusion/sequence/intrinsic/front.hpp>
#include <boost/fusion/sequence/intrinsic/size.hpp>
#include <boost/fusion/algorithm/transformation/transform.hpp>
#include <boost/fusion/algorithm/transformation/pop_front.hpp>
#include <boost/fusion/algorithm/iteration/fold.hpp>
#include <boost/fusion/view/filter_view.hpp>

#include <boost/functional/forward_adapter.hpp>
#include <boost/functional/lightweight_forward_adapter.hpp>

#include <boost/type_traits/remove_reference.hpp>

#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/max.hpp>
#include <boost/mpl/next.hpp>

#include <boost/core/ref.hpp>
#include <iostream>
#include <typeinfo>

namespace impl
{
    namespace fusion = boost::fusion;
    namespace traits = boost::fusion::traits;
    namespace result_of = boost::fusion::result_of;
    namespace mpl = boost::mpl;
    using mpl::placeholders::_;

    // Placeholders (we inherit from mpl::int_, so we can use placeholders
    // as indices for fusion::at, later) 
    template <int I> struct placeholder : mpl::int_<I> { };

    // A traits class to find out whether T is a placeholeder
    template <typename T> struct is_placeholder              : mpl::false_  { };
    template <int I> struct is_placeholder< placeholder<I> > : mpl::true_   { };
    template <int I> struct is_placeholder< placeholder<I> & > : mpl::true_   { };
    template <int I> struct is_placeholder< placeholder<I> const   > : mpl::true_   { };
    template <int I> struct is_placeholder< placeholder<I> const & > : mpl::true_   { };

    // This class template provides a Polymorphic Function Object to be used
    // with fusion::transform. It is applied to the sequence of arguments that
    // describes the binding and holds a reference to the sequence of arguments 
    // from the final call. 
    template<class FinalArgs> struct argument_transform
    {
        FinalArgs const & ref_final_args;
    public:

        explicit argument_transform(FinalArgs const & final_args)
            : ref_final_args(final_args)
        { }

        // A placeholder? Replace it with an argument from the final call...
        template <int Index>
        inline typename result_of::at_c<FinalArgs const, Index>::type
        operator()(placeholder<Index> const &) const
        {
            return fusion::at_c<Index>(this->ref_final_args);
        }
        // ...just return the bound argument, otherwise.
        template <typename T> inline T & operator()(T & bound) const
        {
            return bound;
        }

        template <typename Signature>
        struct result;

        template <class Self, typename T>
        struct result< Self (T) >
            : mpl::eval_if< is_placeholder<T>, 
                result_of::at<FinalArgs,typename boost::remove_reference<T>::type>,
                mpl::identity<T>
            >
        { };
    };

    // Fused implementation of the bound function, the function object 
    // returned by bind
    template <class BindArgs> class fused_bound_function 
    {
        // Transform arguments to be held by value
        typedef typename traits::deduce_sequence<BindArgs>::type bound_args;

        bound_args fsq_bind_args;
    public:

        fused_bound_function(BindArgs const & bind_args)
          : fsq_bind_args(bind_args)
        { }

        template <typename Signature>
        struct result;

        template <class FinalArgs>
        struct result_impl
            : result_of::invoke< typename result_of::front<bound_args>::type,
                typename result_of::transform<
                    typename result_of::pop_front<bound_args>::type,
                    argument_transform<FinalArgs> const 
                >::type
            >
        { }; 

        template <class Self, class FinalArgs>
        struct result< Self (FinalArgs) >
            : result_impl< typename boost::remove_reference<FinalArgs>::type > 
        { };

        template <class FinalArgs>
        inline typename result_impl<FinalArgs>::type 
        operator()(FinalArgs const & final_args) const
        {
            return fusion::invoke( fusion::front(this->fsq_bind_args),
                fusion::transform( fusion::pop_front(this->fsq_bind_args),
                    argument_transform<FinalArgs>(final_args) ) );
        }
        // Could add a non-const variant - omitted for readability

    };

    // Find the number of placeholders in use
    struct n_placeholders
    {
        struct fold_op
        {
            template <typename Sig> struct result;
            template <class S, class A, class B> struct result< S(A &,B &) > 
                : mpl::max<A,B> { };
        };
        struct filter_pred
        {
            template <class X> struct apply : is_placeholder<X> { };
        };

        template <typename Seq>
        struct apply
            : mpl::next< typename result_of::fold<
                fusion::filter_view<Seq,filter_pred>, mpl::int_<-1>, fold_op
            >::type>::type
        { };
    };

    // Fused implementation of the 'bind' function
    struct fused_binder
    {
        template <class Signature>
        struct result;

        template <class BindArgs,
            int Placeholders = n_placeholders::apply<BindArgs>::value>
        struct result_impl
        {
            typedef boost::forward_adapter<fusion::unfused< 
                fused_bound_function<BindArgs>,!Placeholders>,Placeholders> type;
        };

        template <class Self, class BindArgs>
        struct result< Self (BindArgs) >
            : result_impl< typename boost::remove_reference<BindArgs>::type >
        { };

        template <class BindArgs>
        inline typename result_impl< BindArgs >::type 
        operator()(BindArgs & bind_args) const
        {
            return typename result< void(BindArgs) >::type(
                fusion::unfused< fused_bound_function<BindArgs>,
                    ! n_placeholders::apply<BindArgs>::value >(bind_args) );
        }
    };

    // The binder's unfused type. We use lightweght_forward_adapter to make 
    // that thing more similar to Boost.Bind. Because of that we have to use 
    // Boost.Ref (below in the sample code)
    typedef boost::lightweight_forward_adapter< fusion::unfused<fused_binder> > binder;
}

// Placeholder globals
impl::placeholder<0> const _1_ = impl::placeholder<0>();
impl::placeholder<1> const _2_ = impl::placeholder<1>();
impl::placeholder<2> const _3_ = impl::placeholder<2>();
impl::placeholder<3> const _4_ = impl::placeholder<3>();

// The bind function is a global, too
impl::binder const bind = impl::binder();


// OK, let's try it out:

struct func
{
    typedef int result_type;

    inline int operator()() const
    {
        std::cout << "operator()" << std::endl;
        return 0;
    }

    template <typename A> 
    inline int operator()(A const & a) const
    {
        std::cout << "operator()(A const & a)" << std::endl;
        std::cout << "  a = " << a << "  A = " << typeid(A).name() << std::endl;
        return 1;
    }

    template <typename A, typename B> 
    inline int operator()(A const & a, B & b) const
    {
        std::cout << "operator()(A const & a, B & b)" << std::endl;
        std::cout << "  a = " << a << "  A = " << typeid(A).name() << std::endl;
        std::cout << "  b = " << b << "  B = " << typeid(B).name() << std::endl;
        return 2;
    }
};

int main()
{
    func f;
    int value = 42;
    using boost::ref;

    int errors = 0;

    errors += !( bind(f)() == 0);
    errors += !( bind(f,"Hi")() == 1);
    errors += !( bind(f,_1_)("there.") == 1);
    errors += !( bind(f,"The answer is",_1_)(12) == 2);
    errors += !( bind(f,_1_,ref(value))("Really?") == 2);
    errors += !( bind(f,_1_,_2_)("Dunno. If there is an answer, it's",value) == 2);

    return !! errors;
}

