///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////

#pragma once

#include <EASTL/internal/config.h>
#include <EASTL/functional.h>
#include <EASTL/type_traits.h>
#include <EASTL/utility.h>

#include <EASTL/internal/tuple_fwd_decls.h>

EA_DISABLE_VC_WARNING(4623) // warning C4623: default constructor was implicitly defined as deleted
EA_DISABLE_VC_WARNING(4625) // warning C4625: copy constructor was implicitly defined as deleted
EA_DISABLE_VC_WARNING(4510) // warning C4510: default constructor could not be generated

#if EASTL_TUPLE_ENABLED

namespace eastl
{
// non-recursive tuple implementation based on libc++ tuple implementation and description at
// http://mitchnull.blogspot.ca/2012/06/c11-tuple-implementation-details-part-1.html

// TupleTypes helper
template <typename... Ts> struct TupleTypes {};

// tuple_size helper
template <typename T> class tuple_size {};
template <typename T> class tuple_size<const T>          : public tuple_size<T> {};
template <typename T> class tuple_size<volatile T>       : public tuple_size<T> {};
template <typename T> class tuple_size<const volatile T> : public tuple_size<T> {};

template <typename... Ts> class tuple_size<TupleTypes<Ts...>> : public integral_constant<size_t, sizeof...(Ts)> {};
template <typename... Ts> class tuple_size<tuple<Ts...>>      : public integral_constant<size_t, sizeof...(Ts)> {};

#if EASTL_VARIABLE_TEMPLATES_ENABLED
	template <class T>
	EA_CONSTEXPR size_t tuple_size_v = tuple_size<T>::value;
#endif

namespace Internal
{
	template <typename TupleIndices, typename... Ts>
	struct TupleImpl;
} // namespace Internal

template <typename Indices, typename... Ts>
class tuple_size<Internal::TupleImpl<Indices, Ts...>> : public integral_constant<size_t, sizeof...(Ts)>
{
};

// tuple_element helper to be able to isolate a type given an index
template <size_t I, typename T>
class tuple_element
{
};

template <size_t I>
class tuple_element<I, TupleTypes<>>
{
public:
	static_assert(I != I, "tuple_element index out of range");
};

template <typename H, typename... Ts>
class tuple_element<0, TupleTypes<H, Ts...>>
{
public:
	typedef H type;
};

template <size_t I, typename H, typename... Ts>
class tuple_element<I, TupleTypes<H, Ts...>>
{
public:
	typedef tuple_element_t<I - 1, TupleTypes<Ts...>> type;
};

// specialization for tuple
template <size_t I, typename... Ts>
class tuple_element<I, tuple<Ts...>>
{
public:
	typedef tuple_element_t<I, TupleTypes<Ts...>> type;
};

template <size_t I, typename... Ts>
class tuple_element<I, const tuple<Ts...>>
{
public:
	typedef typename add_const<tuple_element_t<I, TupleTypes<Ts...>>>::type type;
};

template <size_t I, typename... Ts>
class tuple_element<I, volatile tuple<Ts...>>
{
public:
	typedef typename add_volatile<tuple_element_t<I, TupleTypes<Ts...>>>::type type;
};

template <size_t I, typename... Ts>
class tuple_element<I, const volatile tuple<Ts...>>
{
public:
	typedef typename add_cv<tuple_element_t<I, TupleTypes<Ts...>>>::type type;
};

// specialization for TupleImpl
template <size_t I, typename Indices, typename... Ts>
class tuple_element<I, Internal::TupleImpl<Indices, Ts...>> : public tuple_element<I, tuple<Ts...>>
{
};

template <size_t I, typename Indices, typename... Ts>
class tuple_element<I, const Internal::TupleImpl<Indices, Ts...>> : public tuple_element<I, const tuple<Ts...>>
{
};

template <size_t I, typename Indices, typename... Ts>
class tuple_element<I, volatile Internal::TupleImpl<Indices, Ts...>> : public tuple_element<I, volatile tuple<Ts...>>
{
};

template <size_t I, typename Indices, typename... Ts>
class tuple_element<I, const volatile Internal::TupleImpl<Indices, Ts...>> : public tuple_element<
																				 I, const volatile tuple<Ts...>>
{
};

// attempt to isolate index given a type
template <typename T, typename Tuple>
struct tuple_index
{
};

template <typename T>
struct tuple_index<T, TupleTypes<>>
{
	typedef void DuplicateTypeCheck;
	tuple_index() = delete; // tuple_index should only be used for compile-time assistance, and never be instantiated
	static const size_t index = 0;
};

template <typename T, typename... TsRest>
struct tuple_index<T, TupleTypes<T, TsRest...>>
{
	typedef int DuplicateTypeCheck;
	// after finding type T in the list of types, try to find type T in TsRest.
	// If we stumble back into this version of tuple_index, i.e. type T appears twice in the list of types, then DuplicateTypeCheck will be of type int, and the static_assert will fail.
	// If we don't, then we'll go through the version of tuple_index above, where all of the types have been exhausted, and DuplicateTypeCheck will be void.
	static_assert(is_void<typename tuple_index<T, TupleTypes<TsRest...>>::DuplicateTypeCheck>::value, "duplicate type T in tuple_vector::get<T>(); unique types must be provided in declaration, or only use get<size_t>()");

