#ifndef INCLUDED_FNWRAP_
#define INCLUDED_FNWRAP_

#include <tuple>
#include <bobcat/typetrait>

namespace FBB
{

class FnWrap
{
    template <int idx, typename Tuple>
    struct Type
    {
        typedef typename std::tuple_element<idx, Tuple>::type type;
    };

    template <typename P>
    struct FunInfo;
    
    template <typename Ret, typename First, typename ... Par>
    struct FunInfo<Ret (*)(First, Par ...)>
    {
        typedef Ret     return_type;
        typedef First   first_type;
    };
        
    template <typename Ret, typename First, typename Second, typename ... Par>
    struct FunInfo<Ret (*)(First, Second, Par ...)>
    {
        typedef Ret     return_type;
        typedef First   first_type;
        typedef Second  second_type;
    };

    template <int size, typename Return, typename Tuple,
              typename Fun, typename Arg1, typename ... Params>
    struct FnWrap1: public FnWrap1<size - 1, Return, Tuple, 
                                   Fun, Arg1, 
                                        typename Type<size - 1, Tuple>::type, 
                                        Params ...>
    {
        typedef typename Type<size - 1, Tuple>::type TupleType;
    
        FnWrap1(Return &ret, Tuple &&tuple, 
                Fun fun, Arg1 &&arg, Params && ... params);
    };
        
    template <typename Return, typename Tuple, 
              typename Fun, typename Arg1, typename ... Params>
    struct FnWrap1<0, Return, Tuple, Fun, Arg1, Params ...>
    {
        FnWrap1(Return &ret, Tuple &&tuple, 
                Fun fun, Arg1 &&arg, Params && ... params);
    };
    
    template <int size, typename Tuple,
              typename Fun, typename Arg1, typename ... Params>
    struct FnWrap1<size, void, Tuple, Fun, Arg1, Params ...>:
        public FnWrap1<size - 1, void, Tuple, 
                       Fun, Arg1, typename Type<size - 1, Tuple>::type, 
                            Params ...>
    {
        typedef typename Type<size - 1, Tuple>::type TupleType;
    
        FnWrap1(Tuple &&tuple, Fun fun, Arg1 &&arg, Params && ... params);
    };
        
    template <typename Tuple, 
              typename Fun, typename Arg1, typename ... Params>
    struct FnWrap1<0, void, Tuple, Fun, Arg1, Params ...>
    {
        FnWrap1(Tuple &&tuple, Fun fun, Arg1 &&arg, Params && ... params);
    };
    
    template <int size, typename Return, typename Tuple,
              typename Fun, typename Arg1, typename Arg2, typename ... Params>
    struct FnWrap2: public FnWrap2<size - 1, Return, Tuple, 
                                   Fun, Arg1, Arg2, 
                                        typename Type<size - 1, Tuple>::type, 
                                        Params ...>
    {
        typedef typename Type<size - 1, Tuple>::type TupleType;
    
        inline FnWrap2(Return &ret, Tuple &&tuple, 
                    Fun fun, Arg1 &&arg1, Arg2 &&arg2, Params && ... params);
    };
    
    template <typename Return, typename Tuple, 
              typename Fun, typename Arg1, typename Arg2, typename ... Params>
    struct FnWrap2<0, Return, Tuple, Fun, Arg1, Arg2, Params ...>
    {
        inline FnWrap2(Return &ret, Tuple &&tuple, 
                    Fun fun, Arg1 &&arg1, Arg2 &&arg2, Params && ... params);
    };
    
    template <int size, typename Tuple,
              typename Fun, typename Arg1, typename Arg2, typename ... Params>
    struct FnWrap2<size, void, Tuple, Fun, Arg1, Arg2, Params ...>:
            public FnWrap2<size - 1, void, Tuple, 
                           Fun, Arg1, Arg2, 
                                typename Type<size - 1, Tuple>::type, 
                                Params ...>
    {
        typedef typename Type<size - 1, Tuple>::type TupleType;
    
        FnWrap2(Tuple &&tuple, 
                Fun fun, Arg1 &&arg1, Arg2 &&arg2, Params && ... params);
    };
    
