#pragma once

#include "CoreTypes.h"
#include "Range/Utility.h"
#include "Iterator/Iterator.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 view. Not directly instantiable. */
template <CClass T> requires (CSameAs<T, TRemoveCV<T>>)
class IBasicViewInterface
{
public:

	/** @return The pointer to the underlying element storage. */
	NODISCARD FORCEINLINE constexpr auto GetData()       requires (CContiguousRange<      T>) { return Range::GetData(static_cast<      T&>(*this)); }
	NODISCARD FORCEINLINE constexpr auto GetData() const requires (CContiguousRange<const T>) { return Range::GetData(static_cast<const T&>(*this)); }

	/** @return The reverse iterator to the first or end element. */
	NODISCARD FORCEINLINE constexpr auto Begin()       requires (CRange<      T>) { return Range::Begin(static_cast<      T&>(*this)); }
	NODISCARD FORCEINLINE constexpr auto End()         requires (CRange<      T>) { return Range::End  (static_cast<      T&>(*this)); }
	NODISCARD FORCEINLINE constexpr auto Begin() const requires (CRange<const T>) { return Range::Begin(static_cast<const T&>(*this)); }
	NODISCARD FORCEINLINE constexpr auto End()   const requires (CRange<const T>) { return Range::End  (static_cast<const T&>(*this)); }

	/** @return The reverse iterator to the first or end element. */
	NODISCARD FORCEINLINE constexpr auto RBegin()       requires (CBidirectionalRange<      T> && CCommonRange<      T>) { return Range::RBegin(static_cast<      T&>(*this)); }
	NODISCARD FORCEINLINE constexpr auto REnd()         requires (CBidirectionalRange<      T> && CCommonRange<      T>) { return Range::REnd  (static_cast<      T&>(*this)); }
	NODISCARD FORCEINLINE constexpr auto RBegin() const requires (CBidirectionalRange<const T> && CCommonRange<const T>) { return Range::RBegin(static_cast<const T&>(*this)); }
	NODISCARD FORCEINLINE constexpr auto REnd()   const requires (CBidirectionalRange<const T> && CCommonRange<const T>) { return Range::REnd  (static_cast<const T&>(*this)); }

	/** @return The number of elements in the container. */
	NODISCARD FORCEINLINE constexpr size_t Num()       requires (CSizedRange<      T>) { return Range::Num(static_cast<      T&>(*this)); }
	NODISCARD FORCEINLINE constexpr size_t Num() const requires (CSizedRange<const T>) { return Range::Num(static_cast<const T&>(*this)); }

	/** @return true if the container is empty, false otherwise. */
	NODISCARD FORCEINLINE constexpr bool IsEmpty()       requires (CSizedRange<      T> || CForwardRange<      T>) { return Range::IsEmpty(static_cast<      T&>(*this)); }
	NODISCARD FORCEINLINE constexpr bool IsEmpty() const requires (CSizedRange<const T> || CForwardRange<const T>) { return Range::IsEmpty(static_cast<const T&>(*this)); }

	/** @return true if the container is empty, false otherwise. */
	NODISCARD FORCEINLINE constexpr explicit operator bool()       requires (CSizedRange<      T> || CForwardRange<      T>) { return !Range::IsEmpty(static_cast<      T&>(*this)); }
	NODISCARD FORCEINLINE constexpr explicit operator bool() const requires (CSizedRange<const T> || CForwardRange<const T>) { return !Range::IsEmpty(static_cast<const T&>(*this)); }

	/** @return The reference to the requested element. */
	NODISCARD FORCEINLINE constexpr decltype(auto) operator[](size_t Index)       requires (CRandomAccessRange<      T>) { return Range::Begin(static_cast<      T&>(*this))[Index]; }
	NODISCARD FORCEINLINE constexpr decltype(auto) operator[](size_t Index) const requires (CRandomAccessRange<const T>) { return Range::Begin(static_cast<const T&>(*this))[Index]; }