	static const size_t index = 0;
};

template <typename T, typename TsHead, typename... TsRest>
struct tuple_index<T, TupleTypes<TsHead, TsRest...>>
{
	typedef typename tuple_index<T, TupleTypes<TsRest...>>::DuplicateTypeCheck DuplicateTypeCheck;
	static const size_t index = tuple_index<T, TupleTypes<TsRest...>>::index + 1;
};

template <typename T, typename Indices, typename... Ts>
struct tuple_index<T, Internal::TupleImpl<Indices, Ts...>> : public tuple_index<T, TupleTypes<Ts...>>
{
};


namespace Internal
{
	// swallow
	//
	// Provides a vessel to expand variadic packs.
	//
	template <typename... Ts>
	void swallow(Ts&&...) {}


	// TupleLeaf
	//
	template <size_t I, typename ValueType, bool IsEmpty = is_empty_v<ValueType>>
	class TupleLeaf;

	template <size_t I, typename ValueType, bool IsEmpty>
	inline void swap(TupleLeaf<I, ValueType, IsEmpty>& a, TupleLeaf<I, ValueType, IsEmpty>& b)
	{
		eastl::swap(a.getInternal(), b.getInternal());
	}

	template <size_t I, typename ValueType, bool IsEmpty>
	class TupleLeaf
	{
	public:
		TupleLeaf() : mValue() {}
		TupleLeaf(const TupleLeaf&) = default;
		TupleLeaf& operator=(const TupleLeaf&) = delete;

		// We shouldn't need this explicit constructor as it should be handled by the template below but OSX clang
		// is_constructible type trait incorrectly gives false for is_constructible<T&&, T&&>::value
		explicit TupleLeaf(ValueType&& v) : mValue(eastl::move(v)) {}

		template <typename T, typename = typename enable_if<is_constructible<ValueType, T&&>::value>::type>
		explicit TupleLeaf(T&& t)
			: mValue(eastl::forward<T>(t))
		{
		}

		template <typename T>
		explicit TupleLeaf(const TupleLeaf<I, T>& t)
			: mValue(t.getInternal())
		{
		}

		template <typename T>
		TupleLeaf& operator=(T&& t)
		{
			mValue = eastl::forward<T>(t);
			return *this;
		}

		int swap(TupleLeaf& t)
		{
			eastl::Internal::swap(*this, t);
			return 0;
		}

		ValueType& getInternal() { return mValue; }
		const ValueType& getInternal() const { return mValue; }

	private:
		ValueType mValue;  
	};

	// TupleLeaf: Specialize for when ValueType is a reference 
	template <size_t I, typename ValueType, bool IsEmpty>
	class TupleLeaf<I, ValueType&, IsEmpty>
	{
	public:
		TupleLeaf(const TupleLeaf&) = default;
		TupleLeaf& operator=(const TupleLeaf&) = delete;

		template <typename T, typename = typename enable_if<is_constructible<ValueType, T&&>::value>::type>
		explicit TupleLeaf(T&& t)
			: mValue(eastl::forward<T>(t))
		{
		}

		explicit TupleLeaf(ValueType& t) : mValue(t)
		{
		}

		template <typename T>
		explicit TupleLeaf(const TupleLeaf<I, T>& t)
			: mValue(t.getInternal())
		{
		}

		template <typename T>
		TupleLeaf& operator=(T&& t)
		{
			mValue = eastl::forward<T>(t);
			return *this;
		}

		int swap(TupleLeaf& t)
		{
			eastl::Internal::swap(*this, t);
			return 0;
		}

		ValueType& getInternal() { return mValue; }
		const ValueType& getInternal() const { return mValue; }

	private:
		ValueType& mValue;
	};

