#include "pch.h"
#include "System/String.h"
#include "System/Array.h"
#include "System/Utils/StringConvert.h"
#include "System/ArgumentException.h"
#include "System/ArgumentOutOfRangeException.h"
#include "System/NotImplementedException.h"
#include "System/OverflowException.h"
#include "System/FormatException.h"
#include <set>
#include <vector>
#include <initializer_list>
#include <sstream>
#include <type_traits>
#include <algorithm>
#include <iostream>
#include <string>


namespace DotNetDupe {
	namespace System {

		struct String::StringImpl {
			std::string s;
			StringImpl() = default;
			StringImpl(const char* str) : s(str) {}
			StringImpl(const std::string& str) : s(str) {}
			StringImpl(std::string&& str) : s(std::move(str)) {}

		};


		namespace {
			template <typename T>
			inline String ToStringHelper(const T& val) {
				using DecayedT = std::decay_t<T>;
				if constexpr (std::is_same_v<DecayedT, String>) {
					return val;
				}
				else if constexpr (std::is_same_v<DecayedT, std::string>) {
					return String(val.c_str());
				}
				else if constexpr (std::is_same_v<DecayedT, std::nullptr_t>) {
					return String("");
				}
				else if constexpr (std::is_same_v<DecayedT, bool>) {
					return val ? "True" : "False";
				}
				else {
					std::ostringstream ss;
					ss << val;
					return String(ss.str().c_str());
				}
			}
		}

		void ThrowArgumentException(const char* msg) {
			throw ArgumentException(msg);
		}

		void ThrowFormatException(const char* msg) {
			throw FormatException(msg);
		}
		String::String() { m_pImpl = new StringImpl("");  }
		String::~String() { delete m_pImpl; }
		static void AppendExplicitArg(const std::string& sFmt, size_t i, size_t end, const String* pArgs, int iArgCount, std::string& sRes) {
			std::string num = sFmt.substr(i + 1, end - i - 1);
			size_t idx = 0;
			int argIdx = std::stoi(num, &idx);
			if (idx != num.length() || argIdx < 0 || argIdx >= iArgCount) throw FormatException("Index out of bounds");
			sRes += pArgs[argIdx].GetRawString() ? pArgs[argIdx].GetRawString() : "";
		}

		static void FormatOpenBrace(const std::string& sFmt, size_t& i, int& iAutoIndex, const String* pArgs, int iArgCount, std::string& sRes) {
			if (i + 1 < sFmt.length() && sFmt[i+1] == '{') {
				sRes += '{'; i++;
			} else if (i + 1 < sFmt.length() && sFmt[i+1] == '}') {
				if (iAutoIndex >= iArgCount) throw FormatException("Index out of bounds");
				sRes += pArgs[iAutoIndex].GetRawString() ? pArgs[iAutoIndex].GetRawString() : "";
				iAutoIndex++; i++;
			} else {
				size_t end = sFmt.find('}', i + 1);
				if (end == std::string::npos) throw FormatException("Unclosed brace");
				AppendExplicitArg(sFmt, i, end, pArgs, iArgCount, sRes);
				i = end;
			}
		}

		static void FormatClosingBrace(const std::string& sFmt, size_t& i, std::string& sRes) {
			if (i + 1 < sFmt.length() && sFmt[i+1] == '}') {
				sRes += '}'; i++;
			} else {
				throw FormatException("Unescaped closing brace");
			}
		}

		String String::InternalFormat(const char* pFormat, const String* pArgs, int iArgCount) {
			if (!pFormat) throw ArgumentException("Format string cannot be null.");
			if (iArgCount == 0 || !pArgs) return String(pFormat);

			std::string sFmt = pFormat;
			std::string sRes;
			int iAutoIndex = 0;
			for (size_t i = 0; i < sFmt.length(); ++i) {
				if (sFmt[i] == '{') {
					FormatOpenBrace(sFmt, i, iAutoIndex, pArgs, iArgCount, sRes);
				} else if (sFmt[i] == '}') {
					FormatClosingBrace(sFmt, i, sRes);
				} else {
					sRes += sFmt[i];
				}
			}
			return String(sRes.c_str());
		}

		void* String::operator new(size_t size) {
			return ::operator new(size);
		}