	/** @return The reference to the first or last element. */
	NODISCARD FORCEINLINE constexpr decltype(auto) Front()       requires (CForwardRange<      T>)                                { return  *Range::Begin(static_cast<      T&>(*this)); }
	NODISCARD FORCEINLINE constexpr decltype(auto) Front() const requires (CForwardRange<const T>)                                { return  *Range::Begin(static_cast<const T&>(*this)); }
	NODISCARD FORCEINLINE constexpr decltype(auto) Back()        requires (CBidirectionalRange<      T> && CCommonRange<      T>) { return *Range::RBegin(static_cast<      T&>(*this)); }
	NODISCARD FORCEINLINE constexpr decltype(auto) Back()  const requires (CBidirectionalRange<const T> && CCommonRange<const T>) { return *Range::RBegin(static_cast<const T&>(*this)); }

	ENABLE_RANGE_BASED_FOR_LOOP_SUPPORT

private:

	FORCEINLINE constexpr IBasicViewInterface()                                      = default;
	FORCEINLINE constexpr IBasicViewInterface(const IBasicViewInterface&)            = default;
	FORCEINLINE constexpr IBasicViewInterface(IBasicViewInterface&&)                 = default;
	FORCEINLINE constexpr IBasicViewInterface& operator=(const IBasicViewInterface&) = default;
	FORCEINLINE constexpr IBasicViewInterface& operator=(IBasicViewInterface&&)      = default;
	FORCEINLINE constexpr ~IBasicViewInterface()                                     = default;

	friend T;
};

NAMESPACE_END(Range)

/**
 * A concept specifies that a range is a view, that is, it has constant time copy, move and assignment.
 * Specify, a view can be movable only but not copyable, or it can be both movable and copyable.
 */
template <typename V>
concept CView = CRange<V> && CMovable<V> && CDerivedFrom<V, Range::IBasicViewInterface<TRemoveCVRef<V>>>;

NAMESPACE_PRIVATE_BEGIN

template <typename T> struct TIsInitializerList                      : FFalse { };
template <typename T> struct TIsInitializerList<initializer_list<T>> : FTrue  { };

NAMESPACE_PRIVATE_END

/** A concept specifies that a viewable range that can be converted into a view through Range::All. */
template <typename R>
concept CViewableRange = CRange<R>
	&& ((CView<TRemoveCVRef<R>> && CConstructibleFrom<TRemoveCVRef<R>, R>)
	|| (!CView<TRemoveCVRef<R>> && (CLValueReference<R> || (CMovable<TRemoveReference<R>>
	&& !NAMESPACE_PRIVATE::TIsInitializerList<TRemoveCVRef<R>>::Value))));

NAMESPACE_BEGIN(Range)

/** A simple view that combines an iterator-sentinel pair into a view. */
template <CInputOrOutputIterator I, CSentinelFor<I> S = I>
class TRangeView : public IBasicViewInterface<TRangeView<I, S>>
{
public:

	using FElementType = TIteratorElement<I>;

	FORCEINLINE constexpr TRangeView() requires (CDefaultConstructible<I>) = default;

	FORCEINLINE constexpr TRangeView(I InFirst, S InLast) : First(MoveTemp(InFirst)), Last(InLast) { }

	NODISCARD FORCEINLINE constexpr I Begin()       requires (!CCopyable<I>) { return MoveTemp(First); }
	NODISCARD FORCEINLINE constexpr I Begin() const requires ( CCopyable<I>) { return          First;  }

	NODISCARD FORCEINLINE constexpr S End() const { return Last; }

	NODISCARD FORCEINLINE constexpr size_t Num() const requires (CSizedSentinelFor<S, I>) { return Last - First; }

	NODISCARD FORCEINLINE constexpr bool IsEmpty() const { return First == Last; }

private:

	NO_UNIQUE_ADDRESS I First;
	NO_UNIQUE_ADDRESS S Last;

};

template <CInputOrOutputIterator I, CSentinelFor<I> S>
TRangeView(I, S) -> TRangeView<I, S>;

NAMESPACE_END(Range)

template <typename I, typename S>
constexpr bool bEnableBorrowedRange<Range::TRangeView<I, S>> = true;

NAMESPACE_BEGIN(Range)

/** Creates A simple view that combines an iterator-sentinel pair. */
template <CInputOrOutputIterator I, CSentinelFor<I> S = I>
NODISCARD FORCEINLINE constexpr TRangeView<I, S> View(I First, S Last)
{
	return TRangeView<I, S>(MoveTemp(First), Last);
}

NAMESPACE_END(Range)

NAMESPACE_MODULE_END(Utility)
NAMESPACE_MODULE_END(Redcraft)
NAMESPACE_REDCRAFT_END