#pragma once

#include "CoreTypes.h"
#include "Iterator/Utility.h"
#include "TypeTraits/TypeTraits.h"
#include "Templates/Noncopyable.h"
#include "Templates/Utility.h"

NAMESPACE_REDCRAFT_BEGIN
NAMESPACE_MODULE_BEGIN(Redcraft)
NAMESPACE_MODULE_BEGIN(Utility)

NAMESPACE_PRIVATE_BEGIN

template <typename F> class TInsertProxy;
template <typename F> class TPostIncrementProxy;
template <typename F> class TInsertIterator;

template <typename F>
class TInsertProxy final : private FSingleton
{
public:

#	if	DO_CHECK
	FORCEINLINE ~TInsertProxy() { checkf(bIsProduced, TEXT("Exception insert, Ensures that the value is assigned to the inserter.")); }
#	endif

	template <typename T> requires (CInvocable<F, T>)
	FORCEINLINE constexpr void operator=(T&& InValue) const
	{
		checkf(!bIsProduced, TEXT("Exception insert, Ensure that no multiple values are assigned to the inserter."));
		Invoke(Iter.Storage, Forward<T>(InValue));
		check_code({ bIsProduced = true; });
	}

private:

	TInsertIterator<F>& Iter;

#	if	DO_CHECK
	mutable bool bIsProduced;
#	endif

	FORCEINLINE constexpr TInsertProxy(TInsertIterator<F>& InIter) : Iter(InIter) { check_code({ bIsProduced = false; }); }

	template <typename> friend class TPostIncrementProxy;
	template <typename> friend class TInsertIterator;
};

static_assert(CAssignableFrom<TInsertProxy<void(*)(int)>, int>);

template <typename F>
class TPostIncrementProxy : private FSingleton
{
public:

#	if	DO_CHECK
	FORCEINLINE ~TPostIncrementProxy() { checkf(bIsProduced, TEXT("Exception insert, Ensures that the value is assigned to the inserter.")); }
#	endif

	NODISCARD FORCEINLINE constexpr TInsertProxy<F> operator*() const
	{
		checkf(!bIsProduced, TEXT("Exception insert, Ensure that no multiple values are assigned to the inserter."));
		check_code({ bIsProduced = true; });
		return TInsertProxy(Iter);
	}

private:

	TInsertIterator<F>& Iter;

#	if	DO_CHECK
	mutable bool bIsProduced;
#	endif

	FORCEINLINE constexpr TPostIncrementProxy(TInsertIterator<F>& InIter) : Iter(InIter) { check_code({ bIsProduced = false; }); }

	template <typename> friend class TInsertProxy;
	template <typename> friend class TInsertIterator;
};

static_assert(CIndirectlyWritable<TPostIncrementProxy<void(*)(int)>, int>);

template <typename F>
class TInsertIterator final : private FNoncopyable
{
public:

	FORCEINLINE constexpr TInsertIterator() requires (CDefaultConstructible<F>) = default;

	FORCEINLINE constexpr explicit TInsertIterator(F InInserter) : Storage(MoveTemp(InInserter)) { check_code({ bIsProduced = false; }); }

	FORCEINLINE constexpr TInsertIterator(TInsertIterator&&)            = default;
	FORCEINLINE constexpr TInsertIterator& operator=(TInsertIterator&&) = default;

	NODISCARD FORCEINLINE constexpr TInsertProxy<F> operator*()
	{
		checkf(!bIsProduced, TEXT("Exception insert, Ensure that no multiple values are assigned to the inserter."));
		check_code({ bIsProduced = true; });
		return TInsertProxy<F>(*this);
	}

	FORCEINLINE constexpr TInsertIterator& operator++() { check_code({ bIsProduced = false; }); return *this; }

	FORCEINLINE constexpr TPostIncrementProxy<F> operator++(int)
	{
		checkf(!bIsProduced, TEXT("Exception insert, Ensure that no multiple values are assigned to the inserter."));
		return TPostIncrementProxy<F>(*this);
	}

private:

	F Storage;

#	if DO_CHECK
	bool bIsProduced;
#	endif

	template <typename> friend class TInsertProxy;
	template <typename> friend class TPostIncrementProxy;
};

static_assert(COutputIterator<TInsertIterator<void(*)(int)>, int>);

NAMESPACE_PRIVATE_END

/** Creates an iterator adapter inserted in the front of the container. */
template <typename C>
NODISCARD FORCEINLINE constexpr auto MakeFrontInserter(C& Container)
{
	return NAMESPACE_PRIVATE::TInsertIterator([&Container]<typename T>(T&& A) { Container.PushFront(Forward<T>(A)); });
}

/** Creates an iterator adapter inserted in the back of the container. */
template <typename C>
NODISCARD FORCEINLINE constexpr auto MakeBackInserter(C& Container)
{
	return NAMESPACE_PRIVATE::TInsertIterator([&Container]<typename T>(T&& A) { Container.PushBack(Forward<T>(A)); });
}

/** Creates an iterator adapter inserted in the container. */
template <typename C>
NODISCARD FORCEINLINE constexpr auto MakeInserter(C& Container, const typename C::FConstIterator& InIter)
{
	return NAMESPACE_PRIVATE::TInsertIterator([&Container, Iter = InIter]<typename T>(T&& A) mutable { Iter = Container.Insert(Iter, Forward<T>(A)); });
}

NAMESPACE_MODULE_END(Utility)
NAMESPACE_MODULE_END(Redcraft)
NAMESPACE_REDCRAFT_END