		void String::operator delete(void* p) {
			::operator delete(p);
		}

		void* String::operator new[](size_t size) {
			return ::operator new[](size);
		}

		void String::operator delete[](void* p) {
			::operator delete[](p);
		}

		String String::operator+(const char* pStr) const {
			String sNewStr = *this;
			if (pStr) {
				sNewStr.m_pImpl->s.append(pStr);
			}
			return sNewStr;
		}

		String String::operator+(char ch) const {
			String sNewStr = *this;
			sNewStr.m_pImpl->s.push_back(ch);
			return sNewStr;
		}

		String& String::operator+=(const String& sStr) {
			m_pImpl->s.append(sStr.GetRawString());
			return *this;
		}

		String& String::operator+=(const char* pStr) {
			if (pStr) {
				m_pImpl->s.append(pStr);
			}
			return *this;
		}

		String& String::operator+=(char ch) {
			m_pImpl->s.push_back(ch);
			return *this;
		}

		String operator+(const char* pStr, const String& sStr) {
			String newStr(pStr ? pStr : "");
			return newStr + sStr;
		}

		String operator+(char ch, const String& sStr) {
			char buf[2] = { ch, 0 };
			String newStr(buf);
			return newStr + sStr;
		}


		String::String(const char* pStr) {
			if (pStr == nullptr) throw ArgumentException("Invalid input pointer");
			m_pImpl = new StringImpl();
			m_pImpl->s = pStr;
		}

		String::String(const String& sStr) { m_pImpl = new StringImpl(sStr.m_pImpl->s);  }

		String& String::operator=(const String& sStr) {
			if (this != &sStr) {
				m_pImpl->s = sStr.m_pImpl->s;
			}
			return *this;
		}

		String::String(String&& sStr) noexcept { m_pImpl = new StringImpl(std::move(sStr.m_pImpl->s));  }
		String& String::operator=(String&& sStr) noexcept {
			if (this != &sStr) {
				m_pImpl->s = std::move(sStr.m_pImpl->s);
			}
			return *this;
		}
		String::String(const wchar_t* pStr) {
			if (pStr == nullptr) throw ArgumentException("Invalid input pointer");
			m_pImpl = new StringImpl();

			m_pImpl->s = Utils::StringConvert::WCharToUtf8(pStr);
		}

		String& String::operator=(const wchar_t* pStr) {
			if (pStr == nullptr) throw ArgumentException("Invalid input pointer");

			m_pImpl->s = Utils::StringConvert::WCharToUtf8(pStr);

			return *this;
		}

		String& String::operator=(const char* pStr) {
			if (pStr == nullptr) throw ArgumentException("Invalid input pointer");
			m_pImpl->s = pStr;
			return *this;
		}
		const char* String::GetRawString() const {
			return m_pImpl->s.c_str();
		}
		int String::GetHashCode() const {
			const char* p = GetRawString();
			if (!p) return 0;
			unsigned int hash = 2166136261u;
			while (*p) {
				hash ^= (unsigned char)(*p);
				hash *= 16777619u;
				++p;
			}
			return (int)hash;
		}
		int String::GetLength() const {
			return static_cast<int>(m_pImpl->s.length());
		}

		String String::Clone() const {
			return String(GetRawString());
		}


		char String::operator[](int iIndex) const {
			if (iIndex >= (int)m_pImpl->s.size()) throw ArgumentOutOfRangeException("Invalid iIndex");
			return GetRawString() [iIndex];
		}
		static std::string ToLowerString(const std::string& s) {
			std::string res = s;
			std::transform(res.begin(), res.end(), res.begin(), [](char ch) {
				return static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
			});
			return res;
		}

		int String::Compare(const String& sStr1, int iIndex1, const String& sStr2, int iIndex2, int iLength, bool bIgnoreCase) {
			if (!bIgnoreCase) {
				return sStr1.m_pImpl->s.compare(iIndex1, iLength, sStr2.m_pImpl->s, iIndex2, iLength);
			}
			auto s1 = ToLowerString(sStr1.m_pImpl->s.substr(iIndex1, iLength));
			auto s2 = ToLowerString(sStr2.m_pImpl->s.substr(iIndex2, iLength));
			return s1.compare(s2);
		}

