// Egtra 2011-12-28
// License: NYSL 0.9982 ( http://www.kmonos.net/nysl/ )

#define BOOST_RESULT_OF_USE_DECLTYPE
#include <tuple>
#include <iostream>
#include <boost/fusion/functional/invocation/invoke.hpp>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/empty.hpp>
#include <boost/fusion/adapted/mpl.hpp>
#include <boost/fusion/algorithm/transformation/transform.hpp>
#include <boost/type_traits.hpp>
#include <boost/fusion/sequence/intrinsic/front.hpp>
#include <boost/fusion/algorithm/transformation/pop_front.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/fusion/container/generation/make_list.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/container/list/convert.hpp>

namespace fusion = boost::fusion;
namespace mpl = boost::mpl;

namespace egtra
{

	namespace detail
	{
		template<int i> struct placeholder {};
	}
	namespace placeholders
	{
		detail::placeholder<1> const _1;
		detail::placeholder<2> const _2;
		detail::placeholder<3> const _3;
	}

	namespace detail
	{
		// http://cpplover.blogspot.com/2011/04/c0xenableif.html
		extern void* enabler;

		template<typename Functor, typename... Args>
		class binder
		{
		public:
			explicit binder(Functor f, Args... args)
				: m_f(f), m_args(args...) {}
			binder(binder const&) = default;
			binder(binder&&) = default;
			binder& operator =(binder const&) = default;
			binder& operator =(binder&&) = default;

		private:
			template<typename Old, typename New>
			struct replace
			{
				New operator ()(Old const&) const
				{
					return newValue;
				}
				template<typename T>
				T operator ()(T x) const
				{
					return x;
				}
				New newValue;
			};

			template<typename ArgsSequence>
			struct invoke0_result :
				fusion::result_of::invoke<Functor, ArgsSequence>
			{};

			template<std::size_t N, typename ArgsSequence, typename Args2Sequence>
			struct invokeN_result :
				mpl::eval_if<
					typename mpl::empty<Args2Sequence>::type,
					invoke0_result<ArgsSequence>,
					invokeN_result<
						N + 1,
						typename fusion::result_of::transform<
							ArgsSequence,
							replace<
								placeholder<N>,
								typename boost::remove_reference<
									typename fusion::result_of::front<Args2Sequence>::type
								>::type
							>
						>::type,
						typename fusion::result_of::pop_front<Args2Sequence>::type
					>
				>
			{};

			template<std::size_t N, typename ArgsSequence, typename Args2Sequence, typename boost::enable_if<typename mpl::empty<Args2Sequence>::type>::type*& = enabler>
			typename invoke0_result<ArgsSequence>::type
			invokeN(ArgsSequence const& s, Args2Sequence const&) const
			{
				return fusion::invoke(m_f, s);
			}

			template<std::size_t N, typename ArgsSequence, typename Args2Sequence, typename boost::disable_if<typename mpl::empty<Args2Sequence>::type>::type*& = enabler>
			typename invokeN_result<N, ArgsSequence, Args2Sequence>::type
			invokeN(ArgsSequence const& s, Args2Sequence const& args2) const
			{
				typedef typename boost::remove_reference<typename fusion::result_of::front<Args2Sequence>::type>::type front_type;
				return this->invokeN<N + 1>(
					fusion::transform(s, replace<placeholder<N>, front_type>{fusion::front(args2)}),
					fusion::pop_front(args2));
			}

		public:
			template<typename... Args2>
			typename invokeN_result<1, std::tuple<Args...>, typename fusion::result_of::as_list<std::tuple<Args2...>>::type>::type
//			invokeN_result<1, std::tuple<Args...>, typename fusion::result_of::make_list<Args2...>::type>::type // GCC 4.6.2 is not supported
			operator ()(Args2... args2) const
			{
				return this->invokeN<1>(m_args, fusion::make_list(args2...));
			}
		private:
			Functor m_f;
			std::tuple<Args...> m_args;
		};
	}

	template<typename Functor, typename... Args>
	detail::binder<Functor, Args...> bind(Functor&& f, Args&&... args)
	{
		return detail::binder<Functor, Args...>{
			f,
			args...,
		};
	}
}

int main()
{
	using namespace egtra::placeholders;
	std::cout << egtra::bind(std::plus<int>(), 1, 2)() << std::endl;
	std::cout << egtra::bind(std::plus<int>(), 1, _1)(3) << std::endl;
	std::cout << egtra::bind(std::plus<int>(), _1, _2)(3, 4) << std::endl;
	std::cout << egtra::bind([](int x, int y, int z) {return x + y + z;}, _1, _2, _3)(1, 2, 3) << std::endl;
}

