/*=============================================================================
  Copyright (c) 2011 RiSK (sscrisk)
  https://github.com/sscrisk/CEL---ConstExpr-Library

  Copyright (c) 2011-2017 Bolero MURAKAMI
  https://github.com/bolero-MURAKAMI/Sprout

  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)
=============================================================================*/
#ifndef SPROUT_FUNCTIONAL_BIND1ST_HPP
#define SPROUT_FUNCTIONAL_BIND1ST_HPP

#include <type_traits>
#include <utility>
#include <sprout/config.hpp>
#include <sprout/functional/base.hpp>
#include <sprout/functional/type_traits/is_strict_function.hpp>

namespace sprout {

	// D.9.1 Class template binder1st
	namespace detail {
		template<typename Fn, typename T = void, typename = void>
		class binder1st;
		template<typename Fn, typename T>
		class binder1st<
			Fn, T,
			typename std::enable_if<sprout::is_strict_binary_function<Fn>::value>::type
		>
			: public sprout::unary_function<typename Fn::second_argument_type, typename Fn::result_type>
		{
		public:
			typedef typename std::conditional<
				std::is_void<T>::value,
				typename Fn::first_argument_type,
				T
			>::type value_type;
		protected:
			Fn op;
			value_type value;
		public:
			SPROUT_CONSTEXPR binder1st(Fn const& x, value_type const& y)
				: op(x), value(y)
			{}
			SPROUT_CONSTEXPR typename Fn::result_type
			operator()(typename Fn::second_argument_type const& x) const {
				return op(value, x);
			}
		};
		template<typename Fn, typename T>
		class binder1st<
			Fn, T,
			typename std::enable_if<!sprout::is_strict_binary_function<Fn>::value>::type
		> {
		public:
			typedef T value_type;
		protected:
			Fn op;
			value_type value;
		public:
			SPROUT_CONSTEXPR binder1st(Fn const& x, value_type const& y)
				: op(x), value(y)
			{}
			template<typename U>
			SPROUT_CONSTEXPR decltype(std::declval<Fn const&>()(std::declval<value_type const&>(), std::declval<U const&>()))
			operator()(U const& x) const {
				return op(value, x);
			}
		};
	}	// namespace detail
	template<typename Fn, typename T = void>
	class binder1st
		: public sprout::detail::binder1st<Fn, T>
	{
	public:
		typedef typename sprout::detail::binder1st<Fn, T>::value_type value_type;
	public:
		SPROUT_CONSTEXPR binder1st(Fn const& x, value_type const& y)
			: sprout::detail::binder1st<Fn, T>(x, y)
		{}
	};

	// D.9.2 bind1st
	template<typename Fn, typename T>
	inline SPROUT_CONSTEXPR sprout::binder1st<Fn, T>
	bind1st(Fn const& fn, T const& x) {
		return sprout::binder1st<Fn, T>(fn, typename sprout::binder1st<Fn, T>::value_type(x));
	}
}	// namespace sprout

#endif	// #ifndef SPROUT_FUNCTIONAL_BIND1ST_HPP