	// TupleLeaf: partial specialization for when we can use the Empty Base Class Optimization
	template <size_t I, typename ValueType>
	class TupleLeaf<I, ValueType, true> : private ValueType
	{
	public:
		// true_type / false_type constructors for case where ValueType is default constructible and should be value
		// initialized and case where it is not
		TupleLeaf(const TupleLeaf&) = default;

		template <typename T, typename = typename enable_if<is_constructible<ValueType, T&&>::value>::type>
		explicit TupleLeaf(T&& t)
			: ValueType(eastl::forward<T>(t))
		{
		}

		template <typename T>
		explicit TupleLeaf(const TupleLeaf<I, T>& t)
			: ValueType(t.getInternal())
		{
		}

		template <typename T>
		TupleLeaf& operator=(T&& t)
		{
			ValueType::operator=(eastl::forward<T>(t));
			return *this;
		}

		int swap(TupleLeaf& t)
		{
			eastl::Internal::swap(*this, t);
			return 0;
		}

		ValueType& getInternal() { return static_cast<ValueType&>(*this); }
		const ValueType& getInternal() const { return static_cast<const ValueType&>(*this); }

	private:
		TupleLeaf& operator=(const TupleLeaf&) = delete;
	};



	// MakeTupleTypes
	//
	//
	template <typename TupleTypes, typename Tuple, size_t Start, size_t End>
	struct MakeTupleTypesImpl;

	template <typename... Types, typename Tuple, size_t Start, size_t End>
	struct MakeTupleTypesImpl<TupleTypes<Types...>, Tuple, Start, End>
	{
		typedef typename remove_reference<Tuple>::type TupleType;
		typedef typename MakeTupleTypesImpl<
			TupleTypes<Types..., typename conditional<is_lvalue_reference<Tuple>::value,
													  // append ref if Tuple is ref
													  tuple_element_t<Start, TupleType>&,
													  // append non-ref otherwise
													  tuple_element_t<Start, TupleType>>::type>,
			Tuple, Start + 1, End>::type type;
	};

	template <typename... Types, typename Tuple, size_t End>
	struct MakeTupleTypesImpl<TupleTypes<Types...>, Tuple, End, End>
	{
		typedef TupleTypes<Types...> type;
	};

	template <typename Tuple>
	using MakeTupleTypes_t = typename MakeTupleTypesImpl<TupleTypes<>, Tuple, 0,
														 tuple_size<typename remove_reference<Tuple>::type>::value>::type;


	// TupleImpl
	//
	//
	template <size_t I, typename Indices, typename... Ts>
	tuple_element_t<I, TupleImpl<Indices, Ts...>>& get(TupleImpl<Indices, Ts...>& t);

	template <size_t I, typename Indices, typename... Ts>
	const_tuple_element_t<I, TupleImpl<Indices, Ts...>>& get(const TupleImpl<Indices, Ts...>& t);

	template <size_t I, typename Indices, typename... Ts>
	tuple_element_t<I, TupleImpl<Indices, Ts...>>&& get(TupleImpl<Indices, Ts...>&& t);

	template <typename T, typename Indices, typename... Ts>
	T& get(TupleImpl<Indices, Ts...>& t);

	template <typename T, typename Indices, typename... Ts>
	const T& get(const TupleImpl<Indices, Ts...>& t);

	template <typename T, typename Indices, typename... Ts>
	T&& get(TupleImpl<Indices, Ts...>&& t);

	template <size_t... Indices, typename... Ts>
	struct TupleImpl<integer_sequence<size_t, Indices...>, Ts...> : public TupleLeaf<Indices, Ts>...
	{
		EA_CONSTEXPR TupleImpl() = default;

		// index_sequence changed to integer_sequence due to issues described below in VS2015 CTP 6. 
		// https://connect.microsoft.com/VisualStudio/feedback/details/1126958/error-in-template-parameter-pack-expansion-of-std-index-sequence
		// 
		template <typename... Us, typename... ValueTypes>
		explicit TupleImpl(integer_sequence<size_t, Indices...>, TupleTypes<Us...>, ValueTypes&&... values)
			: TupleLeaf<Indices, Ts>(eastl::forward<ValueTypes>(values))...
		{
		}

		template <typename OtherTuple>
		TupleImpl(OtherTuple&& t)
			: TupleLeaf<Indices, Ts>(eastl::forward<tuple_element_t<Indices, MakeTupleTypes_t<OtherTuple>>>(get<Indices>(t)))...
		{
		}

