#pragma once

#include "CoreTypes.h"
#include "Range/Utility.h"
#include "Templates/Tuple.h"
#include "Templates/Invoke.h"
#include "Templates/Utility.h"
#include "TypeTraits/TypeTraits.h"

NAMESPACE_REDCRAFT_BEGIN
NAMESPACE_MODULE_BEGIN(Redcraft)
NAMESPACE_MODULE_BEGIN(Utility)

NAMESPACE_BEGIN(Range)

/**
 * An interface class template for defining a range adaptor closure.
 * When the derived class has a unary operator() with range as a reference and is not itself a range,
 * the derived class is the range adaptor closure type and its objects can participate in pipe operations.
 * Specify, the unary operator() with any reference qualifier or cv-qualifier must be defined and has same effect.
 * Not directly instantiable.
 */
template <CObject D> requires (CSameAs<D, TRemoveCV<D>>)
class IAdaptorClosure { };

/** An adaptor closure helper that wraps a callable object. */
template <CDefaultConstructible F, CMoveConstructible... Ts> requires (CEmpty<F> && ... && CSameAs<TDecay<Ts>, Ts>)
class TAdaptorClosure : public IAdaptorClosure<TAdaptorClosure<F, Ts...>>
{
public:

	template <typename... Us> requires (CConstructibleFrom<TTuple<Ts...>, Us...> && ... && CSameAs<TDecay<Us>, Ts>)
	FORCEINLINE constexpr explicit TAdaptorClosure(Us&&... InArgs) : Args(Forward<Us>(InArgs)...) { }

	template <typename R> requires (CInvocable<F, R, Ts&...>)
	NODISCARD FORCEINLINE constexpr auto operator()(R&& Range) &
	{
		return [this, &Range]<size_t... Indices>(TIndexSequence<Indices...>)
		{
			return Invoke(F(), Forward<R>(Range), Args.template GetValue<Indices>()...);
		}
		(TMakeIndexSequence<sizeof...(Ts)>());
	}

	template <typename R> requires (CInvocable<F, R, const Ts&...>)
	NODISCARD FORCEINLINE constexpr auto operator()(R&& Range) const&
	{
		return [this, &Range]<size_t... Indices>(TIndexSequence<Indices...>)
		{
			return Invoke(F(), Forward<R>(Range), Args.template GetValue<Indices>()...);
		}
		(TMakeIndexSequence<sizeof...(Ts)>());
	}

	template <typename R> requires (CInvocable<F, R, Ts&&...>)
	NODISCARD FORCEINLINE constexpr auto operator()(R&& Range) &&
	{
		return [this, &Range]<size_t... Indices>(TIndexSequence<Indices...>)
		{
			return Invoke(F(), Forward<R>(Range), MoveTemp(Args).template GetValue<Indices>()...);
		}
		(TMakeIndexSequence<sizeof...(Ts)>());
	}

	template <typename R> requires (CInvocable<F, R, const Ts&&...>)
	NODISCARD FORCEINLINE constexpr auto operator()(R&& Range) const&&
	{
		return [this, &Range]<size_t... Indices>(TIndexSequence<Indices...>)
		{
			return Invoke(F(), Forward<R>(Range), MoveTemp(Args).template GetValue<Indices>()...);
		}
		(TMakeIndexSequence<sizeof...(Ts)>());
	}

private:

	NO_UNIQUE_ADDRESS TTuple<Ts...> Args;

};

/** A pipe closure that wraps two adaptor closures. */
template <CMoveConstructible T, CMoveConstructible U>
	requires (CSameAs<TRemoveCVRef<T>, T> && CDerivedFrom<T, IAdaptorClosure<T>>
	       && CSameAs<TRemoveCVRef<U>, U> && CDerivedFrom<U, IAdaptorClosure<U>>)
class TPipeClosure final : public IAdaptorClosure<TPipeClosure<T, U>>
{
public:

	template <typename V, typename W>
		requires (CSameAs<TRemoveCVRef<V>, T> && CConstructibleFrom<T, V>
		       && CSameAs<TRemoveCVRef<W>, U> && CConstructibleFrom<U, W>)
	FORCEINLINE constexpr explicit TPipeClosure(V InLHS, W InRHS)
		: LHS(Forward<V>(InLHS)), RHS(Forward<W>(InRHS))
	{ }

	template <typename R> requires (CInvocable<T&, R> && CInvocable<U&, TInvokeResult<T&, R>>)
	NODISCARD FORCEINLINE constexpr auto operator()(R&& Range) &
	{
		return Forward<R>(Range) | LHS | RHS;
	}

	template <typename R> requires (CInvocable<const T&, R> && CInvocable<const U&, TInvokeResult<const T&, R>>)
	NODISCARD FORCEINLINE constexpr auto operator()(R&& Range) const&
	{
		return Forward<R>(Range) | LHS | RHS;
	}

	template <typename R> requires (CInvocable<T&&, R> && CInvocable<U&&, TInvokeResult<T&&, R>>)
	NODISCARD FORCEINLINE constexpr auto operator()(R&& Range) &&
	{
		return Forward<R>(Range) | MoveTemp(LHS) | MoveTemp(RHS);
	}

	template <typename R> requires (CInvocable<const T&&, R> && CInvocable<const U&&, TInvokeResult<const T&&, R>>)
	NODISCARD FORCEINLINE constexpr auto operator()(R&& Range) const&&
	{
		return Forward<R>(Range) | MoveTemp(LHS) | MoveTemp(RHS);
	}

private:

	NO_UNIQUE_ADDRESS T LHS;
	NO_UNIQUE_ADDRESS U RHS;
};

/** Apply the range adaptor closure to the range. */
template <CRange R, CInvocable<R> T> requires (CDerivedFrom<TRemoveCVRef<T>, IAdaptorClosure<TRemoveCVRef<T>>>)
NODISCARD FORCEINLINE constexpr auto operator|(R&& Range, T&& Closure)
{
	return Invoke(Forward<T>(Closure), Forward<R>(Range));
}

/** Create a pipe closure that wraps two adaptor closures. */
template <CMoveConstructible T, CMoveConstructible U>
	requires (CDerivedFrom<TRemoveCVRef<T>, IAdaptorClosure<TRemoveCVRef<T>>>
	       && CDerivedFrom<TRemoveCVRef<U>, IAdaptorClosure<TRemoveCVRef<U>>>)
NODISCARD FORCEINLINE constexpr auto operator|(T&& LHS, U&& RHS)
{
	return TPipeClosure<TRemoveCVRef<T>, TRemoveCVRef<U>>(Forward<T>(LHS), Forward<U>(RHS));
}

NAMESPACE_END(Range)

NAMESPACE_MODULE_END(Utility)
NAMESPACE_MODULE_END(Redcraft)
NAMESPACE_REDCRAFT_END