    template <typename Tuple, 
              typename Fun, typename Arg1, typename Arg2, typename ... Params>
    struct FnWrap2<0, void, Tuple, Fun, Arg1, Arg2, Params ...>
    {
        FnWrap2(Tuple &&tuple, 
                Fun fun, Arg1 &&arg1, Arg2 &&arg2, Params && ... params);
    };

    public:
        template <int unary, typename Return, 
                  typename Fun, typename ... Params>
        class Wrapper
        {
            Fun d_fun;
            std::tuple<Params ...> d_tuple;
            
            public:
                typedef typename TypeTrait<Return>::Plain   result_type;
                typedef typename 
                        TypeTrait<typename FunInfo<Fun>::first_type>::Plain
                             first_argument_type;
        
                Wrapper(Fun fun, Params && ... params);
        
                template <typename Arg1>
                inline Return operator()(Arg1 &&arg1);
        };
        
        template <int unary, typename Fun, typename ... Params>
        class Wrapper<unary, void, Fun, Params ...>
        {
            Fun d_fun;
            std::tuple<Params ...> d_tuple;
            
            public:
                typedef void   result_type;
                typedef typename 
                    TypeTrait<typename FunInfo<Fun>::first_type>::Plain
                             first_argument_type;
        
                Wrapper(Fun fun, Params && ... params);
        
                template <typename Arg1>
                void operator()(Arg1 &&arg1);
        };
        
        template <typename Return, typename Fun, typename ... Params>
        class Wrapper<2, Return, Fun, Params ...>
        {
            Fun d_fun;
            std::tuple<Params ...> d_tuple;
            
            public:
                typedef typename TypeTrait<Return>::Plain   result_type;
                typedef typename 
                        TypeTrait<typename FunInfo<Fun>::first_type>::Plain
                             first_argument_type;
                typedef typename 
                        TypeTrait<typename FunInfo<Fun>::second_type>::Plain
                             second_argument_type;
        
                Wrapper(Fun fun, Params && ... params);
        
                template <typename Arg1, typename Arg2>
                Return operator()(Arg1 &&arg1, Arg2 &&arg2);
        };
        
        template <typename Fun, typename ... Params>
        class Wrapper<2, void, Fun, Params ...>
        {
            Fun d_fun;
            std::tuple<Params ...> d_tuple;
            
            public:
                typedef void   result_type;
                typedef typename 
                        TypeTrait<typename FunInfo<Fun>::first_type>::Plain
                             first_argument_type;
                typedef typename 
                        TypeTrait<typename FunInfo<Fun>::second_type>::Plain
                             second_argument_type;
        
                Wrapper(Fun fun, Params && ... params);
        
                template <typename Arg1>
                inline void operator()(Arg1 &&arg1);
        };

        template <typename Func, typename ... Param>
        static Wrapper<1, typename FunInfo<Func>::return_type, Func, Param ...> 
            unary(Func fun, Param && ... param);
    