		template <typename OtherTuple>
		TupleImpl& operator=(OtherTuple&& t)
		{
			swallow(TupleLeaf<Indices, Ts>::operator=(
						eastl::forward<tuple_element_t<Indices, MakeTupleTypes_t<OtherTuple>>>(get<Indices>(t)))...);
			return *this;
		}

		TupleImpl& operator=(const TupleImpl& t)
		{
			swallow(TupleLeaf<Indices, Ts>::operator=(static_cast<const TupleLeaf<Indices, Ts>&>(t).getInternal())...);
			return *this;
		}

		void swap(TupleImpl& t) { swallow(TupleLeaf<Indices, Ts>::swap(static_cast<TupleLeaf<Indices, Ts>&>(t))...); }
	};

	template <size_t I, typename Indices, typename... Ts>
	inline tuple_element_t<I, TupleImpl<Indices, Ts...>>& get(TupleImpl<Indices, Ts...>& t)
	{
		typedef tuple_element_t<I, TupleImpl<Indices, Ts...>> Type;
		return static_cast<Internal::TupleLeaf<I, Type>&>(t).getInternal();
	}

	template <size_t I, typename Indices, typename... Ts>
	inline const_tuple_element_t<I, TupleImpl<Indices, Ts...>>& get(const TupleImpl<Indices, Ts...>& t)
	{
		typedef tuple_element_t<I, TupleImpl<Indices, Ts...>> Type;
		return static_cast<const Internal::TupleLeaf<I, Type>&>(t).getInternal();
	}

	template <size_t I, typename Indices, typename... Ts>
	inline tuple_element_t<I, TupleImpl<Indices, Ts...>>&& get(TupleImpl<Indices, Ts...>&& t)
	{
		typedef tuple_element_t<I, TupleImpl<Indices, Ts...>> Type;
		return static_cast<Type&&>(static_cast<Internal::TupleLeaf<I, Type>&>(t).getInternal());
	}

	template <typename T, typename Indices, typename... Ts>
	inline T& get(TupleImpl<Indices, Ts...>& t)
	{
		typedef tuple_index<T, TupleImpl<Indices, Ts...>> Index;
		return static_cast<Internal::TupleLeaf<Index::index, T>&>(t).getInternal();
	}

	template <typename T, typename Indices, typename... Ts>
	inline const T& get(const TupleImpl<Indices, Ts...>& t)
	{
		typedef tuple_index<T, TupleImpl<Indices, Ts...>> Index;
		return static_cast<const Internal::TupleLeaf<Index::index, T>&>(t).getInternal();
	}

	template <typename T, typename Indices, typename... Ts>
	inline T&& get(TupleImpl<Indices, Ts...>&& t)
	{
		typedef tuple_index<T, TupleImpl<Indices, Ts...>> Index;
		return static_cast<T&&>(static_cast<Internal::TupleLeaf<Index::index, T>&>(t).getInternal());
	}


	// TupleLike
	//
	// type-trait that determines if a type is an eastl::tuple or an eastl::pair.
	//
	template <typename T> struct TupleLike                   : public false_type {};
	template <typename T> struct TupleLike<const T>          : public TupleLike<T> {};
	template <typename T> struct TupleLike<volatile T>       : public TupleLike<T> {};
	template <typename T> struct TupleLike<const volatile T> : public TupleLike<T> {};

	template <typename... Ts>
	struct TupleLike<tuple<Ts...>> : public true_type {};

	template <typename First, typename Second>
	struct TupleLike<eastl::pair<First, Second>> : public true_type {};


	// TupleConvertible
	//
	//
	//
	template <bool IsSameSize, typename From, typename To>
	struct TupleConvertibleImpl : public false_type
	{
	};

	template <typename... FromTypes, typename... ToTypes>
	struct TupleConvertibleImpl<true, TupleTypes<FromTypes...>,	TupleTypes<ToTypes...>>
		: public integral_constant<bool, conjunction<is_convertible<FromTypes, ToTypes>...>::value>
	{
	};

	template <typename From, typename To,
			  bool = TupleLike<typename remove_reference<From>::type>::value,
			  bool = TupleLike<typename remove_reference<To>::type>::value>
	struct TupleConvertible : public false_type
	{
	};

	template <typename From, typename To>
	struct TupleConvertible<From, To, true, true>
		: public TupleConvertibleImpl<tuple_size<typename remove_reference<From>::type>::value ==
				tuple_size<typename remove_reference<To>::type>::value,
				MakeTupleTypes_t<From>, MakeTupleTypes_t<To>>
	{
	};


