#ifndef BOOST_LEAF_DETAIL_FUNCTION_TRAITS_HPP_INCLUDED
#define BOOST_LEAF_DETAIL_FUNCTION_TRAITS_HPP_INCLUDED

// Copyright (c) 2018-2020 Emil Dotchevski and Reverge Studios, Inc.

// 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)

#if defined(__clang__)
#	pragma clang system_header
#elif (__GNUC__*100+__GNUC_MINOR__>301) && !defined(BOOST_LEAF_ENABLE_WARNINGS)
#	pragma GCC system_header
#elif defined(_MSC_VER) && !defined(BOOST_LEAF_ENABLE_WARNINGS)
#	pragma warning(push,1)
#endif

#include <boost/leaf/detail/mp11.hpp>
#include <tuple>

namespace boost { namespace leaf {

	namespace leaf_detail
	{
		template<class...>
		struct gcc49_workaround //Thanks Glen Fernandes
		{
			typedef void type;
		};

		template<class... T>
		using void_t = typename gcc49_workaround<T...>::type;

		template<class F,class V=void>
		struct function_traits
		{
			constexpr static int arity = -1;
		};

		template<class F>
		struct function_traits<F, void_t<decltype(&F::operator())>>
		{
		private:

			using tr = function_traits<decltype(&F::operator())>;

		public:

			using return_type = typename tr::return_type;
			static constexpr int arity = tr::arity - 1;

			using mp_args = typename leaf_detail_mp11::mp_rest<typename tr::mp_args>;

			template <int I>
			struct arg:
				tr::template arg<I+1>
			{
			};
		};

		template<class R, class... A>
		struct function_traits<R(A...)>
		{
			using return_type = R;
			static constexpr int arity = sizeof...(A);

			using mp_args = leaf_detail_mp11::mp_list<A...>;

			template <int I>
			struct arg
			{
				static_assert(I < arity, "I out of range");
				using type = typename std::tuple_element<I,std::tuple<A...>>::type;
			};
		};

		template<class F> struct function_traits<F&> : function_traits<F> { };
		template<class F> struct function_traits<F&&> : function_traits<F> { };
		template<class R, class... A> struct function_traits<R(*)(A...)> : function_traits<R(A...)> { };
		template<class R, class... A> struct function_traits<R(* &)(A...)> : function_traits<R(A...)> { };
		template<class R, class... A> struct function_traits<R(* const &)(A...)> : function_traits<R(A...)> { };
		template<class C, class R, class... A> struct function_traits<R(C::*)(A...)> : function_traits<R(C&,A...)> { };
		template<class C, class R, class... A> struct function_traits<R(C::*)(A...) const> : function_traits<R(C const &,A...)> { };
		template<class C, class R> struct function_traits<R(C::*)> : function_traits<R(C&)> { };

		template <class F>
		using fn_return_type = typename function_traits<F>::return_type;

		template <class F, int I>
		using fn_arg_type = typename function_traits<F>::template arg<I>::type;

		template <class F>
		using fn_mp_args = typename function_traits<F>::mp_args;
	} // namespace leaf_detail

} }

#endif
