#pragma once

// NOTE: This file is not intended to be included directly, it is included by 'String/String.h'.

#include "Templates/Tuple.h"
#include "Templates/Utility.h"
#include "TypeTraits/TypeTraits.h"

#include <cmath>
#include <limits>

#pragma warning(push)
#pragma warning(disable : 4146)

NAMESPACE_REDCRAFT_BEGIN
NAMESPACE_MODULE_BEGIN(Redcraft)
NAMESPACE_MODULE_BEGIN(Utility)

// NOTE: These functions are used to format an object to a string and parse a string to an object.
// If the user-defined overloads a function with the 'Fmt' parameter, fill-and-align needs to be handled.
// The formatting function should produce a string that can be parsed by the parsing function, if the parsing function exists.

// NOTE: These functions are recommended for debug programs.

#define LEFT_BRACE  LITERAL(T, '{')
#define RIGHT_BRACE LITERAL(T, '}')

#define ESCAPE_LEFT_BRACE  TStringView(LITERAL(T, "<[{"))
#define ESCAPE_RIGHT_BRACE TStringView(LITERAL(T, "}]>"))

NAMESPACE_PRIVATE_BEGIN

template <CCharType T, bool bIsFormat>
struct TStringHelper
{
	FORCEINLINE static bool FormatObject(auto& Result, TStringView<T> Fmt, auto& Object) requires (bIsFormat)
	{
		using U = TRemoveCVRef<decltype(Object)>;

		if constexpr (!CConst<TRemoveReference<decltype(Object)>>)
		{
			checkf(false, TEXT("Unsafe formatting for a variable that is non-const."));

			return false;
		}

		else if (Fmt.IsEmpty())
		{
			if constexpr (CArithmetic<U>)
			{
				constexpr const T* DigitToChar = LITERAL(T, "9876543210123456789");
				constexpr size_t   ZeroIndex   = 9;

				if constexpr (CSameAs<U, bool>)
				{
					Result += Object ? LITERAL(T, "True") : LITERAL(T, "False");

					return true;
				}

				else if constexpr (CIntegral<U>)
				{
					U Value = Object;

					const bool bNegative = Object < 0;

					constexpr size_t BufferSize = 32;

					T Buffer[BufferSize];

					size_t Index = BufferSize;

					do Buffer[--Index] = DigitToChar[ZeroIndex + Value % 10]; while (Value /= 10);

					if (bNegative) Buffer[--Index] = LITERAL(T, '-');

					const T* Begin = Buffer + Index;
					const T* End   = Buffer + BufferSize;

					Result.Append(Begin, End);

					return true;
				}

				else if constexpr (CFloatingPoint<U>)
				{
					if (NAMESPACE_STD::isinf(Object) && !NAMESPACE_STD::signbit(Object)) { Result += LITERAL(T,  "Infinity"); return true; }
					if (NAMESPACE_STD::isinf(Object) &&  NAMESPACE_STD::signbit(Object)) { Result += LITERAL(T, "-Infinity"); return true; }

					if (NAMESPACE_STD::isnan(Object) && !NAMESPACE_STD::signbit(Object)) { Result += LITERAL(T,  "NaN"); return true; }
					if (NAMESPACE_STD::isnan(Object) &&  NAMESPACE_STD::signbit(Object)) { Result += LITERAL(T, "-NaN"); return true; }

					U Value = NAMESPACE_STD::round(Object * static_cast<U>(1e6));

					const bool bNegative = NAMESPACE_STD::signbit(Object);

					TString<T, TInlineAllocator<32>> Buffer;

					for (size_t Index = 0; Index <= 6 || static_cast<signed>(Value) != 0; ++Index)
					{
						Buffer += DigitToChar[ZeroIndex + static_cast<signed>(NAMESPACE_STD::fmod(Value, 10))];

						if (Index == 5) Buffer += LITERAL(T, '.');

						Value /= 10;
					}

					if (bNegative) Buffer += LITERAL(T, '-');

					Result.Append(Buffer.RBegin(), Buffer.REnd());

					return true;
				}

				else static_assert(sizeof(U) == -1, "Unsupported arithmetic type");
			}
		}

		return false;
	}