	// TupleAssignable
	//
	//
	//
	template <bool IsSameSize, typename Target, typename From>
	struct TupleAssignableImpl : public false_type
	{
	};

	template <typename... TargetTypes, typename... FromTypes>
	struct TupleAssignableImpl<true, TupleTypes<TargetTypes...>, TupleTypes<FromTypes...>>
		: public bool_constant<conjunction<is_assignable<TargetTypes, FromTypes>...>::value>
	{
	};

	template <typename Target, typename From,
			  bool = TupleLike<typename remove_reference<Target>::type>::value,
			  bool = TupleLike<typename remove_reference<From>::type>::value>
	struct TupleAssignable : public false_type
	{
	};

	template <typename Target, typename From>
	struct TupleAssignable<Target, From, true, true>
		: public TupleAssignableImpl<
			tuple_size<typename remove_reference<Target>::type>::value ==
			tuple_size<typename remove_reference<From>::type>::value,
			MakeTupleTypes_t<Target>, MakeTupleTypes_t<From>>
	{
	};


	// TupleImplicitlyConvertible and TupleExplicitlyConvertible 
	//
	// helpers for constraining conditionally-explicit ctors
	//
	template <bool IsSameSize, typename TargetType, typename... FromTypes>
	struct TupleImplicitlyConvertibleImpl : public false_type
	{
	};


	template <typename... TargetTypes, typename... FromTypes>
	struct TupleImplicitlyConvertibleImpl<true, TupleTypes<TargetTypes...>, FromTypes...>
		: public conjunction<
		is_constructible<TargetTypes, FromTypes>...,
		is_convertible<FromTypes, TargetTypes>...>
	{
	};

	template <typename TargetTupleType, typename... FromTypes>
	struct TupleImplicitlyConvertible
		: public TupleImplicitlyConvertibleImpl<
		tuple_size<TargetTupleType>::value == sizeof...(FromTypes),
		MakeTupleTypes_t<TargetTupleType>, FromTypes...>::type
	{
	};

	template<typename TargetTupleType, typename... FromTypes>
	using TupleImplicitlyConvertible_t = enable_if_t<TupleImplicitlyConvertible<TargetTupleType, FromTypes...>::value, bool>;

	template <bool IsSameSize, typename TargetType, typename... FromTypes>
	struct TupleExplicitlyConvertibleImpl : public false_type
	{
	};

	template <typename... TargetTypes, typename... FromTypes>
	struct TupleExplicitlyConvertibleImpl<true, TupleTypes<TargetTypes...>, FromTypes...>
		: public conjunction<
			is_constructible<TargetTypes, FromTypes>...,
			negation<conjunction<is_convertible<FromTypes, TargetTypes>...>>>
	{
	};

	template <typename TargetTupleType, typename... FromTypes>
	struct TupleExplicitlyConvertible
		: public TupleExplicitlyConvertibleImpl<
		tuple_size<TargetTupleType>::value == sizeof...(FromTypes),
		MakeTupleTypes_t<TargetTupleType>, FromTypes...>::type
	{
	};

	template<typename TargetTupleType, typename... FromTypes>
	using TupleExplicitlyConvertible_t = enable_if_t<TupleExplicitlyConvertible<TargetTupleType, FromTypes...>::value, bool>;


	// TupleEqual
	//
	//
	//
	template <size_t I>
	struct TupleEqual
	{
		template <typename Tuple1, typename Tuple2>
		bool operator()(const Tuple1& t1, const Tuple2& t2)
		{
			static_assert(tuple_size<Tuple1>::value == tuple_size<Tuple2>::value, "comparing tuples of different sizes.");
			return TupleEqual<I - 1>()(t1, t2) && get<I - 1>(t1) == get<I - 1>(t2);
		}
	};

	template <>
	struct TupleEqual<0>
	{
		template <typename Tuple1, typename Tuple2>
		bool operator()(const Tuple1&, const Tuple2&)
		{
			return true;
		}
	};


	// TupleLess
	//
	//
	//
	template <size_t I>
	struct TupleLess
	{
		template <typename Tuple1, typename Tuple2>
		bool operator()(const Tuple1& t1, const Tuple2& t2)
		{
			static_assert(tuple_size<Tuple1>::value == tuple_size<Tuple2>::value, "comparing tuples of different sizes.");
			return TupleLess<I - 1>()(t1, t2) || (!TupleLess<I - 1>()(t2, t1) && get<I - 1>(t1) < get<I - 1>(t2));
		}
	};