		int String::CompareTo(const String& sStr) const {
			return m_pImpl->s.compare(sStr.GetRawString());
		}
		String String::Concat(
			const std::initializer_list<String> sStrs) const {
			String sNewStr = *this;
			for (auto sStr : sStrs) {
				sNewStr.m_pImpl->s.append(sStr.GetRawString());
			}
			return sNewStr;
		}
		String String::operator+(
			const String& sStr) const {
			return Concat({ sStr });
		}
		bool String::Contains(char ch) const {
			return m_pImpl->s.find(ch) != std::string::npos;
		}
		bool String::Contains(const String& sStr) const {
			return m_pImpl->s.find(sStr.m_pImpl->s) != std::string::npos;
		}

		void String::CopyTo(int iSourceIndex, char* pDestination,
							int iDestinationIndex, int iDestArraySize,
							int iCount) const {
			if (nullptr == pDestination)
				throw ArgumentException("Invalid destination buffer");
			int iLen = GetLength();
			if (iSourceIndex < 0 || iSourceIndex >= iLen)
				throw ArgumentOutOfRangeException("Invalid iSourceIndex");
			if (iCount > iLen)
				throw ArgumentOutOfRangeException(
					"Source array size is smaller than iCount");
			if (iCount > iDestArraySize)
				throw ArgumentOutOfRangeException(
					"Destination array is smaller than iCount");
			m_pImpl->s.copy(pDestination + iDestinationIndex, iCount, iSourceIndex);
		}
		bool String::EndsWith(char ch, bool bIgnoreCase) const {
			auto iLen = m_pImpl->s.length();
			if (iLen == 0) return false;
			if (bIgnoreCase) {
				return std::tolower(static_cast<unsigned char>(m_pImpl->s [iLen - 1])) == std::tolower(static_cast<unsigned char>(ch));
			}
			return m_pImpl->s [iLen - 1] == ch;
		}
		bool String::EndsWith(const String& sSuffix,
							  bool bIgnoreCase) const {
			int iLen = GetLength();
			int iSuffixLen = sSuffix.GetLength();
			if (iSuffixLen > iLen) return false;

			if (bIgnoreCase) {
				return Compare(*this, iLen - iSuffixLen, sSuffix, 0, iSuffixLen, true) == 0;
			}
			return m_pImpl->s.compare(static_cast<size_t>(iLen - iSuffixLen), static_cast<size_t>(iSuffixLen), sSuffix.m_pImpl->s) == 0;
		}
		bool String::Equals(const String& sStr1,
							const String& sStr2) {
			return sStr1 == sStr2;
		}
		bool String::Equals(const String& sStr) const {
			return *this == sStr;
		}
		int String::IndexOf(const String& sSubstring) const {
			return IndexOf(sSubstring, 0, false);
		}
		int String::IndexOf(const String& sSubstring,
							bool bIgnoreCase) const {
			return IndexOf(sSubstring, 0, bIgnoreCase);
		}
		int String::IndexOf(const String& sSubstring,
							int iStartIndex, bool bIgnoreCase) const {
			if (iStartIndex < 0 || iStartIndex > GetLength())
				throw ArgumentOutOfRangeException("Invalid iStartIndex");

			if (sSubstring.IsEmpty()) return iStartIndex;

			if (!bIgnoreCase) {
				auto pos = m_pImpl->s.find(sSubstring.m_pImpl->s, iStartIndex);
				return (pos == std::string::npos) ? -1 : (int)pos;
			}

			// Case-insensitive search
			auto it = std::search(
				m_pImpl->s.begin() + iStartIndex, m_pImpl->s.end(),
				sSubstring.m_pImpl->s.begin(), sSubstring.m_pImpl->s.end(),
				[](char c1, char c2) { return std::tolower(static_cast<unsigned char>(c1)) == std::tolower(static_cast<unsigned char>(c2)); }
			);

			return (it == m_pImpl->s.end()) ? -1 : (int)std::distance(m_pImpl->s.begin(), it);
		}
		int String::IndexOfAny(int iStartIndex,
							   std::initializer_list<char> chChars) {
			if (iStartIndex < 0 || iStartIndex > GetLength())
				throw ArgumentOutOfRangeException("Invalid iStartIndex");

			auto pos = m_pImpl->s.find_first_of(std::string(chChars.begin(), chChars.end()), iStartIndex);
			return (pos == std::string::npos) ? -1 : (int)pos;
		}