        template <typename Func, typename ... Param>
        static Wrapper<2, typename FunInfo<Func>::return_type, Func, Param ...> 
            binary(Func fun, Param && ... param);
};

// IMPLEMENTATIONS NEXT

template <int size, typename Return, typename Tuple,
          typename Fun, typename Arg1, typename ... Params>
inline FnWrap::FnWrap1<size, Return, Tuple, Fun, Arg1, Params ... >::
FnWrap1(Return &ret, Tuple &&tuple, Fun fun, Arg1 &&arg, Params && ... params)
:
    FnWrap1<size - 1, Return, Tuple, 
                Fun, Arg1, typename Type<size - 1, Tuple>::type, 
                     Params ...>
    (
        ret,
        std::forward<Tuple>(tuple), 
        fun, std::forward<Arg1>(arg),
             std::forward<TupleType>(std::get<size - 1>(tuple)), 
             std::forward<Params>(params) ...
    )
{}

template <typename Return, typename Tuple, 
          typename Fun, typename Arg1, typename ... Params>
inline FnWrap::FnWrap1<0, Return, Tuple, Fun, Arg1, Params ...>::
FnWrap1(Return &ret, Tuple &&tuple, Fun fun, Arg1 &&arg, Params && ... params)
{
    ret = fun(std::forward<Arg1>(arg), std::forward<Params>(params) ...);
}

template <int size, typename Tuple,
          typename Fun, typename Arg1, typename ... Params>
inline FnWrap::FnWrap1<size, void, Tuple, Fun, Arg1, Params ...>::
FnWrap1(Tuple &&tuple, Fun fun, Arg1 &&arg, Params && ... params)
:
    FnWrap1<size - 1, void, Tuple, Fun, Arg1, TupleType, Params ...>
    (
        std::forward<Tuple>(tuple), 
        fun, std::forward<Arg1>(arg),
             std::forward<TupleType>(std::get<size - 1>(tuple)), 
             std::forward<Params>(params) ...
    )
{}

template <typename Tuple, typename Fun, typename Arg1, typename ... Params>
inline FnWrap::FnWrap1<0, void, Tuple, Fun, Arg1, Params ...>::
FnWrap1(Tuple &&tuple, Fun fun, Arg1 &&arg, Params && ... params)
{
    fun(std::forward<Arg1>(arg), std::forward<Params>(params) ...);
}

template <int size, typename Return, typename Tuple,
          typename Fun, typename Arg1, typename Arg2, typename ... Params>
inline FnWrap::FnWrap2<size, Return, Tuple, Fun, Arg1, Arg2, Params ...>::
FnWrap2(Return &ret, Tuple &&tuple, 
        Fun fun, Arg1 &&arg1, Arg2 &&arg2, Params && ... params)
:
    FnWrap2<size - 1, Return, Tuple, 
            Fun, Arg1, Arg2, TupleType, Params ...>
    (
        ret,
        std::forward<Tuple>(tuple), 
        fun, std::forward<Arg1>(arg1), std::forward<Arg2>(arg2),
             std::forward<TupleType>(std::get<size - 1>(tuple)), 
             std::forward<Params>(params) ...
    )
{}

template <typename Return, typename Tuple, 
          typename Fun, typename Arg1, typename Arg2, typename ... Params>
inline FnWrap::FnWrap2<0, Return, Tuple, Fun, Arg1, Arg2, Params ...>::
FnWrap2(Return &ret, Tuple &&tuple, 
        Fun fun, Arg1 &&arg1, Arg2 &&arg2, Params && ... params)
{
    ret =
        fun(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), 
            std::forward<Params>(params) ...);
}

template <int size, typename Tuple,
          typename Fun, typename Arg1, typename Arg2, typename ... Params>
inline FnWrap::FnWrap2<size, void, Tuple, Fun, Arg1, Arg2, Params ...>::
FnWrap2(Tuple &&tuple, 
        Fun fun, Arg1 &&arg1, Arg2 &&arg2, Params && ... params)
:
    FnWrap2<size - 1, void, Tuple, 
            Fun, Arg1, Arg2, TupleType, Params ...>
    (
        std::forward<Tuple>(tuple), 
        fun, std::forward<Arg1>(arg1), std::forward<Arg2>(arg2),
             std::forward<TupleType>(std::get<size - 1>(tuple)), 
             std::forward<Params>(params) ...
    )
{}

template <typename Tuple, 
          typename Fun, typename Arg1, typename Arg2, typename ... Params>
inline FnWrap::FnWrap2<0, void, Tuple, Fun, Arg1, Arg2, Params ...>::
FnWrap2(Tuple &&tuple, 
        Fun fun, Arg1 &&arg1, Arg2 &&arg2, Params && ... params)
{
    fun(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), 
        std::forward<Params>(params) ...);
}