	template <>
	struct TupleLess<0>
	{
		template <typename Tuple1, typename Tuple2>
		bool operator()(const Tuple1&, const Tuple2&)
		{
			return false;
		}
	};


	// MakeTupleReturnImpl
	//
	//
	//
	template <typename T> struct MakeTupleReturnImpl                       { typedef T type; };
	template <typename T> struct MakeTupleReturnImpl<reference_wrapper<T>> { typedef T& type; };

	template <typename T>
	using MakeTupleReturn_t = typename MakeTupleReturnImpl<decay_t<T>>::type;


	// tuple_cat helpers
	//
	//
	//

	// TupleCat2Impl
	template <typename Tuple1, typename Is1, typename Tuple2, typename Is2>
	struct TupleCat2Impl;

	template <typename... T1s, size_t... I1s, typename... T2s, size_t... I2s>
	struct TupleCat2Impl<tuple<T1s...>, index_sequence<I1s...>, tuple<T2s...>, index_sequence<I2s...>>
	{
		using ResultType = tuple<T1s..., T2s...>;

		template <typename Tuple1, typename Tuple2>
		static inline ResultType DoCat2(Tuple1&& t1, Tuple2&& t2)
		{
			return ResultType(get<I1s>(eastl::forward<Tuple1>(t1))..., get<I2s>(eastl::forward<Tuple2>(t2))...);
		}
	};

	// TupleCat2
	template <typename Tuple1, typename Tuple2>
	struct TupleCat2;

	template <typename... T1s, typename... T2s>
	struct TupleCat2<tuple<T1s...>, tuple<T2s...>>
	{
		using Is1        = make_index_sequence<sizeof...(T1s)>;
		using Is2        = make_index_sequence<sizeof...(T2s)>;
		using TCI        = TupleCat2Impl<tuple<T1s...>, Is1, tuple<T2s...>, Is2>;
		using ResultType = typename TCI::ResultType;

		template <typename Tuple1, typename Tuple2>
		static inline ResultType DoCat2(Tuple1&& t1, Tuple2&& t2)
		{
			return TCI::DoCat2(eastl::forward<Tuple1>(t1), eastl::forward<Tuple2>(t2));
		}
	};

	// TupleCat
	template <typename... Tuples>
	struct TupleCat;

	template <typename Tuple1, typename Tuple2, typename... TuplesRest>
	struct TupleCat<Tuple1, Tuple2, TuplesRest...>
	{
		using FirstResultType = typename TupleCat2<Tuple1, Tuple2>::ResultType;
		using ResultType      = typename TupleCat<FirstResultType, TuplesRest...>::ResultType;

		template <typename TupleArg1, typename TupleArg2, typename... TupleArgsRest>
		static inline ResultType DoCat(TupleArg1&& t1, TupleArg2&& t2, TupleArgsRest&&... ts)
		{
			return TupleCat<FirstResultType, TuplesRest...>::DoCat(
				TupleCat2<TupleArg1, TupleArg2>::DoCat2(eastl::forward<TupleArg1>(t1), eastl::forward<TupleArg2>(t2)),
				eastl::forward<TupleArgsRest>(ts)...);
		}
	};

	template <typename Tuple1, typename Tuple2>
	struct TupleCat<Tuple1, Tuple2>
	{
		using TC2 = TupleCat2<Tuple1, remove_reference_t<Tuple2>>;
		using ResultType = typename TC2::ResultType;

		template <typename TupleArg1, typename TupleArg2>
		static inline ResultType DoCat(TupleArg1&& t1, TupleArg2&& t2)
		{
			return TC2::DoCat2(eastl::forward<TupleArg1>(t1), eastl::forward<TupleArg2>(t2));
		}
	};
}  // namespace Internal



// tuple
//
// eastl::tuple is a fixed-size container of heterogeneous values. It is a
// generalization of eastl::pair which hold only two heterogeneous values.
//
// https://en.cppreference.com/w/cpp/utility/tuple
//
template <typename... Ts>
class tuple;

template <typename T, typename... Ts>
class tuple<T, Ts...>
{
public:
	EA_CONSTEXPR tuple() = default;
	
	template <typename T2 = T, 
		Internal::TupleImplicitlyConvertible_t<tuple, const T2&, const Ts&...> = 0>
	EA_CONSTEXPR tuple(const T& t, const Ts&... ts)
		: mImpl(make_index_sequence<sizeof...(Ts) + 1>{}, Internal::MakeTupleTypes_t<tuple>{}, t, ts...)
	{
	}