		String& String::Append(const char ch) {
			m_pImpl->s += ch;
			return *this;
		}
		String& String::Append(
			const String& sStr) {
			m_pImpl->s.append(sStr.GetRawString());
			return *this;
		}
		String& String::Insert(
			int iIndex, const String& sStr) {
			int iLen = GetLength();
			if (iIndex < 0 || iIndex > iLen)
				throw ArgumentOutOfRangeException("Invalid iIndex");

			m_pImpl->s.insert(iIndex, sStr.GetRawString(), sStr.GetLength());
			return *this;
		}
		bool String::IsEmpty() const {
			return m_pImpl->s.empty();
		}

		String String::Join(
			char chSeparator, std::initializer_list<String> sStrings) {
			return Join(chSeparator, sStrings, 0, (int)sStrings.size());
		}
		String String::Join(
			char chSeparator, std::initializer_list<String> sStrings,
			int iStartIndex, int iCount) {
			if (iStartIndex < 0 || iStartIndex > (int)sStrings.size())
				throw ArgumentOutOfRangeException("Invalid iStartIndex");
			if (iCount < 0 || (iStartIndex + iCount) > (int)sStrings.size())
				throw ArgumentOutOfRangeException("Invalid iCount");

			const String* pStrs = sStrings.begin();
			String sJoinStr("");

			for (int i = iStartIndex; i < iStartIndex + iCount; i++) {
				sJoinStr.Append(pStrs[i]);
				if (i != iStartIndex + iCount - 1) {
					sJoinStr.Append(chSeparator);
				}
			}
			return sJoinStr;
		}
		String String::Join(
			const String& sSeparator,
			std::initializer_list<String> sStrings) {
			return Join(sSeparator, sStrings, 0, (int)sStrings.size());
		}
		String String::Join(
			const String& sSeparator,
			std::initializer_list<String> sStrings, int iStartIndex,
			int iCount) {
			if (iStartIndex < 0 || iStartIndex > (int)sStrings.size())
				throw ArgumentOutOfRangeException("Invalid iStartIndex");
			if (iCount < 0 || (iStartIndex + iCount) > (int)sStrings.size())
				throw ArgumentOutOfRangeException("Invalid iCount");

			const String* pStrs = sStrings.begin();
			String sJoinStr("");

			for (int i = iStartIndex; i < iStartIndex + iCount; i++) {
				sJoinStr.Append(pStrs[i]);
				if (i != iStartIndex + iCount - 1) {
					sJoinStr.Append(sSeparator);
				}
			}
			return sJoinStr;
		}
		int String::LastIndexOf(const String& sStr,
								bool bIgnoreCase) {
			if (sStr.IsEmpty()) return GetLength();

			if (!bIgnoreCase) {
				auto pos = m_pImpl->s.rfind(sStr.m_pImpl->s);
				return (pos == std::string::npos) ? -1 : (int)pos;
			}

			auto it = std::find_end(
				m_pImpl->s.begin(), m_pImpl->s.end(),
				sStr.m_pImpl->s.begin(), sStr.m_pImpl->s.end(),
				[](char c1, char c2) { return std::tolower(static_cast<unsigned char>(c1)) == std::tolower(static_cast<unsigned char>(c2)); }
			);

			return (it == m_pImpl->s.end()) ? -1 : (int)std::distance(m_pImpl->s.begin(), it);
		}

		static int FindLastCharIndex(const std::string& s, int iStartIndex, char ch, bool bIgnoreCase) {
			for (int i = static_cast<int>(s.length()) - 1; i >= iStartIndex; --i) {
				bool match = bIgnoreCase 
					? std::tolower(static_cast<unsigned char>(s[i])) == std::tolower(static_cast<unsigned char>(ch))
					: (s[i] == ch);
				if (match) return i;
			}
			return -1;
		}