template <int unary, typename Return, typename Fun, typename ... Params>
inline FnWrap::Wrapper<unary, Return, Fun, Params ...>::
Wrapper(Fun fun, Params && ... params)
:
    d_fun(fun),
    d_tuple(std::forward<Params>(params) ...)
{}

template <int unary, typename Return, typename Fun, typename ... Params>
template <typename Arg1>
inline Return FnWrap::Wrapper<unary, Return, Fun, Params ...>::
operator()(Arg1 &&arg1)
{
    Return ret;
    FnWrap1<std::tuple_size<std::tuple<Params ...>>::value, 
            Return &, std::tuple<Params ...>, 
            Fun, Arg1>
        (ret, std::move(d_tuple), d_fun, arg1);

    return ret;
}

template <int unary, typename Fun, typename ... Params>
FnWrap::Wrapper<unary, void, Fun, Params ...>::
Wrapper(Fun fun, Params && ... params)
:
    d_fun(fun),
    d_tuple(std::forward<Params>(params) ...)
{}

template <int unary, typename Fun, typename ... Params>
template <typename Arg1>
inline void FnWrap::Wrapper<unary, void, Fun, Params ...>::
operator()(Arg1 &&arg1)
{
    FnWrap1< std::tuple_size<std::tuple<Params ...>>::value, 
            void, std::tuple<Params ...>, 
            Fun, Arg1>
        (std::move(d_tuple), d_fun, arg1);
}

template <typename Return, typename Fun, typename ... Params>
FnWrap::Wrapper<2, Return, Fun, Params ...>::
Wrapper(Fun fun, Params && ... params)
:
    d_fun(fun),
    d_tuple(std::forward<Params>(params) ...)
{}

template <typename Return, typename Fun, typename ... Params>
template <typename Arg1, typename Arg2>
inline Return FnWrap::Wrapper<2, Return, Fun, Params ...>::
operator()(Arg1 &&arg1, Arg2 &&arg2)
{
    Return ret;
    FnWrap2<std::tuple_size<std::tuple<Params ...>>::value, 
                Return &, std::tuple<Params ...>, 
                Fun, Arg1, Arg2>
            (ret, std::move(d_tuple), d_fun, arg1, arg2);
    return ret;
}

template <typename Fun, typename ... Params>
FnWrap::Wrapper<2, void, Fun, Params ...>::
Wrapper(Fun fun, Params && ... params)
:
    d_fun(fun),
    d_tuple(std::forward<Params>(params) ...)
{}

template <typename Fun, typename ... Params>
template <typename Arg1>
inline void FnWrap::Wrapper<2, void, Fun, Params ...>::
operator()(Arg1 &&arg1)
{
    FnWrap1<std::tuple_size<std::tuple<Params ...>>::value, 
                void, std::tuple<Params ...>, 
                Fun, Arg1>
            (std::move(d_tuple), d_fun, arg1);
}

template <typename Func, typename ... Param>
FnWrap::Wrapper<1, typename FnWrap::FunInfo<Func>::return_type, Func, 
                                                                    Param ...> 
FnWrap::unary(Func fun, Param && ... param)
{
    Wrapper<1, typename FunInfo<Func>::return_type, Func, 
                                                    Param ...> 
         wrap(fun, std::forward<Param>(param) ... );
    return wrap;
}

template <typename Func, typename ... Param>
FnWrap::Wrapper<2, typename FnWrap::FunInfo<Func>::return_type, Func, 
                                                                    Param ...> 
FnWrap::binary(Func fun, Param && ... param)
{
    Wrapper<2, typename FunInfo<Func>::return_type, Func, 
                                                    Param ...> 
         wrap(fun, std::forward<Param>(param) ... );
    return wrap;
}

} // FBB

#endif