	template <typename T2 = T, 
		Internal::TupleExplicitlyConvertible_t<tuple, const T2&, const Ts&...> = 0>
	explicit EA_CONSTEXPR tuple(const T& t, const Ts&... ts)
		: mImpl(make_index_sequence<sizeof...(Ts) + 1>{}, Internal::MakeTupleTypes_t<tuple>{}, t, ts...)
	{
	}

	template <typename U, typename... Us,
		Internal::TupleImplicitlyConvertible_t<tuple, U, Us...> = 0>
		EA_CONSTEXPR tuple(U&& u, Us&&... us)
			: mImpl(make_index_sequence<sizeof...(Us) + 1>{}, Internal::MakeTupleTypes_t<tuple>{}, eastl::forward<U>(u),
					eastl::forward<Us>(us)...)
	{
	}

	template <typename U, typename... Us,
		Internal::TupleExplicitlyConvertible_t<tuple, U, Us...> = 0>
		explicit EA_CONSTEXPR tuple(U&& u, Us&&... us)
			: mImpl(make_index_sequence<sizeof...(Us) + 1>{}, Internal::MakeTupleTypes_t<tuple>{}, eastl::forward<U>(u),
					eastl::forward<Us>(us)...)
	{
	}

	template <typename OtherTuple,
			  typename enable_if<Internal::TupleConvertible<OtherTuple, tuple>::value, bool>::type = false>
	tuple(OtherTuple&& t)
		: mImpl(eastl::forward<OtherTuple>(t))
	{
	}

	template <typename OtherTuple,
			  typename enable_if<Internal::TupleAssignable<tuple, OtherTuple>::value, bool>::type = false>
	tuple& operator=(OtherTuple&& t)
	{
		mImpl.operator=(eastl::forward<OtherTuple>(t));
		return *this;
	}

	void swap(tuple& t) { mImpl.swap(t.mImpl); }

private:
	typedef Internal::TupleImpl<make_index_sequence<sizeof...(Ts) + 1>, T, Ts...> Impl;
	Impl mImpl;

	template <size_t I, typename... Ts_>
	friend tuple_element_t<I, tuple<Ts_...>>& get(tuple<Ts_...>& t);

	template <size_t I, typename... Ts_>
	friend const_tuple_element_t<I, tuple<Ts_...>>& get(const tuple<Ts_...>& t);

	template <size_t I, typename... Ts_>
	friend tuple_element_t<I, tuple<Ts_...>>&& get(tuple<Ts_...>&& t);

	template <typename T_, typename... ts_>
	friend T_& get(tuple<ts_...>& t);

	template <typename T_, typename... ts_>
	friend const T_& get(const tuple<ts_...>& t);