		int String::LastIndexOfAny(int iStartIndex, std::initializer_list<char> chChars, bool bIgnoreCase) {
			if (iStartIndex < 0 || iStartIndex > GetLength()) throw ArgumentOutOfRangeException("Invalid iStartIndex");
			if (IsEmpty()) return -1;

			for (auto ch : chChars) {
				int idx = FindLastCharIndex(m_pImpl->s, iStartIndex, ch, bIgnoreCase);
				if (idx != -1) return idx;
			}
			return -1;
		}
		String String::PadLeft(int iTotalWidth) {
			return PadLeft(iTotalWidth, (char)' ');
		}
		String String::PadLeft(int iTotalWidth,
							   char ch) {
			if (iTotalWidth < 0) {
				throw ArgumentException("Invalid iTotalWidth");
			}
			int iLen = GetLength();
			if (iTotalWidth <= iLen) {
				return *this;
			}
			std::string sPadding(iTotalWidth - iLen, ch);
			m_pImpl->s.insert(0, sPadding);
			return *this;
		}
		String String::PadRight(int iTotalWidth) {
			return PadRight(iTotalWidth, (char)' ');
		}
		String String::PadRight(int iTotalWidth,
								char ch) {
			if (iTotalWidth < 0) {
				throw ArgumentException("Invalid iTotalWidth");
			}
			int iLen = GetLength();
			if (iTotalWidth <= iLen) {
				return *this;
			}
			m_pImpl->s.append(iTotalWidth - iLen, ch);
			return *this;
		}
		String String::Remove(int iStartIndex) const {
			return Remove(iStartIndex, GetLength() - iStartIndex);
		}
		String String::Remove(int iStartIndex,
							  int iCount) const {
			int iLen = GetLength();
			if (iStartIndex < 0 || iStartIndex > iLen || iCount < 0 || (iStartIndex + iCount) > iLen) {
				throw ArgumentOutOfRangeException("Invalid iStartIndex or iCount");
			}

			std::string sRet = m_pImpl->s;
			sRet.erase(iStartIndex, iCount);
			return String(sRet.c_str());
		}
		String String::Replace(char chOriginalChar,
							   char chReplaceChar) const {
			std::string sRet = m_pImpl->s;
			std::replace(sRet.begin(), sRet.end(), chOriginalChar, chReplaceChar);
			return String(sRet.c_str());
		}
		String String::Replace(
			const String& sOriginalStr,
			const String& sReplaceStr) const {
			if (sOriginalStr.IsEmpty()) return *this;

			std::string sRet = m_pImpl->s;
			size_t pos = 0;
			while ((pos = sRet.find(sOriginalStr.m_pImpl->s, pos)) != std::string::npos) {
				sRet.replace(pos, sOriginalStr.GetLength(), sReplaceStr.m_pImpl->s);
				pos += sReplaceStr.GetLength();
			}
			return String(sRet.c_str());
		}
		Array<String> String::Split(char chSeparator) const {
			std::vector<String> vTempResult;
			std::stringstream ss(m_pImpl->s);
			std::string sToken;
			while (std::getline(ss, sToken, chSeparator)) {
				vTempResult.push_back(String(sToken.c_str()));
			}

			Array<String> result((int)vTempResult.size());
			for (int i = 0; i < (int)vTempResult.size(); i++) result [i] = vTempResult [i];
			return result;
		}

		static void AddSplitToken(const std::string& sCurrent, StringSplitOptions eOptions, std::vector<String>& vResult) {
			String s(sCurrent.c_str());
			if (eOptions == StringSplitOptions::TrimEntries) s = s.Trim();
			if (eOptions != StringSplitOptions::RemoveEmptyEntries || !s.IsEmpty()) {
				vResult.push_back(s);
			}
		}

		static std::set<char> PopulateSplitCharSet(const String* pSeparator, int iCount) {
			std::set<char> charSet;
			if (!pSeparator || iCount <= 0) return charSet;
			for (int i = 0; i < iCount; ++i) {
				const char* raw = pSeparator[i].GetRawString();
				if (raw) { while (*raw) charSet.insert(*raw++); }
			}
			return charSet;
		}