	FORCEINLINE static bool ParseObject(TStringView<T>& View, TStringView<T> Fmt, auto& Object) requires (!bIsFormat)
	{
		using U = TRemoveCVRef<decltype(Object)>;

		if constexpr (CConst<TRemoveReference<decltype(Object)>>)
		{
			checkf(false, TEXT("Cannot assign to a variable that is const."));

			return false;
		}

		else if constexpr (CArithmetic<U>)
		{
			checkf(Fmt.IsEmpty(), TEXT("Formatted parsing of arithmetic types not implemented."));

			// Skip leading white spaces.
			while (!View.IsEmpty() && TChar<T>::IsSpace(View.Front())) View.RemovePrefix(1);

			if (View.IsEmpty()) return false;

			bool bNegative = false;

			// Handle optional sign.
			if (View.Front() == LITERAL(T, '+'))
			{
				View.RemovePrefix(1);
			}
			else if (View.Front() == LITERAL(T, '-'))
			{
				bNegative = true;
				View.RemovePrefix(1);
			}

			// Handle boolean conversion.
			else if constexpr (CSameAs<U, bool>)
			{
				bool bIsTrue  = false;
				bool bIsFalse = false;

				bIsTrue  |= View.StartsWith(LITERAL(T, "true"))  || View.StartsWith(LITERAL(T, "True"))  || View.StartsWith(LITERAL(T, "TRUE"));
				bIsFalse |= View.StartsWith(LITERAL(T, "false")) || View.StartsWith(LITERAL(T, "False")) || View.StartsWith(LITERAL(T, "FALSE"));

				if (bIsTrue)  { View.RemovePrefix(4); Object = true;  return true; }
				if (bIsFalse) { View.RemovePrefix(5); Object = false; return true; }
			}

			// Handle floating-point conversion.
			if constexpr (CFloatingPoint<U>)
			{
				bool bIsInfinity = false;
				bool bIsNaN      = false;

				bIsInfinity |= View.StartsWith(LITERAL(T, "infinity")) || View.StartsWith(LITERAL(T, "Infinity")) || View.StartsWith(LITERAL(T, "INFINITY"));
				bIsNaN      |= View.StartsWith(LITERAL(T, "nan"))      || View.StartsWith(LITERAL(T, "NaN"))      || View.StartsWith(LITERAL(T, "NAN"));

				if (bIsInfinity) { View.RemovePrefix(8); Object = bNegative ? -NAMESPACE_STD::numeric_limits<U>::infinity()  : NAMESPACE_STD::numeric_limits<U>::infinity();  return true; }
				if (bIsNaN)      { View.RemovePrefix(3); Object = bNegative ? -NAMESPACE_STD::numeric_limits<U>::quiet_NaN() : NAMESPACE_STD::numeric_limits<U>::quiet_NaN(); return true; }
			}

			unsigned Base = 0;

			// Auto detect base.
			{
				if (View.Num() >= 2 && View.Front() == LITERAL(T, '0'))
				{
					if (View[1] == LITERAL(T, 'x') || View[1] == LITERAL(T, 'X'))
					{
						Base = 16;
						View.RemovePrefix(2);
					}
					else if (View[1] == LITERAL(T, 'b') || View[1] == LITERAL(T, 'B'))
					{
						Base = 2;
						View.RemovePrefix(2);
					}
					else if (TChar<T>::IsDigit(View.Front(), 8))
					{
						Base = 8;
						View.RemovePrefix(1);
					}
					else Base = 10;
				}
				else Base = 10;
			}

			// Parse the number.
			auto ToNumber = [&View]<typename NumberType>(TInPlaceType<NumberType>, unsigned Base, NumberType Init = static_cast<NumberType>(0)) -> NumberType
			{
				NumberType Result = Init;

				while (!View.IsEmpty() && (Base == 10 ? TChar<T>::IsDigit(View.Front()) : TChar<T>::IsDigit(View.Front(), Base)))
				{
					Result = static_cast<NumberType>(Result * Base + *TChar<T>::ToDigit(View.Front()));

					View.RemovePrefix(1);
				}

				return Result;
			};

			// Handle integral conversion.
			if constexpr (CIntegral<U>)
			{
				using UnsignedU = TMakeUnsigned<TConditional<!CSameAs<U, bool>, U, int>>;

				if (View.IsEmpty()) return false;

				// The integral number must start with a digit.
				if (!TChar<T>::IsDigit(View.Front(), Base)) return false;

				// Parse the integral number.
				UnsignedU Number = ToNumber(InPlaceType<UnsignedU>, Base);

				Object = static_cast<U>(bNegative ? -Number : Number);

				return true;
			}

			// Handle floating-point conversion.
			else if constexpr (CFloatingPoint<U>)
			{
				if (View.IsEmpty()) return false;

				// The floating-point number must start with a digit or a dot.
				if (!(TChar<T>::IsDigit(View.Front(), Base) || View.Front() == LITERAL(T, '.'))) return false;

				size_t IntegralBeginNum = View.Num();

				// Parse the integral number.
				Object = ToNumber(InPlaceType<U>, Base);

				size_t IntegralLength = IntegralBeginNum - View.Num();

				// Parse the fractional number.
				if (!View.IsEmpty() && View.Front() == LITERAL(T, '.'))
				{
					View.RemovePrefix(1);

					U InvBase = 1 / static_cast<U>(Base);

					size_t FractionBeginNum = View.Num();

					Object = ToNumber(InPlaceType<U>, Base, Object);

					size_t FractionLength = FractionBeginNum - View.Num();

					Object *= NAMESPACE_STD::pow(InvBase, static_cast<U>(FractionLength));
				}
				else if (IntegralLength == 0) return false;

				// For floating point numbers apply the symbols directly
				Object = static_cast<U>(bNegative ? -Object : Object);

				if (View.IsEmpty()) return true;

				if (Base != 10 && Base != 16) return true;

				bool bHasExponent = false;

				bHasExponent |= Base == 10 && View.Front() == LITERAL(T, 'e');
				bHasExponent |= Base == 10 && View.Front() == LITERAL(T, 'E');
				bHasExponent |= Base == 16 && View.Front() == LITERAL(T, 'p');
				bHasExponent |= Base == 16 && View.Front() == LITERAL(T, 'P');

				if (!bHasExponent) return true;

				View.RemovePrefix(1);

				if (View.IsEmpty()) return false;

				// Parse the exponent number.
				{
					bool bNegativeExponent = false;

					if (View.Front() == LITERAL(T, '+'))
					{
						View.RemovePrefix(1);
					}
					else if (View.Front() == LITERAL(T, '-'))
					{
						bNegativeExponent = true;
						View.RemovePrefix(1);
					}

					// The exponent number must start with a digit.
					if (!TChar<T>::IsDigit(View.Front())) return false;

					U Exponent = ToNumber(InPlaceType<U>, 10);

					Exponent = bNegativeExponent ? -Exponent : Exponent;

					Object *= static_cast<U>(NAMESPACE_STD::pow(static_cast<U>(Base == 16 ? 2 : 10), Exponent));
				}

				return true;
			}

			else static_assert(sizeof(U) == -1, "Unsupported arithmetic type");

			return false;
		}

		return false;
	}