	template <typename T_, typename... ts_>
	friend T_&& get(tuple<ts_...>&& t);
};

// template specialization for an empty tuple
template <>
class tuple<>
{
public:
	void swap(tuple&) {}
};

template <size_t I, typename... Ts>
inline tuple_element_t<I, tuple<Ts...>>& get(tuple<Ts...>& t)
{
	return get<I>(t.mImpl);
}

template <size_t I, typename... Ts>
inline const_tuple_element_t<I, tuple<Ts...>>& get(const tuple<Ts...>& t)
{
	return get<I>(t.mImpl);
}

template <size_t I, typename... Ts>
inline tuple_element_t<I, tuple<Ts...>>&& get(tuple<Ts...>&& t)
{
	return get<I>(eastl::move(t.mImpl));
}

template <typename T, typename... Ts>
inline T& get(tuple<Ts...>& t)
{
	return get<T>(t.mImpl);
}

template <typename T, typename... Ts>
inline const T& get(const tuple<Ts...>& t)
{
	return get<T>(t.mImpl);
}

template <typename T, typename... Ts>
inline T&& get(tuple<Ts...>&& t)
{
	return get<T>(eastl::move(t.mImpl));
}

template <typename... Ts>
inline void swap(tuple<Ts...>& a, tuple<Ts...>& b)
{
	a.swap(b);
}


// tuple operators
//
//
template <typename... T1s, typename... T2s>
inline bool operator==(const tuple<T1s...>& t1, const tuple<T2s...>& t2)
{
	return Internal::TupleEqual<sizeof...(T1s)>()(t1, t2);
}

template <typename... T1s, typename... T2s>
inline bool operator<(const tuple<T1s...>& t1, const tuple<T2s...>& t2)
{
	return Internal::TupleLess<sizeof...(T1s)>()(t1, t2);
}

template <typename... T1s, typename... T2s> inline bool operator!=(const tuple<T1s...>& t1, const tuple<T2s...>& t2) { return !(t1 == t2); }
template <typename... T1s, typename... T2s> inline bool operator> (const tuple<T1s...>& t1, const tuple<T2s...>& t2) { return t2 < t1; }
template <typename... T1s, typename... T2s> inline bool operator<=(const tuple<T1s...>& t1, const tuple<T2s...>& t2) { return !(t2 < t1); }
template <typename... T1s, typename... T2s> inline bool operator>=(const tuple<T1s...>& t1, const tuple<T2s...>& t2) { return !(t1 < t2); }


// tuple_cat 
//
//
template <typename... Tuples>
inline typename Internal::TupleCat<Tuples...>::ResultType tuple_cat(Tuples&&... ts)
{
	return Internal::TupleCat<Tuples...>::DoCat(eastl::forward<Tuples>(ts)...);
}


// make_tuple 
//
//
template <typename... Ts>
inline EA_CONSTEXPR tuple<Internal::MakeTupleReturn_t<Ts>...> make_tuple(Ts&&... values)
{
	return tuple<Internal::MakeTupleReturn_t<Ts>...>(eastl::forward<Ts>(values)...);
}


// forward_as_tuple 
//
//
template <typename... Ts>
inline EA_CONSTEXPR tuple<Ts&&...> forward_as_tuple(Ts&&... ts) EA_NOEXCEPT
{
	return tuple<Ts&&...>(eastl::forward<Ts&&>(ts)...);
}


// ignore 
//
// An object of unspecified type such that any value can be assigned to it with no effect.
//
// https://en.cppreference.com/w/cpp/utility/tuple/ignore
//
namespace Internal 
{
	struct ignore_t
	{
		ignore_t() = default;

		template <typename T>
		const ignore_t& operator=(const T&) const
		{
			return *this;
		}
	};
}// namespace Internal

static const Internal::ignore_t ignore;


// tie 
//
// Creates a tuple of lvalue references to its arguments or instances of eastl::ignore.
//
// https://en.cppreference.com/w/cpp/utility/tuple/tie
//
template <typename... Ts>
inline EA_CONSTEXPR tuple<Ts&...> tie(Ts&... ts) EA_NOEXCEPT
{
	return tuple<Ts&...>(ts...);
}


// apply
//
// Invoke a callable object using a tuple to supply the arguments. 
//
// http://en.cppreference.com/w/cpp/utility/apply
//
namespace detail
{
	template <class F, class Tuple, size_t... I>
	EA_CONSTEXPR decltype(auto) apply_impl(F&& f, Tuple&& t, index_sequence<I...>)
	{
		return invoke(eastl::forward<F>(f), get<I>(eastl::forward<Tuple>(t))...);
	}
} // namespace detail

template <class F, class Tuple>
EA_CONSTEXPR decltype(auto) apply(F&& f, Tuple&& t)
{
	return detail::apply_impl(eastl::forward<F>(f), eastl::forward<Tuple>(t),
		                      make_index_sequence<tuple_size_v<remove_reference_t<Tuple>>>{});
}

}  // namespace eastl


///////////////////////////////////////////////////////////////
// C++17 structured bindings support for eastl::tuple
//
#ifndef EA_COMPILER_NO_STRUCTURED_BINDING
	#include <tuple>
	namespace std
	{
		// NOTE(rparolin): Some platform implementations didn't check the standard specification and implemented the
		// "tuple_size" and "tuple_element" primary template with as a struct.  The standard specifies they are
		// implemented with the class keyword so we provide the template specializations as a class and disable the
		// generated warning.
		EA_DISABLE_CLANG_WARNING(-Wmismatched-tags)

		template <class... Ts>
		class tuple_size<::eastl::tuple<Ts...>> : public ::eastl::integral_constant<size_t, sizeof...(Ts)>
		{
		};

		template <size_t I, class... Ts>
		class tuple_element<I, ::eastl::tuple<Ts...>> : public ::eastl::tuple_element<I, ::eastl::tuple<Ts...>>
		{
		};

		EA_RESTORE_CLANG_WARNING()
	}
#endif


#endif  // EASTL_TUPLE_ENABLED
EA_RESTORE_VC_WARNING()
EA_RESTORE_VC_WARNING()
EA_RESTORE_VC_WARNING()
