#pragma once

#include "CoreTypes.h"
#include "Templates/Tuple.h"
#include "Templates/Utility.h"
#include "Memory/PointerTraits.h"
#include "Templates/Noncopyable.h"
#include "TypeTraits/TypeTraits.h"

NAMESPACE_REDCRAFT_BEGIN
NAMESPACE_MODULE_BEGIN(Redcraft)
NAMESPACE_MODULE_BEGIN(Utility)

NAMESPACE_PRIVATE_BEGIN

template <typename RT, typename ST>
using TRawPointer = TConditional<CVoid<RT>, typename TPointerTraits<TRemoveCVRef<ST>>::FElementType*, RT>;

template <bool bEnableInput, typename RT, typename ST, typename... Ts>
class FInOutPtr final : private FSingleton
{
public:

	explicit FInOutPtr(ST& InPtr, Ts&&... Args) requires (!bEnableInput)
		: SPointer(InPtr), Tuple(nullptr, Forward<Ts>(Args)...)
	{ }

	explicit FInOutPtr(ST& InPtr, Ts&&... Args) requires (bEnableInput)
		: SPointer(InPtr), Tuple(InPtr.Release(), Forward<Ts>(Args)...)
	{ }

	~FInOutPtr()
	{
		if constexpr (requires(RT* RPtr, ST& SPtr, Ts&&... Args) { SPtr.Reset(RPtr, Forward<Ts>(Args)...); })
		{
			Tuple.Apply([this](Ts&&... Args) { SPointer.Reset(Forward<Ts>(Args)...); });
		}
		else if constexpr (CConstructibleFrom<ST, RT, Ts...> && CMoveAssignable<ST>)
		{
			SPointer = Tuple.template Construct<ST>();
		}
		else check_no_entry();
	}

	operator    RT*()                                { return &Tuple.First; }
	operator void**() requires (!CSameAs<void*, RT>) { return &Tuple.First; }

private:

	ST& SPointer;
	TTuple< RT, Ts&&...> Tuple;

};

NAMESPACE_PRIVATE_END

template <typename RT = void, typename ST, typename... Ts> requires ((CVoid<RT>) || (CPointer<RT>)
	&& (requires(NAMESPACE_PRIVATE::TRawPointer<RT, ST>* RPtr, ST& SPtr, Ts&&... Args) { SPtr.Reset(RPtr, Forward<Ts>(Args)...); })
	|| (CConstructibleFrom<ST, NAMESPACE_PRIVATE::TRawPointer<RT, ST>, Ts...> && CMoveAssignable<ST>)
	&& requires { typename TPointerTraits<TRemoveCV<ST>>::FElementType; })
auto OutPtr(ST& InPtr, Ts&&... Args)
{
	return NAMESPACE_PRIVATE::FInOutPtr<false, NAMESPACE_PRIVATE::TRawPointer<RT, ST>, ST, Ts...>(InPtr, Forward<Ts>(Args)...);
}

template <typename RT = void, typename ST, typename... Ts> requires ((CVoid<RT>) || (CPointer<RT>)
	&& (requires(NAMESPACE_PRIVATE::TRawPointer<RT, ST>* RPtr, ST& SPtr, Ts&&... Args) { SPtr.Reset(RPtr, Forward<Ts>(Args)...); })
	|| (CConstructibleFrom<ST, NAMESPACE_PRIVATE::TRawPointer<RT, ST>, Ts...> && CMoveAssignable<ST>)
	&& requires(ST& SPtr) { { SPtr.Release() } -> CConvertibleTo<NAMESPACE_PRIVATE::TRawPointer<RT, ST>>; }
	&& requires { typename TPointerTraits<TRemoveCV<ST>>::FElementType; })
auto InOutPtr(ST& InPtr, Ts&&... Args)
{
	return NAMESPACE_PRIVATE::FInOutPtr<true, NAMESPACE_PRIVATE::TRawPointer<RT, ST>, ST, Ts...>(InPtr, Forward<Ts>(Args)...);
}

NAMESPACE_MODULE_END(Utility)
NAMESPACE_MODULE_END(Redcraft)
NAMESPACE_REDCRAFT_END