	FORCEINLINE static size_t Do(auto& Result, TStringView<T> Fmt, auto ArgsTuple)
	{
		size_t FormattedObjectNum = 0;

		size_t ArgsIndex = 0;

		auto ParseFormat = [&FormattedObjectNum, &ArgsIndex, ArgsTuple](auto& Self, auto& String, TStringView<T>& Fmt) -> bool
		{
			bool bIsFullyFormatted = true;

			while (!Fmt.IsEmpty())
			{
				if (Fmt.StartsWith(ESCAPE_LEFT_BRACE))
				{
					Fmt.RemovePrefix(ESCAPE_LEFT_BRACE.Num());

					if constexpr (!bIsFormat)
					{
						if (!String.StartsWith(LEFT_BRACE)) return false;

						String.RemovePrefix(1);
					}
					else String += LEFT_BRACE;

					continue;
				}

				if (Fmt.StartsWith(ESCAPE_RIGHT_BRACE))
				{
					Fmt.RemovePrefix(ESCAPE_RIGHT_BRACE.Num());

					if constexpr (!bIsFormat)
					{
						if (!String.StartsWith(RIGHT_BRACE)) return false;

						String.RemovePrefix(1);
					}
					else String += RIGHT_BRACE;

					continue;
				}

				if (Fmt.StartsWith(LEFT_BRACE))
				{
					Fmt.RemovePrefix(1);

					int SubplaceholderNum = -1;

					size_t PlaceholderBegin = -1;
					size_t PlaceholderEnd   = -1;

					// Find the end of the placeholder.
					do
					{
						while (true)
						{
							PlaceholderBegin = Fmt.FindFirstOf(LEFT_BRACE, PlaceholderBegin + 1);

							if (PlaceholderBegin == INDEX_NONE) break;

							if (Fmt.First(PlaceholderBegin + 1).EndsWith(ESCAPE_LEFT_BRACE))
							{
								++PlaceholderBegin;
							}
							else break;
						}

						while (true)
						{
							PlaceholderEnd = Fmt.FindFirstOf(RIGHT_BRACE, PlaceholderEnd + 1);

							if (PlaceholderEnd == INDEX_NONE) break;

							if (Fmt.Substr(PlaceholderEnd).StartsWith(ESCAPE_RIGHT_BRACE))
							{
								++PlaceholderEnd;
							}
							else break;
						}

						if (PlaceholderEnd == INDEX_NONE)
						{
							checkf(false, TEXT("Unmatched '{' in format string."));

							if constexpr (bIsFormat) String += Fmt;

							Fmt = LITERAL(T, "");

							return false;
						}

						++SubplaceholderNum;
					}
					while (PlaceholderBegin != INDEX_NONE && PlaceholderBegin < PlaceholderEnd);

					TStringView Subfmt = Fmt.First(PlaceholderEnd);

					Fmt.RemovePrefix(PlaceholderEnd + 1);

					bool bIsSuccessful = true;

					// The subformat string size are usually smaller than 16.
					TString<T, TInlineAllocator<16>> FormattedSubfmt;

					// Recursively format the subformat string.
					if (SubplaceholderNum > 0)
					{
						if constexpr (bIsFormat) bIsSuccessful = Self(Self, FormattedSubfmt, Subfmt);

						else bIsSuccessful = TStringHelper<T, true>::Do(FormattedSubfmt, Subfmt, ArgsTuple);

						Subfmt = FormattedSubfmt;
					}

					if (bIsSuccessful)
					{
						// Find the placeholder index delimiter.
						size_t IndexLength = Subfmt.FindFirstOf(LITERAL(T, ':'));

						if (IndexLength == INDEX_NONE) IndexLength = Subfmt.Num();

						TStringView PlaceholderIndex  = Subfmt.First(IndexLength);
						TStringView PlaceholderSubfmt = IndexLength != Subfmt.Num() ? Subfmt.Substr(IndexLength + 1) : LITERAL(T, "");

						size_t Index;

						if (IndexLength != 0)
						{
							if (!PlaceholderIndex.IsNumeric())
							{
								checkf(false, TEXT("Invalid placeholder index."));

								if constexpr (bIsFormat)
								{
									String += LEFT_BRACE;
									String += Subfmt;
									String += RIGHT_BRACE;

									bIsFullyFormatted = false;
								}
								else return false;

								continue;
							}

							verify(PlaceholderIndex.Parse(LITERAL(T, "{}"), Index) == 1);
						}
						else Index = ArgsIndex++;

						checkf(Index < ArgsTuple.Num(), TEXT("Argument not found."));

						bIsSuccessful = ArgsTuple.Visit(
							[&String, Subfmt = PlaceholderSubfmt](auto& Object) mutable
							{
								if (Subfmt.StartsWith(LITERAL(T, ':'))) Subfmt.RemovePrefix(1);

								if constexpr (bIsFormat) return TStringHelper::FormatObject(String, Subfmt, Object);

								else return TStringHelper::ParseObject(String, Subfmt, Object);
							},
							Index
						);
					}

					if (!bIsSuccessful)
					{
						if constexpr (bIsFormat)
						{
							String += LEFT_BRACE;
							String += Subfmt;
							String += RIGHT_BRACE;

							bIsFullyFormatted = false;
						}
						else return false;
					}
					else ++FormattedObjectNum;

					continue;
				}

				check_code({ if (Fmt.StartsWith(RIGHT_BRACE)) check_no_entry(); });

				if constexpr (!bIsFormat)
				{
					if (TChar<T>::IsSpace(Fmt.Front()))
					{
						Fmt.RemovePrefix(1);

						while (TChar<T>::IsSpace(String.Front()))
						{
							String.RemovePrefix(1);
						}

						continue;
					}

					if (!String.StartsWith(Fmt.Front())) return false;

					String.RemovePrefix(1);
				}
				else String += Fmt.Front();

				Fmt.RemovePrefix(1);
			}

			return bIsFullyFormatted;
		};

		bool bIsSuccessful = ParseFormat(ParseFormat, Result, Fmt);

		if constexpr (bIsFormat) return bIsSuccessful;

		return FormattedObjectNum;
	}
};