		static std::vector<String> SplitByCharSet(const std::string& sText, const std::set<char>& charSet, StringSplitOptions eOptions) {
			std::vector<String> vResult;
			std::string sCurrent;
			for (char c : sText) {
				if (charSet.find(c) == charSet.end()) {
					sCurrent += c;
				} else {
					AddSplitToken(sCurrent, eOptions, vResult);
					sCurrent.clear();
				}
			}
			AddSplitToken(sCurrent, eOptions, vResult);
			return vResult;
		}

		Array<String> String::Split(const Array<String>& arrSeparators, StringSplitOptions eOptions) const {
			return Split(arrSeparators.GetData(), arrSeparators.GetLength(), eOptions);
		}

		Array<String> String::Split(const String* pSeparator, int iCount, StringSplitOptions eOptions) const {
			std::set<char> charSet = PopulateSplitCharSet(pSeparator, iCount);
			std::vector<String> vTempResult = SplitByCharSet(m_pImpl->s, charSet, eOptions);
			Array<String> result(static_cast<int>(vTempResult.size()));
			for (int i = 0; i < result.GetLength(); i++) result[i] = vTempResult[i];
			return result;
		}

		bool String::StartsWith(const String& sPrefix) const {
			return StartsWith(sPrefix, false);
		}

		bool String::StartsWith(const String& sPrefix, bool bIgnoreCase) const {
			if (sPrefix.GetLength() > GetLength()) return false;

			if (!bIgnoreCase) {
				return m_pImpl->s.compare(0, sPrefix.GetLength(), sPrefix.m_pImpl->s) == 0;
			}

			return Compare(*this, 0, sPrefix, 0, sPrefix.GetLength(), true) == 0;
		}

		String String::FromInt(int iValue) {
			return ValueOf(iValue);
		}

		String String::ValueOf(int iValue) {
			return ToStringHelper(iValue);
		}


		String String::ValueOf(long long llValue) {
			return ToStringHelper(llValue);
		}

		String String::ValueOf(double dValue) {
			std::ostringstream ss;
			ss << dValue;
			return String(ss.str().c_str());
		}

		String String::ToString(int iValue) {
			return ToStringHelper(iValue);
		}

		String String::ToString(long long llValue) {
			return ToStringHelper(llValue);
		}

		String String::ToString(double dValue) {
			return ToStringHelper(dValue);
		}

		String String::ToString(bool bValue) {
			return ToStringHelper(bValue);
		}

		String String::Substring(int iStartIndex) const {
			return Substring(iStartIndex, GetLength() - iStartIndex);
		}

		String String::Substring(int iStartIndex, int iLength) const {
			int iLen = GetLength();
			if (iStartIndex < 0 || iStartIndex > iLen || iLength < 0 || (iStartIndex + iLength) > iLen) {
				throw ArgumentOutOfRangeException("Invalid iStartIndex or iLength");
			}
			return String(m_pImpl->s.substr(iStartIndex, iLength).c_str());
		}


		String String::ToLower() const {
			std::string sRet = m_pImpl->s;
			std::transform(sRet.begin(), sRet.end(), sRet.begin(), [](char ch) -> char {
				return static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
						   });
			return String(sRet.c_str());
		}

		String String::ToUpper() const {
			std::string sRet = m_pImpl->s;
			std::transform(sRet.begin(), sRet.end(), sRet.begin(), [](char ch) -> char {
				return static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
						   });
			return String(sRet.c_str());
		}

		String String::Trim() const {
			return TrimStart().TrimEnd();
		}

		String String::TrimStart() const {
			auto it = std::find_if(m_pImpl->s.begin(), m_pImpl->s.end(), [](char ch) {
				return !std::isspace(static_cast<unsigned char>(ch));
								   });
			if (it == m_pImpl->s.end()) return "";
			return String(m_pImpl->s.substr(std::distance(m_pImpl->s.begin(), it)).c_str());
		}

		String String::TrimEnd() const {
			auto it = std::find_if(m_pImpl->s.rbegin(), m_pImpl->s.rend(), [](char ch) {
				return !std::isspace(static_cast<unsigned char>(ch));
								   });
			if (it == m_pImpl->s.rend()) return "";
			return String(m_pImpl->s.substr(0, m_pImpl->s.length() - std::distance(m_pImpl->s.rbegin(), it)).c_str());
		}
	}  // namespace System
}  // namespace DotNetDupe

Generated by OpenCppCoverage (Version: 0.9.9.0)