// Copyright (c) 2024 Codemongous LLC. All Rights Reserved.

#pragma once

#include <algorithm>
#include <stdarg.h>
#include <string>
#include <vector>

using std::stof;
using std::transform;
using std::vector;
using std::wstring;


namespace Sourcify
{
	class StringHelpers
	{
		StringHelpers() = delete;

	public:
		enum class ESearchCaseType
		{
			/** Case sensitive. Upper/lower casing must match for strings to be considered equal. */
			CaseSensitive,

			/** Ignore case. Upper/lower casing does not matter when making a comparison. */
			IgnoreCase,
		};

		static double Atod(const wstring& InString)
		{
			return stof(InString);
		}

		static bool EndsWith(const wstring& InString, const wchar_t* Suffix)
		{
			return InString.find(Suffix) == InString.length() - wcslen(Suffix);
		}
		
		static wstring Join(const vector<wstring>& Strings, const wchar_t* Separator)
		{
			wstring Result;
			for (size_t i = 0; i < Strings.size(); ++i)
			{
				Result.append(Strings[i]);
				if (i < Strings.size() - 1)
				{
					Result.append(Separator);
				}
			}
			return Result;
		}

		/** Returns the left most given number of characters */
		static wstring Left(const wstring& InString, size_t Count)
		{
			if (InString.size() <= Count)
			{
				return InString;
			}

			return InString.substr(0, Count);
		}

		/** Modifies the string such that it is now the left most characters chopping the given number of characters from the end */
		static void LeftChopInline(wstring& InString, size_t Count)
		{
			if (InString.size() <= Count)
			{
				InString = L"";
				return;
			}

			InString = InString.substr(0, InString.size() - Count);
		}

		/** Modifies the string such that it is now the left most given number of characters */
		static void LeftInline(wstring& InString, uint32_t Count)
		{
			if (InString.size() <= Count)
			{
				return;
			}

			InString = InString.substr(0, Count);
		}

		/** Returns the string to the right of the specified location, counting forward from the left (from the beginning of the word). */
		static wstring RightChop(const wstring& InString, size_t Count)
		{
			if (InString.size() <= Count)
			{
				return L"";
			}

			return InString.substr(Count, InString.size() - Count);
		}

		/** Returns the string to the right of the specified location, counting back from the right (end of the word). */
		static wstring Right(const wstring& InString, size_t Count)
		{
			if (InString.size() <= Count)
			{
				return InString;
			}

			return InString.substr(InString.size() - Count);
		}
		
		/** Returns a string populated from a format string and its arguments */
		static wstring Printf(const wchar_t* Format, ...)
		{
			// Try to format the string with a fixed buffer size
			const int32_t VA_BUFFER_SIZE = 1024;
			va_list Args;
			va_start(Args, Format);
			wchar_t Buffer[VA_BUFFER_SIZE];
			int32_t result = vswprintf(Buffer, VA_BUFFER_SIZE, Format, Args);
			va_end(Args);

			int32_t NewSize = VA_BUFFER_SIZE * 2;
			wchar_t* NewBuffer = nullptr;
			while (result == -1) // Failed to fit into our fixed stack allocated buffer, find an appropriate heap allocated buffer size
			{
				NewBuffer = new wchar_t[NewSize];
				if (!NewBuffer)
				{
					return {}; // Failed to allocate memory
				}

				va_start(Args, Format);				
				result = vswprintf(NewBuffer, NewSize, Format, Args);
				va_end(Args);
				if (result >= 0) // Fit into the buffer, so we're good to return
				{
					wstring FormattedStr{ NewBuffer };
					delete[] NewBuffer;
					return FormattedStr;
				}
				delete[] NewBuffer;
				NewSize *= 2;
			}

			return { Buffer };
		}
		
		/**
		 * Removes InPrefix from the start of the string (inline) if it exists. 
		 * Returns true if the prefix was removed, otherwise false.
		 */
		static bool RemoveFromStart(wstring& InString, const wstring& InPrefix, ESearchCaseType SearchCase = ESearchCaseType::IgnoreCase)
		{
			wstring TransformedString = InString;
			wstring TransformedSubstring = InPrefix;
			
			if (SearchCase == ESearchCaseType::IgnoreCase)
			{
				TransformedString = InString;
				transform(TransformedString.begin(), TransformedString.end(), TransformedString.begin(), ::towlower);
				TransformedSubstring = InPrefix;
				transform(TransformedSubstring.begin(), TransformedSubstring.end(), TransformedSubstring.begin(), ::towlower);
			}

			wstring::size_type Index = TransformedString.find(TransformedSubstring);
			if (Index == wstring::npos || Index > 0)
			{
				return false;
			}
			
			InString = InString.substr(InPrefix.size(), InString.size());
			return true;
		}
	};
}