NAMESPACE_PRIVATE_END

template <CCharType T, CAllocator<T> Allocator>
template <typename ... Ts>
TString<T, Allocator> TString<T, Allocator>::Format(TStringView<ElementType> Fmt, const Ts&... Args)
{
	// The Unreal Engine says that the starting buffer size catches 99.97% of printf calls.
	constexpr size_t ReserveBufferSize = 512;

	TString Result;

	Result.Reserve(ReserveBufferSize);

	NAMESPACE_PRIVATE::TStringHelper<ElementType, true>::Do(Result, Fmt, ForwardAsTuple(Args...));

	return Result;
}

template <CCharType T>
template <typename ... Ts>
size_t TStringView<T>::Parse(TStringView Fmt, Ts&... Args) const
{
	TStringView View = *this;

	return NAMESPACE_PRIVATE::TStringHelper<ElementType, false>::Do(View, Fmt, ForwardAsTuple(Args...));
}

#undef LEFT_BRACE
#undef RIGHT_BRACE

#undef ESCAPE_LEFT_BRACE
#undef ESCAPE_RIGHT_BRACE

NAMESPACE_MODULE_END(Utility)
NAMESPACE_MODULE_END(Redcraft)
NAMESPACE_REDCRAFT_END

#pragma warning(pop)