// This file contains portions of code released by Microsoft under the MIT license as part
// of an open-sourcing initiative in 2014 of the C# core libraries.
// The original source was submitted to https://github.com/Microsoft/referencesource

using System.Diagnostics.Contracts;
using System.Collections;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System;

namespace System
{
	// String size type
#if BF_LARGE_STRINGS
	typealias int_strsize = int64;
	typealias uint_strsize = uint64;
#else
	typealias int_strsize = int32;
	typealias uint_strsize = uint32;
#endif

	enum UnicodeNormalizationOptions
	{
		Stable = (1<<1),
		Compat = (1<<2),
		Compose = (1<<3),
		Decompose = (1<<4),
		CaseFold = (1<<10),
		CharBound = (1<<11),
		Lump = (1<<12),

		NFD = Stable | Decompose,
		NFC = Stable | Compose,
		NFKD = Stable | Decompose | Compat,
		NFKC = Stable | Compose | Compat,
	}

	[Ordered]
	class String : IHashable, IFormattable, IPrintable
	{
		enum CreateFlags
		{
			None = 0,
			NullTerminate = 1
		}

		int_strsize mLength;
		uint_strsize mAllocSizeAndFlags;
		char8* mPtr = null;

		extern const String* sStringLiterals;
		extern const String* sIdStringLiterals;
		static String* sPrevInternLinkPtr; // For detecting changes to sStringLiterals for hot loads
		static Monitor sMonitor = new Monitor() ~ delete _;
		static HashSet<String> sInterns = new .() ~ delete _;
		static List<String> sOwnedInterns = new .() ~ DeleteContainerAndItems!(_);
		public const String Empty = "";

#if BF_LARGE_STRINGS
		const uint64 cSizeFlags = 0x3FFFFFFF'FFFFFFFF;
		const uint64 cDynAllocFlag = 0x80000000'00000000;
		const uint64 cStrPtrFlag = 0x40000000'00000000;
#else
		const uint32 cSizeFlags = 0x3FFFFFFF;
		const uint32 cDynAllocFlag = 0x80000000;
		const uint32 cStrPtrFlag = 0x40000000;
#endif

		[AllowAppend]
		public this(int count) // 0
		{
			int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
			char8* addlPtr = append char8[bufferSize]*;			
			mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
			mLength = 0;
		}

		[AllowAppend]
		public this()
		{
		    let bufferSize = 16 - sizeof(char8*);
#unwarn
		    char8* addlPtr = append char8[bufferSize]*;            
		    mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
		    mLength = 0;
		}

		[AllowAppend]
		public this(String str)
		{
			let count = str.mLength;
			int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
			char8* addlPtr = append char8[bufferSize]*;			
			Internal.MemCpy(Ptr, str.Ptr, count);
			mLength = count;
			mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
		}

		[AllowAppend]
		public this(String str, int offset)
		{
			Debug.Assert((uint)offset <= (uint)str.Length);
			let count = str.mLength - offset;
			int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
			char8* addlPtr = append char8[bufferSize]*;
			let ptr = Ptr;
			let srcPtr = str.Ptr;
			for (int_strsize i = 0; i < count; i++)
			    ptr[i] = srcPtr[i + offset];
			mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
			mLength = (int_strsize)count;
		}

		[AllowAppend]
		public this(String str, int offset, int count)
		{
			Debug.Assert((uint)offset <= (uint)str.Length);
			Debug.Assert(offset + count <= str.Length);

			int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
			char8* addlPtr = append char8[bufferSize]*;            
			let ptr = Ptr;
			let srcPtr = str.Ptr;
			for (int i = 0; i < count; i++)
			    ptr[i] = srcPtr[i + offset];
			mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
			mLength = (int_strsize)count;
		}

		[AllowAppend]
		public this(char8 c, int count)
		{
			int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
			char8* addlPtr = append char8[bufferSize]*;            
			let ptr = Ptr;
			for (int_strsize i = 0; i < count; i++)
			    ptr[i] = c;
			mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
			mLength = (int_strsize)count;
		}

		[AllowAppend]
		public this(char8* char8Ptr)
		{
			let count = Internal.CStrLen(char8Ptr);
			int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
			char8* addlPtr = append char8[bufferSize]*;            
			let ptr = Ptr;
			for (int_strsize i = 0; i < count; i++)
			    ptr[i] = char8Ptr[i];
			mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
			mLength = count;
		}

		[AllowAppend]
		public this(char8* char8Ptr, int count)
		{
		    int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
		    char8* addlPtr = append char8[bufferSize]*;            
			let ptr = Ptr;
		    for (int i = 0; i < count; i++)
		        ptr[i] = char8Ptr[i];
		    mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
		    mLength = (int_strsize)count;
		}

		[AllowAppend]
		public this(char16* char16Ptr)
		{
			let count = UTF16.GetLengthAsUTF8(char16Ptr);
			int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
			char8* addlPtr = append char8[bufferSize]*;
			mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
			mLength = 0;
			UTF16.Decode(char16Ptr, this);
		}

		[AllowAppend]
		public this(Span<char16> chars)
		{
			let count = UTF16.GetLengthAsUTF8(chars);
			int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
			char8* addlPtr = append char8[bufferSize]*;
			mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
			mLength = 0;
			UTF16.Decode(chars, this);
		}

		[AllowAppend]
		public this(StringView strView)
		{			
			let tryBufferSize = strView.Length - sizeof(char8*);
			let bufferSize = (tryBufferSize >= 0) ? tryBufferSize : 0;
#unwarn
			char8* addlPtr = append char8[bufferSize]*;			
			let ptr = Ptr;
			Internal.MemCpy(ptr, strView.Ptr, strView.Length);
			mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
			mLength = (int_strsize)strView.Length;
		}

		[AllowAppend]
		public this(StringView strView, CreateFlags flags)
		{			
			let count = strView.Length + (flags.HasFlag(.NullTerminate) ? 1 : 0);
			int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
			char8* addlPtr = append char8[bufferSize]*;			
			let ptr = Ptr;
			Internal.MemCpy(ptr, strView.Ptr, strView.Length);
			if (flags.HasFlag(.NullTerminate))
				ptr[strView.Length] = 0;
			mAllocSizeAndFlags = (uint_strsize)bufferSize + (int32)sizeof(char8*);
			mLength = (int32)strView.Length;
		}

		[AllowAppend]
		public this(StringView strView, int offset)
		{
			Debug.Assert((uint)offset <= (uint)strView.Length);
			
			let count = strView.Length - offset;
			int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
			char8* addlPtr = append char8[bufferSize]*;            
			let ptr = Ptr;
			let srcPtr = strView.Ptr;
			for (int i = 0; i < count; i++)
			    ptr[i] = srcPtr[i + offset];
			mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
			mLength = (int_strsize)count;
		}

		[AllowAppend]
		public this(StringView strView, int offset, int count)
		{
			Debug.Assert((uint)offset <= (uint)strView.Length);
			Debug.Assert(offset + count <= strView.Length);

			int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
			char8* addlPtr = append char8[bufferSize]*;            
			let ptr = Ptr;
			let srcPtr = strView.Ptr;
			for (int i = 0; i < count; i++)
			    ptr[i] = srcPtr[i + offset];
			mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
			mLength = (int_strsize)count;
		}

		[AllowAppend]
		public this(char8[] chars, int offset, int count)
		{
			int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
			char8* addlPtr = append char8[bufferSize]*;            
			let ptr = Ptr;
			for (int i = 0; i < count; i++)
			    ptr[i] = chars[i + offset];
			mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
			mLength = (int_strsize)count;
		}

		static int StrLengths(String[] strs)
		{
			int count = 0;
			for (var str in strs)
				count += str.Length;
			return count;
		}

		[AllowAppend]
		public this(params String[] strs)
		{
			int count = StrLengths(strs);
			int bufferSize = (count == 0) ? 0 : (count - 1) & ~(sizeof(char8*) - 1);
#unwarn
			char8* addlPtr = append char8[bufferSize]*;
			let ptr = Ptr;
			int curIdx = 0;
			for (var str in strs)
			{
				Internal.MemCpy(ptr + curIdx, str.Ptr, str.mLength);
				curIdx += str.Length;
			}
			
			mLength = (int_strsize)count;
			mAllocSizeAndFlags = (uint_strsize)bufferSize + (int_strsize)sizeof(char8*);
		}

		public ~this()
		{
			if (IsDynAlloc)
			    delete:this mPtr;
		}

		void FakeMethod ()
		{

		}

		protected virtual void* Alloc(int size, int align)
		{
			return new char8[size]* (?);
		}

		protected virtual void Free(void* ptr)
		{
			delete ptr;
		}

		public int Length
		{
			[Inline]
			get
			{
				return mLength;
			}

			set
			{
				Debug.Assert((uint)mLength <= (uint)value);
				mLength = (int_strsize)value;
			}
		}

		public int AllocSize
		{
			[Inline]
			get
			{
				return (int_strsize)(mAllocSizeAndFlags & cSizeFlags);
			}
		}

		public bool IsDynAlloc
		{
			[Inline]
			get
			{
				return (mAllocSizeAndFlags & cDynAllocFlag) != 0;
			}
		}

		public bool HasExternalPtr
		{
			get
			{
				return (mAllocSizeAndFlags & cStrPtrFlag) != 0;
			}
		}

		public char8* Ptr
		{
			//[Optimize]
			get
			{
				return ((mAllocSizeAndFlags & cStrPtrFlag) != 0) ? mPtr : (char8*)&mPtr;
			}
		}

		public bool IsWhiteSpace
		{
			get
			{
				let ptr = Ptr;
				for (int i = 0; i < mLength; i++)
					if (!ptr[i].IsWhiteSpace)
						return false;
				return true;
			}
		}

		public bool IsEmpty
		{
			get
			{
				return mLength == 0;
			}
		}

		static int GetHashCode(char8* ptr, int length)
		{
			int charsLeft = length;
			int hash = 0;
			char8* curPtr = ptr;
			let intSize = sizeof(int);
			while (charsLeft >= intSize)
			{
				hash = (hash ^ *((int*)curPtr)) + (hash * 16777619);
				charsLeft -= intSize;
				curPtr += intSize;
			}

			while (charsLeft > 1)
			{
				hash = ((hash ^ (int)*curPtr) << 5) - hash;
				charsLeft--;
				curPtr++;
			}

			return hash;
		}

		public int GetHashCode()
		{
			return GetHashCode(Ptr, mLength);
		}

		public override void ToString(String strBuffer)
		{
			strBuffer.Append(this);
		}

		public static void QuoteString(char8* ptr, int length, String outString)
		{
			outString.Append('"');
			for (int i < length)
			{
				char8 c = ptr[i];
				switch (c)
				{
				case '\'': outString.Append(@"\'");
                case '\"': outString.Append("\\\"");
                case '\\': outString.Append(@"\\");
                case '\0': outString.Append(@"\0");
                case '\a': outString.Append(@"\a");
                case '\b': outString.Append(@"\b");
                case '\f': outString.Append(@"\f");
                case '\n': outString.Append(@"\n");
                case '\r': outString.Append(@"\r");
                case '\t': outString.Append(@"\t");
                case '\v': outString.Append(@"\v");
                default: 
                	if (c < (char8)32)
                	{
                		outString.Append(@"\x");
                		outString.Append(sHexUpperChars[((int)c>>4) & 0xF]);
                		outString.Append(sHexUpperChars[(int)c & 0xF]);
                		break;
                	}
                	outString.Append(c);
				}
			}
			outString.Append('"');
		}

		public void QuoteString(String outString)
		{
			QuoteString(Ptr, Length, outString);
		}

		public static Result<void> UnQuoteString(char8* ptr, int length, String outString)
		{
			if (length < 2)
				return .Err;

			// Literal string?
			if ((ptr[0] == '@') && (ptr[1] == '"') && (ptr[length - 1] == '\"'))
			{
				outString.Append(ptr + 2, length - 3);
				return .Ok;
			}

			if ((*ptr != '\"') && (ptr[length - 1] != '\"'))
			{
				return .Err;
			}

			var ptr;
			ptr++;
			char8* endPtr = ptr + length - 2;

			while (ptr < endPtr)
			{
				char8 c = *(ptr++);
				if (c == '\\')
				{
					if (ptr == endPtr)
						return .Err;

					char8 nextC = *(ptr++);
					switch (nextC)
					{
					case '\'': outString.Append("'");
					case '\"': outString.Append("\"");
					case '\\': outString.Append("\\");
					case '0': outString.Append("\0");
					case 'a': outString.Append("\a");
					case 'b': outString.Append("\b");
					case 'f': outString.Append("\f");
					case 'n': outString.Append("\n");
					case 'r': outString.Append("\r");
					case 't': outString.Append("\t");
					case 'v': outString.Append("\v");
					default:
						return .Err;
					}
					continue;
				}

				outString.Append(c);
			}

			return .Ok;
		}

		public Result<void> UnQuoteString(String outString)
		{
			return UnQuoteString(Ptr, Length, outString);
		}

		static String sHexUpperChars = "0123456789ABCDEF";
		public void ToString(String outString, String format, IFormatProvider formatProvider)
		{
			if (format == "Q")
			{
				QuoteString(Ptr, mLength, outString);
				return;
			}
			outString.Append(this);
		}

		void IPrintable.Print(String outString)
		{
			String.QuoteString(Ptr, mLength, outString);
		}

		[AlwaysInclude]
		public char8* CStr()
		{
			EnsureNullTerminator();
			return Ptr;
		}

		public static implicit operator char8*(String str)
		{
		    if (str == null)
				return null;
			str.EnsureNullTerminator();
			return str.Ptr;
		}

		public static bool operator==(String s1, String s2)
		{
			return Equals(s1, s2);
		}

		public static int operator<=>(String s1, String s2)
		{
			return String.Compare(s1, s2, false);
		}

		public void Clear()
		{
			mLength = 0;
		}

		public void Set(String str)
		{
			if ((Object)this == (Object)str)
				return;
			mLength = 0;
            Append(str.Ptr, str.mLength);
		}

		public void Set(StringView str)
		{
			mLength = 0;
			Append(str.Ptr, str.Length);
		}

		public void MoveTo(String str, bool keepRef = false)
		{
			if (IsDynAlloc)
			{
				if (str.IsDynAlloc)
				{
					delete str.mPtr;
				}
	
				str.mPtr = mPtr;
				str.mAllocSizeAndFlags = mAllocSizeAndFlags;
				str.mLength = mLength;

				if (keepRef)
				{
					mAllocSizeAndFlags &= ~cDynAllocFlag;
				}
				else
				{
					mPtr = null;
					mAllocSizeAndFlags = (int_strsize)sizeof(char8*);
					mLength = 0;
				}
			}
			else
			{
				str.Set(this);
				if (!keepRef)
					Clear();
			}
		}

		public void Reference(String str)
		{
			if (IsDynAlloc)
				delete:this mPtr;
			mPtr = str.Ptr;
			mLength = str.mLength;
			mAllocSizeAndFlags = cStrPtrFlag;
		}

		public void Reference(char8* ptr, int length, int allocSize)
		{
			if (IsDynAlloc)
				delete:this mPtr;
			mPtr = ptr;
			mLength = (int_strsize)length;
			mAllocSizeAndFlags = cStrPtrFlag;
		}

		public void Reference(char8* ptr, int length)
		{
			if (IsDynAlloc)
				delete:this mPtr;
			mPtr = ptr;
			mLength = (int_strsize)length;
			mAllocSizeAndFlags = cStrPtrFlag;
		}

		public void Reference(StringView stringView)
		{
			Reference(stringView.Ptr, stringView.Length, stringView.Length);
		}

		public void Reference(char8* ptr)
		{
			if (IsDynAlloc)
				delete:this mPtr;
			mPtr = ptr;
			mLength = StrLen(ptr);
			mAllocSizeAndFlags = cStrPtrFlag;
		}

		// This is a fast way to remove characters are the beginning of a string, but only works for strings
		//  that are not dynamically allocated.  Mostly useful for parsing through Referenced strings quickly.
		public void AdjustPtr(int adjBytes)
		{
			Debug.Assert(!IsDynAlloc);
			Debug.Assert(AllocSize == 0); // Assert is reference
			Debug.Assert((uint)mLength >= (uint)adjBytes);
			mPtr += adjBytes;
			mLength -= (int_strsize)adjBytes;
		}

		int CalcNewSize(int minSize)
		{
			// Grow factor is 1.5
			int bumpSize = AllocSize;
			bumpSize += bumpSize / 2;
			return (bumpSize > minSize) ? bumpSize : minSize;
		}

		[Inline]
		void CalculatedReserve(int newSize)
		{
			if (newSize > AllocSize)
				Realloc(CalcNewSize(newSize));
		}

		void Realloc(int newSize)
		{
			Debug.Assert(AllocSize > 0, "String has been frozen");
			Debug.Assert((uint)newSize <= cSizeFlags);
			char8* newPtr = new:this char8[newSize]*;
			Internal.MemCpy(newPtr, Ptr, mLength);
			if (IsDynAlloc)
				delete:this mPtr;
			mPtr = newPtr;
			mAllocSizeAndFlags = (uint_strsize)newSize | cDynAllocFlag | cStrPtrFlag;
		}

		public void Reserve(int size)
		{
			if (size > AllocSize)
				Realloc(size);
		}

		void Realloc(char8* newPtr, int newSize)
		{
			Debug.Assert(AllocSize > 0, "String has been frozen");
			Debug.Assert((uint)newSize <= cSizeFlags);
			Internal.MemCpy(newPtr, Ptr, mLength);
			if (IsDynAlloc)
				delete:this mPtr;
			mPtr = newPtr;
			mAllocSizeAndFlags = (uint_strsize)newSize | cDynAllocFlag | cStrPtrFlag;
		}

		public static int_strsize StrLen(char8* str)
		{
			for (int_strsize i = 0; true; i++)
				if (str[i] == (char8)0)
					return i;
		}

		public void Append(StringView strView)
		{
			Append(strView.Ptr, strView.Length);
		}

		public void Append(Span<char16> utf16Str)
		{
			UTF16.Decode(utf16Str, this);
		}

		public void Append(char16* utf16Str)
		{
			UTF16.Decode(utf16Str, this);
		}

		public void Append(StringView str, int offset)
		{
			Debug.Assert((uint)offset <= (uint)str.[Friend]mLength);
			Append(str.Ptr + offset, str.[Friend]mLength - offset);
		}

		public void Append(StringView str, int offset, int length)
		{
			Debug.Assert((uint)offset + (uint)length <= (uint)str.[Friend]mLength);
			Append(str.Ptr + offset, length);
		}

		public void Append(char8* appendPtr)
		{
			int_strsize length = StrLen(appendPtr);
			int_strsize newCurrentIndex = mLength + length;
			char8* ptr;
			if (newCurrentIndex > AllocSize)
			{
				// This handles appending to ourselves, we invalidate 'ptr' after calling Realloc
				int newSize = CalcNewSize(newCurrentIndex);
				char8* newPtr = new:this char8[newSize]*;
				Internal.MemCpy(newPtr + mLength, appendPtr, length);
				Realloc(newPtr, newSize);
				ptr = newPtr;
			}
			else
			{
				ptr = Ptr;
				Internal.MemCpy(ptr + mLength, appendPtr, length);
			}
			mLength = newCurrentIndex;
		}

		public void Append(char8* appendPtr, int length)
		{
			int newCurrentIndex = mLength + length;
			char8* ptr;
			if (newCurrentIndex > AllocSize)
			{
				// This handles appending to ourselves, we invalidate 'ptr' after calling Realloc
				int newSize = CalcNewSize(newCurrentIndex);
				char8* newPtr = new:this char8[newSize]*;
				Internal.MemCpy(newPtr + mLength, appendPtr, length);
				Realloc(newPtr, newSize);
				ptr = newPtr;
			}
			else
			{
				ptr = Ptr;
				Internal.MemCpy(ptr + mLength, appendPtr, length);
			}
			mLength = (int_strsize)newCurrentIndex;
		}

		public void Append(char8[] arr, int idx, int length)
		{
			int newCurrentIndex = mLength + length;
			char8* ptr;
			if (newCurrentIndex > AllocSize)
			{
				// This handles appending to ourselves, we invalidate 'ptr' after calling Realloc
				int newSize = CalcNewSize(newCurrentIndex);
				char8* newPtr = new:this char8[newSize]*;
				Internal.MemCpy(newPtr + mLength, arr.CArray() + idx, length);
				Realloc(newPtr, newSize);
				ptr = newPtr;
			}
			else
			{
				ptr = Ptr;
				Internal.MemCpy(ptr + mLength, arr.CArray() + idx, length);
			}
			mLength = (int_strsize)newCurrentIndex;
		}

		public char8* PrepareBuffer(int bytes)
		{
			Debug.Assert(bytes >= 0);
			if (bytes <= 0)
				return null;
			int count = bytes;
			CalculatedReserve(mLength + count + 1);
			char8* ptr = Ptr + mLength;
			mLength += (int_strsize)bytes;
			return ptr;
		}

        // Appends a copy of this string at the end of this string builder.
		public void Append(String value)
		{
            //Contract.Ensures(Contract.Result<String>() != null);
			Append(value.Ptr, value.mLength);
		}

		public void Append(String str, int offset)
		{
			Debug.Assert((uint)offset <= (uint)str.mLength);
			Append(str.Ptr + offset, str.mLength - offset);
		}

		public void Append(String str, int offset, int length)
		{
			Debug.Assert((uint)offset <= (uint)str.mLength);
			Debug.Assert(length >= 0);
			Debug.Assert((uint)offset + (uint)length <= (uint)str.mLength);
			Append(str.Ptr + offset, length);
		}

		public void Append(params String[] strings)
		{
			for (var str in strings)
				Append(str);
		}

		public void Append(char8 c)
		{
			CalculatedReserve(mLength + 1);
			let ptr = Ptr;
			ptr[mLength++] = c;
		}

		public void Append(char8 c, int count)
		{
			if (count <= 0)
				return;

			CalculatedReserve(mLength + count);
			let ptr = Ptr;
			for (int_strsize i = 0; i < count; i++)
				ptr[mLength++] = c;
		}

		public void Append(char32 c)
		{
			if (c < (char32)0x80)
		    {
				CalculatedReserve(mLength + 1);
				let ptr = Ptr;
			    ptr[mLength++] = (char8)c;
			}
			else if (c < (char32)0x800)
		    {
				CalculatedReserve(mLength + 2);
				let ptr = Ptr;
			    ptr[mLength++] = (char8)(c>>6) | (char8)0xC0;
			    ptr[mLength++] = (char8)(c & (char8)0x3F) | (char8)0x80;
			}
			else if (c < (char32)0x10000)
		    {
				CalculatedReserve(mLength + 3);
				let ptr = Ptr;
			    ptr[mLength++] = (char8)(c>>12) | (char8)0xE0;
			    ptr[mLength++] = (char8)((c>>6) & (char8)0x3F) | (char8)0x80;
			    ptr[mLength++] = (char8)(c & (char8)0x3F) | (char8)0x80;
			}
			else if (c < (char32)0x110000)
		    {
				CalculatedReserve(mLength + 4);
				let ptr = Ptr;
			    ptr[mLength++] = (char8)((c>>18) | (char8)0xF0);
			    ptr[mLength++] = (char8)((c>>12) & (char8)0x3F) | (char8)0x80;
			    ptr[mLength++] = (char8)((c>>6) & (char8)0x3F) | (char8)0x80;
			    ptr[mLength++] = (char8)(c & (char8)0x3F) | (char8)0x80;
			}
		}

		public void Append(char32 c, int count)
		{
			if (count <= 0)
				return;

			if (count == 1)
			{
				Append(c);
				return;
			}

			int encodedLen = UTF8.GetEncodedLength(c);

			CalculatedReserve(mLength + count * encodedLen);

			let ptr = Ptr;
			for (int_strsize i = 0; i < count; i++)
			{
				if (c < (char32)0x80)
	            {
				    ptr[mLength++] = (char8)c;
				}
				else if (c < (char32)0x800)
	            {
				    ptr[mLength++] = (char8)(c>>6) | (char8)0xC0;
				    ptr[mLength++] = (char8)(c & (char8)0x3F) | (char8)0x80;
				}
				else if (c < (char32)0x10000)
	            {
				    ptr[mLength++] = (char8)(c>>12) | (char8)0xE0;
				    ptr[mLength++] = (char8)((c>>6) & (char8)0x3F) | (char8)0x80;
				    ptr[mLength++] = (char8)(c & (char8)0x3F) | (char8)0x80;
				}
				else if (c < (char32)0x110000)
	            {
				    ptr[mLength++] = (char8)((c>>18) | (char8)0xF0);
				    ptr[mLength++] = (char8)((c>>12) & (char8)0x3F) | (char8)0x80;
				    ptr[mLength++] = (char8)((c>>6) & (char8)0x3F) | (char8)0x80;
				    ptr[mLength++] = (char8)(c & (char8)0x3F) | (char8)0x80;
				}
			}
		}

		public void EnsureNullTerminator()
		{
			int allocSize = AllocSize;
			if ((allocSize == mLength) || (Ptr[mLength] != 0))
			{
				CalculatedReserve(mLength + 1);
				Ptr[mLength] = 0;
			}
		}

		public ref char8 this[int index]
		{
			get
			{
				Debug.Assert((uint)index < (uint)mLength);
				return ref Ptr[index];
			}

			set
			{
				Debug.Assert((uint)index < (uint)mLength);
				Ptr[index] = value;
			}
		}

		public void Concat(params Object[] objects)
		{
			// This reserves the correct number of characters if it can
			int totalLen = 0;
			for (var obj in objects)
			{
				if (var str = obj as String)
				{
					totalLen += str.Length;
				}
				else if (obj is char8)
				{
					totalLen++;
				}
				else
				{
					totalLen = 0;
					break;
				}
			}

			if (totalLen > 0)
				Reserve(mLength + totalLen);

			for (var obj in objects)
			{
				obj.ToString(this);
			}
		}

		public static String GetStringOrEmpty(String str)
		{
			return str ?? Empty;
		}

		public static bool IsNullOrEmpty(String str)
		{
			return (str == null) || (str.Length == 0);
		}

		public static bool IsNullOrWhiteSpace(String str)
		{
			if ((str == null) || (str.Length == 0))
				return true;

			let strPtr = str.Ptr;
			for (int_strsize i = 0; i < str.mLength; i++)
			{
				if (!strPtr[i].IsWhiteSpace)
					return false;
			}
			return true;
		}

		Result<void> FormatError()
		{
			return .Err;
		}

		/** Appends formatted text.
		* @param provider The format provider
		* @returns This method can fail if the format string is invalid.
		*/
		public Result<void> AppendF(IFormatProvider provider, StringView format, params Object[] args)
		{
			if (format.Ptr == null)
			{
				return .Err;
			}
			//Contract.Ensures(Contract.Result<StringBuilder>() != null);
			//Contract.EndContractBlock();

			int pos = 0;
			int len = format.Length;
			char8 ch = '\x00';

			/*ICustomFormatter cf = null;
			if (provider != null)
			{
				cf = (ICustomFormatter)provider.GetFormat(typeof(ICustomFormatter));
			}*/
			String s = null;
			String fmt = "";
			int autoArgIdx = 0;

			while (true)
			{
				int p = pos;
				int i = pos;
				while (pos < len)
				{
					ch = format[pos];

					pos++;
					if (ch == '}')
					{
						if (pos < len && format[pos] == '}') // Treat as escape character for }}
							pos++;
						else
							return FormatError();
					}

					if (ch == '{')
					{
						if (pos < len && format[pos] == '{') // Treat as escape character for {{
							pos++;
						else
						{
							pos--;
							break;
						}
					}

					Append(ch);
				}

				if (pos == len) break;
				pos++;
				int index = 0;
				if (pos == len || (ch = format[pos]) < '0' || ch > '9')
				{
					if ((pos < len) &&
						((ch == '}') || (ch == ':')))
						index = autoArgIdx++;
					else
						return FormatError();
				}
				else
				{
					repeat
					{
						index = index * 10 + ch - '0';
						pos++;
						if (pos == len) return FormatError();
						ch = format[pos];
					}
			        while (ch >= '0' && ch <= '9' && index < 1000000);
				}
				if (index >= args.Count) return FormatError();
				while (pos < len && (ch = format[pos]) == ' ') pos++;
				bool leftJustify = false;
				int width = 0;
				if (ch == ',')
				{
					pos++;
					while (pos < len && format[pos] == ' ') pos++;

					if (pos == len) return FormatError();
					ch = format[pos];
					if (ch == '-')
					{
						leftJustify = true;
						pos++;
						if (pos == len) return FormatError();
						ch = format[pos];
					}
					if (ch < '0' || ch > '9') return FormatError();
					repeat
					{
						width = width * 10 + ch - '0';
						pos++;
						if (pos == len) return FormatError();
						ch = format[pos];
					}
		            while (ch >= '0' && ch <= '9' && width < 1000000);
				}

				while (pos < len && (ch = format[pos]) == ' ') pos++;
				Object arg = args[index];
				if (ch == ':')
				{
					if (fmt == "")
						fmt = scope:: String(64);
					else
						fmt.Clear();

					bool isFormatEx = false;
					pos++;
					p = pos;
					i = pos;
					while (true)
					{
						if (pos == len) return FormatError();
						ch = format[pos];
						pos++;
						if (ch == '{')
						{
							isFormatEx = true;
							if (pos < len && format[pos] == '{')  // Treat as escape character for {{
								pos++;
							else
								return FormatError();
						}
						else if (ch == '}')
						{
							// We only treat '}}' as an escape character if the format had an opening '{'. Otherwise we just close on the first '}'
							if ((isFormatEx) && (pos < len && format[pos] == '}'))  // Treat as escape character for }}
								pos++;
							else
							{
								pos--;
								break;
							}
						}

						if (fmt == null)
						{
							fmt = scope:: String(0x100);
						}
						fmt.Append(ch);
					}
				}
				if (ch != '}') return FormatError();
				pos++;
				if (s == null)
					s = scope:: String(128);
				
				s.Clear();
				IFormattable formattableArg = arg as IFormattable;
				if (formattableArg != null)
					formattableArg.ToString(s, fmt, provider);
				else if (arg != null)
					arg.ToString(s);
				else
					s.Append("null");
				if (fmt != (Object)"")
					fmt.Clear();
				
				if (s == null) s = String.Empty;
				int pad = width - s.Length;
				if (!leftJustify && pad > 0) Append(' ', pad);
				Append(s);
				if (leftJustify && pad > 0) Append(' ', pad);
			}

			return .Ok;
		}

		public Result<void> AppendF(StringView format, params Object[] args)
		{
			return AppendF(null, format, params args);
		}

		public int IndexOf(StringView subStr, bool ignoreCase = false)
		{
			for (int ofs = 0; ofs <= Length - subStr.Length; ofs++)
			{
				if (Compare(Ptr+ofs, subStr.Length, subStr.Ptr, subStr.Length, ignoreCase) == 0)
					return ofs;
			}

			return -1;
		}

		public int IndexOf(StringView subStr, int startIdx, bool ignoreCase = false)
		{
			for (int ofs = startIdx; ofs <= Length - subStr.Length; ofs++)
			{
				if (Compare(Ptr+ofs, subStr.Length, subStr.Ptr, subStr.Length, ignoreCase) == 0)
					return ofs;
			}

			return -1;
		}

		public int Count(char8 c)
		{
			int count = 0;
			let ptr = Ptr;
			for (int i = 0; i < mLength; i++)
				if (ptr[i] == c)
					count++;
			return count;
		}

		public int IndexOf(char8 c, int startIdx = 0)
		{
			let ptr = Ptr;
			for (int i = startIdx; i < mLength; i++)
				if (ptr[i] == c)
					return i;
			return -1;
		}

		public int LastIndexOf(char8 c)
		{
			let ptr = Ptr;
			for (int i = mLength - 1; i >= 0; i--)
				if (ptr[i] == c)
					return i;
			return -1;
		}

		public int LastIndexOf(char8 c, int startCheck)
		{
			let ptr = Ptr;
			for (int i = startCheck; i >= 0; i--)
				if (ptr[i] == c)
					return i;
			return -1;
		}

		public int IndexOfAny(char8[] targets)
		{
			return IndexOfAny(targets, 0, mLength);
		}

		public int IndexOfAny(char8[] targets, int startIdx)
		{
			return IndexOfAny(targets, startIdx, mLength - startIdx);
		}

		public int IndexOfAny(char8[] targets, int startIdx, int count)
		{
			let ptr = Ptr;
			for (var i = startIdx; i < count; i++)
			{
				let ch = ptr[i];
				for (let tag in targets)
				{
					if (ch == tag)
						return i;
				}
			}

			return -1;
		}

		public bool Contains(String str)
		{
			return IndexOf(str) != -1;
		}

		public bool Contains(String str, bool ignoreCase)
		{
			return IndexOf(str, ignoreCase) != -1;
		}

		public bool Contains(char8 c)
		{
			let ptr = Ptr;
			for (int_strsize i = 0; i < mLength; i++)
				if (ptr[i] == c)
					return true;
			return false;
		}

		public void Replace(char8 c, char8 newC)
		{
			let ptr = Ptr;
			for (int_strsize i = 0; i < mLength; i++)
				if (ptr[i] == c)
					ptr[i] = newC;
		}

		void CaseConv(bool toUpper)
		{
			let ptr = Ptr;
			int addSize = 0;
			bool sizeChanged = false;

			for (int i = 0; i < mLength; i++)
			{
				let c = toUpper ? ptr[i].ToUpper : ptr[i].ToLower;
				if (c < '\x80')
				{
					ptr[i] = c;
					continue;
				}

				var (c32, decLen) = UTF8.Decode(ptr + i, mLength - i);
				if (c32 == (char32)-1)
					return; // Error

				let uc32 = toUpper ? c32.ToUpper : c32.ToLower;
				int encLen = UTF8.Encode(uc32, .(ptr + i, decLen));
				if (encLen != decLen)
				{
					// Put old char back
					sizeChanged = true;
					addSize += encLen - decLen;
					encLen = UTF8.Encode(c32, .(ptr + i, decLen));
				}

				i += encLen - 1;
			}
			if (!sizeChanged)
				return;

			// Handle 'slow' resize case
			if (sizeChanged)
			{
				int newSize = mLength + addSize + 1;
				char8* newPtr = new:this char8[newSize]*;

				int outIdx = 0;
				for (int i = 0; i < mLength; i++)
				{
					let c = toUpper ? ptr[i].ToUpper : ptr[i].ToLower;
					if (c < '\x80')
					{
						newPtr[outIdx++] = c;
						continue;
					}

					var (c32, decLen) = UTF8.Decode(ptr + i, mLength - i);
					if (c32 == (char32)-1)
						return; // Error

					c32 = toUpper ? c32.ToUpper : c32.ToLower;
					int encLen = UTF8.Encode(c32, .(newPtr + outIdx, newSize - outIdx));
					i += decLen - 1;
					outIdx += encLen;
				}
				newPtr[outIdx] = '\0';

				if (IsDynAlloc)
					delete mPtr;
				mPtr = newPtr;
				mAllocSizeAndFlags = (uint_strsize)newSize | cDynAllocFlag | cStrPtrFlag;
			}
		}

		public void ToUpper()
		{
			CaseConv(true);
		}

		public void ToLower()
		{
			CaseConv(false);
		}

		//[CLink]
		//static extern int utf8proc_map(char8* str, int strlen, out char8* outStr, int options);

		static extern int UTF8GetAllocSize(char8* str, int strlen, int32 options);
		static extern int UTF8Map(char8* str, int strlen, char8* outStr, int outSize, int32 options);

		public Result<void> Normalize(UnicodeNormalizationOptions unicodeNormalizationOptions = .NFC)
		{
			int allocSize = UTF8GetAllocSize(Ptr, mLength, (int32)unicodeNormalizationOptions);
			if (allocSize < 0)
				return .Err;
			char8* newStr = (char8*)Alloc(allocSize, 1);
			int newLen = UTF8Map(Ptr, mLength, newStr, allocSize, (int32)unicodeNormalizationOptions);

			if (IsDynAlloc)
				delete:this mPtr;
			mPtr = newStr;
			mLength = (int_strsize)newLen;
			mAllocSizeAndFlags = (uint32)(allocSize) | cDynAllocFlag | cStrPtrFlag;
			return .Ok;
		}

		public Result<void> Normalize(String destStr, UnicodeNormalizationOptions unicodeNormalizationOptions = .NFC)
		{
			if (destStr == (Object)this)
				return Normalize(unicodeNormalizationOptions);

			int allocSize = UTF8GetAllocSize(Ptr, mLength, (int32)unicodeNormalizationOptions);
			if (allocSize < 0)
			{
				// Just append unnormalized
				destStr.Append(this);
				return .Err;
			}
			char8* newStr = (char8*)Alloc(allocSize, 1);
			int newLen = UTF8Map(Ptr, mLength, newStr, allocSize, (int32)unicodeNormalizationOptions);

			if (destStr.IsDynAlloc)
				delete:destStr destStr.mPtr;
			destStr.mPtr = newStr;
			destStr.mLength = (int_strsize)newLen;
			destStr.mAllocSizeAndFlags = (uint_strsize)(newLen + 1) | cDynAllocFlag | cStrPtrFlag;
			return .Ok;
		}

		public void Remove(int startIdx, int length)
		{
			Contract.Requires((startIdx >= 0) && (length >= 0) && (startIdx + length <= mLength));
			int moveCount = mLength - startIdx - length;
			let ptr = Ptr;
			if (moveCount > 0)
				Internal.MemMove(ptr + startIdx, ptr + startIdx + length, mLength - startIdx - length);
			mLength -= (int_strsize)length;
		}

		public void Remove(int char8Idx)
		{
			Remove(char8Idx, 1);
		}

		public void RemoveToEnd(int startIdx)
		{
			Remove(startIdx, mLength - startIdx);
		}

		public void RemoveFromEnd(int length)
		{
			Remove(mLength - length, length);
		}

		public void Insert(int idx, StringView addString)
		{
			Contract.Requires(idx >= 0);

			int_strsize length = (int_strsize)addString.Length;
			int_strsize newLength = mLength + length;
			CalculatedReserve(newLength);

			let moveChars = mLength - idx;
			let ptr = Ptr;
			if (moveChars > 0)
				Internal.MemMove(ptr + idx + length, ptr + idx, moveChars);
			Internal.MemCpy(ptr + idx, addString.Ptr, length);
			mLength = newLength;
		}

		public void Insert(int_strsize idx, char8 c)
		{
			Contract.Requires(idx >= 0);

			let newLength = mLength + 1;
			CalculatedReserve(newLength);

			let moveChars = mLength - idx;
			let ptr = Ptr;
			if (moveChars > 0)
				Internal.MemMove(ptr + idx + 1, ptr + idx, moveChars);
			ptr[idx] = c;
			mLength = newLength;
		}

		public void Insert(int_strsize idx, char8 c, int count)
		{
			Contract.Requires(idx >= 0);

			if (count <= 0)
				return;

			let newLength = mLength + (int_strsize)count;
			CalculatedReserve(newLength);

			let moveChars = mLength - idx;
			let ptr = Ptr;
			if (moveChars > 0)
				Internal.MemMove(ptr + idx + count, ptr + idx, moveChars);
			for (let i < count)
				ptr[idx + i] = c;
			mLength = newLength;
		}

		public void Insert(int_strsize idx, char32 c)
		{
			Contract.Requires(idx >= 0);

			let moveChars = mLength - idx;
			if (c < (char32)0x80)
		    {
				CalculatedReserve(mLength + 1);
				let ptr = Ptr;
				if (moveChars > 0)
					Internal.MemMove(ptr + idx + 1, ptr + idx, moveChars);
			    ptr[idx] = (char8)c;
				mLength++;
			}
			else if (c < (char32)0x800)
		    {
				CalculatedReserve(mLength + 2);
				let ptr = Ptr;
				if (moveChars > 0)
					Internal.MemMove(ptr + idx + 2, ptr + idx, moveChars);
			    ptr[idx] = (char8)(c>>6) | (char8)0xC0;
			    ptr[idx + 1] = (char8)(c & (char8)0x3F) | (char8)0x80;
				mLength += 2;
			}
			else if (c < (char32)0x10000)
		    {
				CalculatedReserve(mLength + 3);
				let ptr = Ptr;
				if (moveChars > 0)
					Internal.MemMove(ptr + idx + 3, ptr + idx, moveChars);
			    ptr[idx] = (char8)(c>>12) | (char8)0xE0;
			    ptr[idx + 1] = (char8)((c>>6) & (char8)0x3F) | (char8)0x80;
			    ptr[idx + 2] = (char8)(c & (char8)0x3F) | (char8)0x80;
				mLength += 3;
			}
			else if (c < (char32)0x110000)
		    {
				CalculatedReserve(mLength + 4);
				let ptr = Ptr;
				if (moveChars > 0)
					Internal.MemMove(ptr + idx + 4, ptr + idx, moveChars);
			    ptr[idx] = (char8)((c>>18) | (char8)0xF0);
			    ptr[idx + 1] = (char8)((c>>12) & (char8)0x3F) | (char8)0x80;
			    ptr[idx + 2] = (char8)((c>>6) & (char8)0x3F) | (char8)0x80;
			    ptr[idx + 3] = (char8)(c & (char8)0x3F) | (char8)0x80;
				mLength += 4;
			}
		}

		public void Insert(int_strsize idx, char32 c, int count)
		{
			Contract.Requires(idx >= 0);

			if (count <= 0)
				return;

			if (count == 1)
			{
				Insert(idx, c);
				return;
			}

			let encodedLen = UTF8.GetEncodedLength(c);
			let newLength = mLength + (int_strsize)(count * encodedLen);
			CalculatedReserve(newLength);

			let moveChars = mLength - idx;
			let ptr = Ptr;
			if (moveChars > 0)
				Internal.MemMove(ptr + idx + count * encodedLen, ptr + idx, moveChars);
			for (let i < count)
			{
				let ofs = idx + i * encodedLen;
				if (c < (char32)0x80)
	            {
				    ptr[ofs] = (char8)c;
				}
				else if (c < (char32)0x800)
	            {
				    ptr[ofs] = (char8)(c>>6) | (char8)0xC0;
				    ptr[ofs + 1] = (char8)(c & (char8)0x3F) | (char8)0x80;
				}
				else if (c < (char32)0x10000)
	            {
				    ptr[ofs] = (char8)(c>>12) | (char8)0xE0;
				    ptr[ofs + 1] = (char8)((c>>6) & (char8)0x3F) | (char8)0x80;
				    ptr[ofs + 2] = (char8)(c & (char8)0x3F) | (char8)0x80;
				}
				else if (c < (char32)0x110000)
	            {
				    ptr[ofs] = (char8)((c>>18) | (char8)0xF0);
				    ptr[ofs + 1] = (char8)((c>>12) & (char8)0x3F) | (char8)0x80;
				    ptr[ofs + 2] = (char8)((c>>6) & (char8)0x3F) | (char8)0x80;
				    ptr[ofs + 3] = (char8)(c & (char8)0x3F) | (char8)0x80;
				}
			}

			mLength = newLength;
		}

		static bool EqualsHelper(char8* a, char8* b, int length)
		{
			for (int i = 0; i < length; i++)
				if (a[i] != b[i])
					return false;
			return true;
		}

		static bool EqualsIgnoreCaseHelper(char8* a, char8* b, int length)
		{
			char8* curA = a;
			char8* curB = b;
			int curLength = length;

            /*Contract.Requires(strA != null);
            Contract.Requires(strB != null);
            Contract.EndContractBlock();*/
			while (curLength != 0)
			{
				int_strsize charA = (int_strsize)*curA;
				int_strsize charB = (int_strsize)*curB;

                //Contract.Assert((char8A | char8B) <= 0x7F, "strings have to be ASCII");

                // uppercase both char8s - notice that we need just one compare per char8
				if ((uint32)(charA - 'a') <= (uint32)('z' - 'a')) charA -= 0x20;
				if ((uint32)(charB - 'a') <= (uint32)('z' - 'a')) charB -= 0x20;

                //Return the (case-insensitive) difference between them.
				if (charA != charB)
					return false;

                // Next char8
				curA++;curB++;
				curLength--;
			}

			return true;
		}

		private static int CompareOrdinalIgnoreCaseHelper(String strA, String strB)
		{
            /*Contract.Requires(strA != null);
            Contract.Requires(strB != null);
            Contract.EndContractBlock();*/
			int_strsize length = Math.Min(strA.mLength, strB.mLength);

			char8* a = strA.Ptr;
			char8* b = strB.Ptr;

			while (length != 0)
			{
				int_strsize charA = (int_strsize)*a;
				int_strsize charB = (int_strsize)*b;

                //Contract.Assert((char8A | char8B) <= 0x7F, "strings have to be ASCII");

                // uppercase both char8s - notice that we need just one compare per char8
				if ((uint32)(charA - 'a') <= (uint32)('z' - 'a')) charA -= 0x20;
				if ((uint32)(charB - 'a') <= (uint32)('z' - 'a')) charB -= 0x20;

                //Return the (case-insensitive) difference between them.
				if (charA != charB)
					return charA - charB;

                // Next char8
				a++;b++;
				length--;
			}

			return strA.mLength - strB.mLength;
		}

		private static int CompareOrdinalIgnoreCaseHelper(char8* strA, int lengthA, char8* strB, int lengthB)
		{
			char8* a = strA;
			char8* b = strB;
			int length = Math.Min(lengthA, lengthB);

			while (length != 0)
			{
				int_strsize charA = (int_strsize)*a;
				int_strsize charB = (int_strsize)*b;

			    //Contract.Assert((char8A | char8B) <= 0x7F, "strings have to be ASCII");
			    // uppercase both char8s - notice that we need just one compare per char8
				if ((uint32)(charA - 'a') <= (uint32)('z' - 'a')) charA -= 0x20;
				if ((uint32)(charB - 'a') <= (uint32)('z' - 'a')) charB -= 0x20;

			    //Return the (case-insensitive) difference between them.
				if (charA != charB)
					return charA - charB;

			    // Next char8
				a++;b++;
				length--;
			}

			return lengthA - lengthB;
		}

		private static int CompareOrdinalIgnoreCaseHelper(String strA, int indexA, int lengthA, String strB, int indexB, int lengthB)
		{
			return CompareOrdinalIgnoreCaseHelper(strA.Ptr + indexA, lengthA, strB.Ptr + indexB, lengthB);
		}

		private static int CompareOrdinalHelper(char8* strA, int lengthA, char8* strB, int lengthB)
		{
			char8* a = strA;
			char8* b = strB;
			int length = Math.Min(lengthA, lengthB);

			while (length != 0)
			{
				int_strsize char8A = (int_strsize)*a;
				int_strsize char8B = (int_strsize)*b;

			    //Return the (case-insensitive) difference between them.
				if (char8A != char8B)
					return char8A - char8B;

			    // Next char8
				a++;b++;
				length--;
			}

			return lengthA - lengthB;
		}

		public static int Compare(char8* strA, int lengthA, char8* strB, int lengthB, bool ignoreCase)
		{
			if (ignoreCase)
				return CompareOrdinalIgnoreCaseHelper(strA, lengthA, strB, lengthB);
			return CompareOrdinalHelper(strA, lengthA, strB, lengthB);
		}

		private static int CompareOrdinalHelper(String strA, int indexA, int lengthA, String strB, int indexB, int lengthB)
		{
		    return CompareOrdinalHelper(strA.Ptr + indexA, lengthA, strB.Ptr + indexB, lengthB);
		}

		public int CompareTo(String strB, bool ignoreCase = false)
		{
			if (ignoreCase)
				return CompareOrdinalIgnoreCaseHelper(Ptr, Length, strB.Ptr, strB.Length);
			return CompareOrdinalHelper(Ptr, Length, strB.Ptr, strB.Length);
		}

		public static int Compare(String strA, String strB, bool ignoreCase)
		{
			//they can't both be null;
			if (strA == null)
			{
			    return -1;
			}

			if (strB == null)
			{
			    return 1;
			}

			if (ignoreCase)
				return CompareOrdinalIgnoreCaseHelper(strA.Ptr, strA.Length, strB.Ptr, strB.Length);
			return CompareOrdinalHelper(strA.Ptr, strA.Length, strB.Ptr, strB.Length);

			//return Compare(strA, 0, strB, 0, strB.Length, ignoreCase);
		}

		public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase)
        {
		    int lengthA = length;
		    int lengthB = length;

		    if (strA!=null) 
            {
		        if (strA.Length - indexA < lengthA) 
                {
		          lengthA = (strA.Length - indexA);
		        }
		    }

		    if (strB!=null) 
            {
		        if (strB.Length - indexB < lengthB) 
                {
		            lengthB = (strB.Length - indexB);
		        }
		    }

			if (ignoreCase)
				return CompareOrdinalIgnoreCaseHelper(strA, indexA, lengthA, strB, indexB, lengthB);
			return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);

		    /*if (ignoreCase) {
		        return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
		    }
		    return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);*/
		}

		public bool Equals(String b, StringComparison comparisonType = StringComparison.Ordinal)
		{
			return String.Equals(this, b, comparisonType);
		}

		public static bool Equals(String a, String b, StringComparison comparisonType = StringComparison.Ordinal)
		{
			if ((Object)a == (Object)b)
				return true;
			if ((Object)a == null || (Object)b == null)
				return false;
			if (a.mLength != b.mLength)
				return false;
			if (comparisonType == StringComparison.OrdinalIgnoreCase)
				return EqualsIgnoreCaseHelper(a.Ptr, b.Ptr, a.mLength);
			return EqualsHelper(a.Ptr, b.Ptr, a.mLength);
		}

		public bool StartsWith(StringView b, StringComparison comparisonType = StringComparison.Ordinal)
		{
			if (mLength < b.[Friend]mLength)
				return false;
			if (comparisonType == StringComparison.OrdinalIgnoreCase)
				return EqualsIgnoreCaseHelper(this.Ptr, b.Ptr, b.Length);
			return EqualsHelper(this.Ptr, b.Ptr, b.[Friend]mLength);
		}

		public bool EndsWith(StringView b, StringComparison comparisonType = StringComparison.Ordinal)
		{
			if (mLength < b.[Friend]mLength)
				return false;
			if (comparisonType == StringComparison.OrdinalIgnoreCase)
				return EqualsIgnoreCaseHelper(Ptr + mLength - b.[Friend]mLength, b.Ptr, b.[Friend]mLength);
			return EqualsHelper(this.Ptr + mLength - b.[Friend]mLength, b.Ptr, b.[Friend]mLength);
		}

		public bool StartsWith(char8 c)
		{
			if (mLength == 0)
				return false;
			return Ptr[0] == c;
		}

		public bool StartsWith(char32 c)
		{
			if (c < '\x80')
				return StartsWith((char8)c);
			if (mLength == 0)
				return false;
			return UTF8.Decode(Ptr, mLength).c == c;
		}

		public bool EndsWith(char8 c)
		{
			if (mLength == 0)
				return false;
			return Ptr[mLength - 1] == c;
		}

		public bool EndsWith(char32 c)
		{
			if (c < '\x80')
				return EndsWith((char8)c);
			if (mLength == 0)
				return false;
			char8* ptr = Ptr;
			int idx = mLength - 1;
			while ((idx > 0) && ((uint8)ptr[idx] & 0xC0 == 0x80))
				idx--;
			return UTF8.Decode(ptr + idx, mLength - idx).c == c;
		}

		public void ReplaceLargerHelper(String find, String replace)
		{
			List<int> replaceEntries = scope List<int>(8192);
			
			int_strsize moveOffset = replace.mLength - find.mLength;

			for (int startIdx = 0; startIdx <= mLength - find.mLength; startIdx++)
			{
				if (EqualsHelper(Ptr + startIdx, find.Ptr, find.mLength))
				{
					replaceEntries.Add(startIdx);
					startIdx += find.mLength - 1;
				}
			}

			if (replaceEntries.Count == 0)
				return;

			int destLength = mLength + moveOffset * replaceEntries.Count;
			int needSize = destLength;
			CalculatedReserve(needSize);

			let replacePtr = replace.Ptr;
			let ptr = Ptr;

			int lastDestStartIdx = destLength;
			for (int moveIdx = replaceEntries.Count - 1; moveIdx >= 0; moveIdx--)
			{
				int srcStartIdx = replaceEntries[moveIdx];
				int srcEndIdx = srcStartIdx + find.mLength;
				int destStartIdx = srcStartIdx + moveIdx * moveOffset;
				int destEndIdx = destStartIdx + replace.mLength;

				for (int i = lastDestStartIdx - destEndIdx - 1; i >= 0; i--)
					ptr[destEndIdx + i] = ptr[srcEndIdx + i];

				for (int i < replace.mLength)
					ptr[destStartIdx + i] = replacePtr[i];

				lastDestStartIdx = destStartIdx;
			}

			mLength = (int_strsize)destLength;
		}

		public void Replace(String find, String replace)
		{
			if (replace.mLength > find.mLength)
			{
				ReplaceLargerHelper(find, replace);
				return;
			}

			let ptr = Ptr;
			let findPtr = find.Ptr;
			let replacePtr = replace.Ptr;

			int_strsize inIdx = 0;
			int_strsize outIdx = 0;

			if (find.Length == 1)
			{
				// Optimized version for single-character replacements
				char8 findC = find[0];
				while (inIdx < mLength)
				{
					if (ptr[inIdx] == findC)
					{
						for (int i = 0; i < replace.mLength; i++)
							ptr[outIdx++] = replacePtr[i];

						inIdx += find.mLength;
					}
					else if (inIdx == outIdx)
					{
						++inIdx;
						++outIdx;
					}
					else // We need to physically move characters once we've found an equal span
					{
						ptr[outIdx++] = ptr[inIdx++];
					}
				}
			}
			else
			{
				while (inIdx <= mLength - find.mLength)
				{
					if (EqualsHelper(ptr + inIdx, findPtr, find.mLength))
					{
						for (int i = 0; i < replace.mLength; i++)
							ptr[outIdx++] = replacePtr[i];

						inIdx += find.mLength;
					}
					else if (inIdx == outIdx)
					{
						++inIdx;
						++outIdx;
					}
					else // We need to physically move characters once we've found an equal span
					{
						ptr[outIdx++] = ptr[inIdx++];
					}
				}
			}

			while (inIdx < mLength)
			{
				if (inIdx == outIdx)
				{
					++inIdx;
					++outIdx;
				}
				else
				{
					ptr[outIdx++] = ptr[inIdx++];
				}
			}

			mLength = outIdx;
		}

		public void TrimEnd()
		{
			let ptr = Ptr;
			for (int i = mLength - 1; i >= 0; i--)
			{
				char8 c = ptr[i];
				if (c >= (char8)0x80)
				{
					var (c32, idx, len) = GetChar32WithBacktrack(i);
					if (!c32.IsWhiteSpace)
					{
						if (i < mLength - 1)
							RemoveToEnd(i + 1);
						return;
					}
					i = idx;
				}
				else if (!c.IsWhiteSpace)
				{
					if (i < mLength - 1)
						RemoveToEnd(i + 1);
					return;
				}
			}
			Clear();
		}

		public void TrimStart()
		{
			let ptr = Ptr;
			for (int i = 0; i < mLength; i++)
			{
				char8 c = ptr[i];
				if (c >= (char8)0x80)
				{
					var (c32, len) = GetChar32(i);
					if (!c32.IsWhiteSpace)
					{
						if (i > 0)
							Remove(0, i);
						return;
					}
					i += len - 1;
				}
				else if (!c.IsWhiteSpace)
				{
					if (i > 0)
						Remove(0, i);
					return;
				}
			}
			Clear();
		}

		public void Trim()
		{
			TrimStart();
			TrimEnd();
		}

		public void Join(StringView sep, IEnumerator<String> enumerable)
		{
			bool isFirst = true;
			for (var str in enumerable)
			{
				if (!isFirst)
					Append(sep);
				Append(str);
				isFirst = false;
			}
		}

		public void Join(StringView sep, IEnumerator<StringView> enumerable)
		{
			bool isFirst = true;
			for (var str in enumerable)
			{
				if (!isFirst)
					Append(sep);
				Append(str);
				isFirst = false;
			}
		}

		public void Join(String separator, params String[] values)
		{
			for (int i = 0; i < values.Count; i++)
			{
				if (i > 0)
					Append(separator);
				values[i].ToString(this);
			}
		}

		public StringSplitEnumerator Split(char8 c)
		{
			return StringSplitEnumerator(Ptr, Length, c, Int32.MaxValue, StringSplitOptions.None);
		}

		public StringSplitEnumerator Split(char8 separator, int count)
		{
			return StringSplitEnumerator(Ptr, Length, separator, count, StringSplitOptions.None);
		}

		public StringSplitEnumerator Split(char8 separator, StringSplitOptions options)
		{
			return StringSplitEnumerator(Ptr, Length, separator, Int32.MaxValue, options);
		}

		public StringSplitEnumerator Split(char8 separator, int count, StringSplitOptions options)
		{
			return StringSplitEnumerator(Ptr, Length, separator, count, options);
		}

		public StringSplitEnumerator Split(params char8[] separators)
		{
			return StringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, StringSplitOptions.None);
		}

		public StringSplitEnumerator Split(char8[] separators)
		{
			return StringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, StringSplitOptions.None);
		}

		public StringSplitEnumerator Split(char8[] separators, int count)
		{
			return StringSplitEnumerator(Ptr, Length, separators, count, StringSplitOptions.None);
		}

		public StringSplitEnumerator Split(char8[] separators, int count, StringSplitOptions options)
		{
			return StringSplitEnumerator(Ptr, Length, separators, count, options);
		}

		public static mixin NewOrSet(var target, var source)
		{
			if (target == null)
				target = new String(source);
			else
				target.Set(source);
		}

		public static mixin DupIfReferenceEqual(var inStr, var outStr)
		{
			if ((Object)inStr == (Object)outStr)
				inStr = scope:: String(outStr);
		}

		public static mixin StackSplit(var target, var splitChar)
		{
			var strings = scope:mixin List<String>();
			for (var strView in target.Split(splitChar))
				strings.Add(scope:mixin String(strView));
			strings
		}

		public mixin Split(var splitChar)
		{
			int count = 0;
			for (this.Split(splitChar))
				count++;
			var stringArr = scope:mixin StringView[count];
			int idx = 0;
			for (var strView in Split(splitChar))
				stringArr[idx++] = strView;
			stringArr
		}
		
		public mixin ToScopedNativeWChar()
		{
			int encodedLen = UTF16.GetEncodedLen(this);
			char16* buf;
			if (encodedLen < 128)
			{
				buf = scope:mixin char16[encodedLen]* ( ? );
			}
			else
			{
				buf = new char16[encodedLen]* ( ? );
				defer:mixin delete buf;
			}

			UTF16.Encode(this, buf, encodedLen);
			buf
		}

		public static bool Equals(char8* str1, char8* str2)
		{
			for (int i = 0; true; i++)
			{
				char8 c = str1[i];
				char8 c2 = str2[i];
				if (c != c2)
					return false;
				if ((c == (char8)0) || (c2 == (char8)0))
					return ((c == (char8)0) && (c2 == (char8)0));
			}
		}

		public RawEnumerator RawChars
		{
			get
			{
				return RawEnumerator(Ptr, 0, mLength);
			}
		}

		public UTF8Enumerator DecodedChars
		{
			get
			{
				return UTF8Enumerator(Ptr, 0, mLength);
			}
		}

		public UTF8Enumerator DecodedChars(int startIdx)
		{
			return UTF8Enumerator(Ptr, startIdx, mLength);
		}

		public bool HasMultibyteChars()
		{
			char8* ptr = Ptr;
			int len = Length;
			for (int i < len)
				if (ptr[i] >= '\x80')
					return true;
			return false;
		}

		public (char32 c, int8 length) GetChar32(int idx)
		{
			Debug.Assert((uint)idx < (uint)mLength);
			char8* ptr = Ptr;
			char8 c = ptr[idx];
			if (c < '\x80')
				return (c, 1);
			if (((uint8)ptr[idx] & 0xC0) == 0x80)
				return (0, 0); // Invalid UTF8 data
			return UTF8.Decode(ptr + idx, mLength - idx);
		}

		public (char32 c, int idx, int8 length) GetChar32WithBacktrack(int idx)
		{
			Debug.Assert((uint)idx < (uint)mLength);
			char8* ptr = Ptr;
			char8 c = ptr[idx];
			if (c < '\x80')
				return (c, idx, 1);
			var idx;
			while (((uint8)ptr[idx] & 0xC0) == 0x80)
			{
				if (idx == 0) // Invalid UTF8 data
					return (0, 0, 0);
				idx--;
			}
			let (c32, len) = UTF8.Decode(ptr + idx, mLength - idx);
			return (c32, idx, len);
		}

		public (int startIdx, int length) GetCodePointSpan(int idx)
		{
			char8* ptr = Ptr;
			int startIdx = idx;

			// Move to start of char
			while (startIdx >= 0)
			{
				char8 c = ptr[startIdx];
				if (((uint8)c & 0x80) == 0)
					return (startIdx, 1);
				if (((uint8)c & 0xC0) != 0x80)
					break;
				startIdx--;
			}

			return (startIdx, UTF8.GetDecodedLength(ptr + startIdx));
		}

		public (int startIdx, int length) GetGraphemeClusterSpan(int idx)
		{
			char8* ptr = Ptr;
			int startIdx = idx;

			// Move to start of char
			while (startIdx >= 0)
			{
				char8 c = ptr[startIdx];
				if (((uint8)c & 0x80) == 0)
				{
					// This is the simple and fast case - ASCII followed by the string end or more ASCII
					if ((startIdx == mLength - 1) || ((uint8)ptr[startIdx + 1] & 0x80) == 0)
						return (startIdx, 1);
					break;
				}
				if (((uint8)c & 0xC0) != 0x80)
				{
					let (c32, cLen) = UTF8.Decode(ptr + startIdx, mLength - startIdx);
					if (!c32.IsCombiningMark)
						break;
				}
				startIdx--;
			}

			int curIdx = startIdx;
			while (true)
			{
				let (c32, cLen) = UTF8.Decode(ptr + curIdx, mLength - curIdx);
				int nextIdx = curIdx + cLen;
                if ((curIdx != startIdx) && (!c32.IsCombiningMark))
					return (startIdx, curIdx - startIdx);
				if (nextIdx == mLength)
					return (startIdx, nextIdx - startIdx);
				curIdx = nextIdx;
			}
		}

		static void CheckLiterals(String* ptr)
		{
			var ptr;

			String* prevList = *((String**)(ptr++));
			if (prevList != null)
			{
				CheckLiterals(prevList);
			}

			while (true)
			{
				String str = *(ptr++);
				if (str == null)
					break;
				sInterns.Add(str);
			}
		}

		public String Intern()
		{
			using (sMonitor.Enter())
			{
				bool needsLiteralPass = sInterns.Count == 0;
				String* internalLinkPtr = *((String**)(sStringLiterals));
				if (internalLinkPtr != sPrevInternLinkPtr)
				{
					sPrevInternLinkPtr = internalLinkPtr;
					needsLiteralPass = true;
				}
				if (needsLiteralPass)
					CheckLiterals(sStringLiterals);

				String* entryPtr;
				if (sInterns.TryAdd(this, out entryPtr))
				{
					String result = new String(mLength + 1);
					result.Append(this);
					result.EnsureNullTerminator();
					*entryPtr = result;
					sOwnedInterns.Add(result);
					return result;
				}
				return *entryPtr;
			}
		}

		public struct RawEnumerator : IRefEnumerator<char8*>, IEnumerator<char8>
		{
			char8* mPtr;
			int_strsize mIdx;
			int_strsize mLength;

			public this(char8* ptr, int idx, int length)
			{
				mPtr = ptr;
				mIdx = (int_strsize)idx - 1;
				mLength = (int_strsize)length;
			}

			public char8 Current
			{
			    get
			    {
			        return mPtr[mIdx];
			    }

				set
				{
					mPtr[mIdx] = value;
				}
			}

			public ref char8 CurrentRef
			{
			    get
			    {
			        return ref mPtr[mIdx];
			    }
			}

			public int Index
			{
				get
				{
					return mIdx;
				}				
			}

			public int Length
			{
				get
				{
					return mLength;
				}				
			}

			public void Dispose()
			{

			}

			public void Reset()
			{

			}

			public bool MoveNext() mut
			{
				++mIdx;
				if (mIdx >= mLength)
					return false;
				return true;
			}

			public Result<char8> GetNext() mut
			{
				if (!MoveNext())
					return .Err;
				return Current;
			}

			public Result<char8*> GetNextRef() mut
			{
				if (!MoveNext())
					return .Err;
				return &CurrentRef;
			}
		}

		public struct UTF8Enumerator : IEnumerator<char32>
		{
			char8* mPtr;
			int_strsize mNextIndex;
			int_strsize mLength;
			char32 mChar;

			public this(char8* ptr, int idx, int length)
			{
				mPtr = ptr;
				mNextIndex = (int_strsize)idx;
				mLength = (int_strsize)length;
				mChar = 0;
			}

			public int NextIndex
			{
				get
				{
					return mNextIndex;
				}
				set mut
				{
					mNextIndex = (int_strsize)value;
				}
			}

			public char32 Current
		    {
		        get
				{
					return mChar;
				}
		    }

			public bool MoveNext() mut
			{
				let strLen = mLength;
				if (mNextIndex >= strLen)
					return false;

				mChar = (char32)mPtr[mNextIndex++];
				if (mChar < (char32)0x80)
					return true;
#if BF_UTF_PEDANTIC
				// If this fails then we are starting on a trailing byte
				Debug.Assert((mChar & (char32)0xC0) != (char32)0x80);
#endif
				int8 trailingBytes = UTF8.sTrailingBytesForUTF8[mChar];
				if (mNextIndex + trailingBytes > strLen)
					return true;

				switch (trailingBytes)
				{
				case 3: mChar <<= 6; mChar += (int32)mPtr[mNextIndex++]; fallthrough;
				case 2: mChar <<= 6; mChar += (int32)mPtr[mNextIndex++]; fallthrough;
				case 1: mChar <<= 6; mChar += (int32)mPtr[mNextIndex++]; fallthrough;
				}

				mChar -= (int32)UTF8.sOffsetsFromUTF8[trailingBytes];
				return true;
			}

			public void Reset() mut
			{
				mNextIndex = -1;
			}

			public void Dispose()
			{

			}

			public Result<char32> GetNext() mut
			{
				if (!MoveNext())
					return .Err;
				return Current;
			}
		}
	}

	public enum StringSplitOptions
	{
		None = 0,
		RemoveEmptyEntries = 1
	}

	struct StringSplitEnumerator : IEnumerator<StringView>
	{
		StringSplitOptions mSplitOptions;
		char8 mSplitChar0;
		char8[] mSplitChars;
		char8* mPtr;
		int_strsize mStrLen;
		int32 mCurCount;
		int32 mMaxCount;
		int_strsize mPos;
		int_strsize mMatchPos;

		public this(char8* ptr, int strLength, char8[] splitChars, int count, StringSplitOptions splitOptions)
		{
			mPtr = ptr;
			mStrLen = (int_strsize)strLength;
			if (splitChars.Count > 0)
				mSplitChar0 = splitChars[0];
			else
				mSplitChar0 = '\0';
			mSplitChars = splitChars;
			mCurCount = 0;
			mMaxCount = (int32)count;
			mPos = 0;
			mMatchPos = -1;
			mSplitOptions = splitOptions;
		}

		public this(char8* ptr, int strLength, char8 splitChar, int count, StringSplitOptions splitOptions)
		{
			mPtr = ptr;
			mStrLen = (int_strsize)strLength;
			mSplitChar0 = splitChar;
			mSplitChars = null;
			mCurCount = 0;
			mMaxCount = (int32)count;
			mPos = 0;
			mMatchPos = -1;
			mSplitOptions = splitOptions;
		}

		public StringView Current
	    {
	        get
			{
				return StringView(mPtr + mPos, mMatchPos - mPos);
			}
	    }

		public int_strsize Pos
		{
			get
			{
				return mPos;
			}
		}

		public int_strsize MatchPos
		{
			get
			{
				return mMatchPos;
			}
		}

		public bool HasMore
		{
			get
			{
				return mMatchPos < mStrLen;
			}
		}

		public bool MoveNext() mut
		{
			if (mCurCount >= mMaxCount)
				return false;

			mPos = mMatchPos + 1;

			mCurCount++;
			if (mCurCount == mMaxCount)
			{
				mMatchPos = (int_strsize)mStrLen;
				if (mPos > mMatchPos)
					return false;
				if ((mMatchPos == mPos) && (mSplitOptions.HasFlag(.RemoveEmptyEntries)))
					return false;
				return true;
			}

			int endDiff = mStrLen - mMatchPos;
			if (endDiff == 0)
				return false;
			while (true)
			{
				mMatchPos++;
				endDiff--;
				bool foundMatch = false;
				if (endDiff == 0)
				{
					foundMatch = true;
				}
				else
				{
					char8 c = mPtr[mMatchPos];
					if (c == mSplitChar0)
					{
						foundMatch = true;
					}
					else if (mSplitChars != null)
					{
						for (int i = 1; i < mSplitChars.Count; i++)
							if (c == mSplitChars[i])
								foundMatch = true;
					}
				}

				if (foundMatch)
				{
					if ((mMatchPos > mPos + 1) || (!mSplitOptions.HasFlag(StringSplitOptions.RemoveEmptyEntries)))
						return true;
					mPos = mMatchPos + 1;
					if (mPos >= mStrLen)
						return false;
				}
			}
		}

		public void Reset() mut
		{
			mPos = 0;
			mMatchPos = -1;
		}

		public void Dispose()
		{

		}

		public Result<StringView> GetNext() mut
		{
			if (!MoveNext())
				return .Err;
			return Current;
		}
	}

	public struct StringView : Span<char8>, IFormattable, IPrintable, IHashable
	{
		public this()
		{
			mPtr = null;
			mLength = 0;
		}

		public this(String string)
		{
			mPtr = string.Ptr;
			mLength = string.Length;
		}

		public this(String string, int offset)
		{
			Debug.Assert((uint)offset <= (uint)string.Length);
			mPtr = string.Ptr + offset;
			mLength = string.Length - offset;
		}

		public this(String string, int offset, int length)
		{
			Debug.Assert((uint)offset + (uint)length <= (uint)string.Length);
			mPtr = string.Ptr + offset;
			mLength = length;
		}

		public this(StringView stringView)
		{
			mPtr = stringView.mPtr;
			mLength = stringView.mLength;
		}

		public this(StringView stringView, int offset)
		{
			Debug.Assert((uint)offset <= (uint)stringView.Length);
			mPtr = stringView.mPtr + offset;
			mLength = stringView.mLength - offset;
		}

		public this(StringView stringView, int offset, int length)
		{
			Debug.Assert((uint)offset + (uint)length <= (uint)stringView.Length);
			mPtr = stringView.mPtr + offset;
			mLength = length;
		}

		public this(char8[] arr, int offset, int length)
		{
			Debug.Assert((uint)offset + (uint)length <= (uint)arr.Count);
			mPtr = arr.CArray() + offset;
			mLength = length;
		}

		public this(char8* ptr)
		{
			mPtr = ptr;
			mLength = String.StrLen(ptr);
		}

		public this(char8* ptr, int length)

		{
			mPtr = ptr;
			mLength = length;
		}

		public String.RawEnumerator RawChars
		{
			get
			{
				return String.RawEnumerator(Ptr, 0, mLength);
			}
		}

		public String.UTF8Enumerator DecodedChars
		{
			get
			{
				return String.UTF8Enumerator(Ptr, 0, mLength);
			}
		}

		public bool IsWhiteSpace
		{
			get
			{
				for (int i = 0; i < mLength; i++)
					if (!mPtr[i].IsWhiteSpace)
						return false;
				return true;
			}
		}

		public int GetHashCode()
		{
			return String.[Friend]GetHashCode(mPtr, mLength);
		}

		public override void ToString(String strBuffer)
		{
			strBuffer.Append(mPtr, mLength);
		}

		public void ToString(String outString, String format, IFormatProvider formatProvider)
		{
			if (format == "Q")
			{
				String.QuoteString(mPtr, mLength, outString);
				return;
			}
			outString.Append(mPtr, mLength);
		}

		void IPrintable.Print(String outString)
		{
			String.QuoteString(mPtr, mLength, outString);
		}

		public static bool operator==(StringView val1, StringView val2)
		{
			if (val1.mLength != val2.mLength)
				return false;
			char8* ptr1 = val1.mPtr;
			char8* ptr2 = val2.mPtr;
			if (ptr1 == ptr2)
				return true;
			if ((ptr1 == null) || (ptr2 == null))
				return false;
			return String.[Friend]EqualsHelper(ptr1, ptr2, val1.mLength);
		}

		public static bool operator==(StringView val1, String val2)
		{
			if (val1.mLength != val2.Length)
				return false;
			char8* ptr1 = val1.mPtr;
			char8* ptr2 = val2.Ptr;
			if (ptr1 == ptr2)
				return true;
			if ((ptr1 == null) || (ptr2 == null))
				return false;
			return String.[Friend]EqualsHelper(ptr1, ptr2, val1.mLength);
		}

		public static int Compare(StringView val1, StringView val2, bool ignoreCase = false)
		{
			if (ignoreCase)
				return String.[Friend]CompareOrdinalIgnoreCaseHelper(val1.mPtr, val1.mLength, val2.mPtr, val2.mLength);
			else
				return String.[Friend]CompareOrdinalHelper(val1.mPtr, val1.mLength, val2.mPtr, val2.mLength);
		}

		public bool Equals(StringView str)
		{
			if (mLength != str.[Friend]mLength)
				return false;
			return String.[Friend]EqualsHelper(str.Ptr, mPtr, mLength);
		}

		public bool Equals(StringView str, bool ignoreCase)
		{
			if (mLength != str.[Friend]mLength)
				return false;
			if (ignoreCase)
				return String.[Friend]EqualsIgnoreCaseHelper(str.Ptr, mPtr, mLength);
			return String.[Friend]EqualsHelper(str.Ptr, mPtr, mLength);
		}

		public int IndexOf(StringView subStr, bool ignoreCase = false)
		{
			/*for (int ofs = 0; ofs <= Length - subStr.Length; ofs++)
			{
				if (String.[Friend]Compare(mPtr + ofs, subStr.mLength, subStr.mPtr, subStr.mLength, ignoreCase) == 0)
					return ofs;
			}

			return -1;*/
			return IndexOf(subStr, 0, ignoreCase);
		}

		public int IndexOf(StringView subStr, int startIdx, bool ignoreCase = false)
		{
			if (subStr.Length == 0)
				return -1;

			if (!ignoreCase)
			{
				char8 firstC = subStr[0];
				for (int ofs = startIdx; ofs <= mLength - subStr.mLength; ofs++)
				{
					if ((mPtr[ofs] == firstC) && (String.Compare(mPtr + ofs + 1, subStr.mLength - 1, subStr.mPtr + 1, subStr.mLength - 1, ignoreCase) == 0))
						return ofs;
				}

			}
			else
			{
				char8 firstC = subStr[0];
				char8 firstC2 = firstC;
				if (firstC.IsLower)
					firstC2 = firstC.ToUpper;
				else
					firstC2 = firstC.ToLower;
				for (int ofs = startIdx; ofs <= mLength - subStr.mLength; ofs++)
				{
					if (((mPtr[ofs] == firstC) || (mPtr[ofs] == firstC2)) &&
						(String.Compare(mPtr + ofs + 1, subStr.mLength - 1, subStr.mPtr + 1, subStr.mLength - 1, ignoreCase) == 0))
						return ofs;
				}
			}

			return -1;
		}

		public int IndexOf(char8 c, int startIdx = 0)
		{
			let ptr = mPtr;
			for (int i = startIdx; i < mLength; i++)
				if (ptr[i] == c)
					return i;
			return -1;
		}

		public int LastIndexOf(char8 c)
		{
			let ptr = mPtr;
			for (int i = mLength - 1; i >= 0; i--)
				if (ptr[i] == c)
					return i;
			return -1;
		}

		public int LastIndexOf(char8 c, int startCheck)
		{
			let ptr = mPtr;
			for (int i = startCheck; i >= 0; i--)
				if (ptr[i] == c)
					return i;
			return -1;
		}

		public int IndexOfAny(char8[] targets)
		{
			return IndexOfAny(targets, 0, mLength);
		}

		public int IndexOfAny(char8[] targets, int startIdx)
		{
			return IndexOfAny(targets, startIdx, mLength - startIdx);
		}

		public int IndexOfAny(char8[] targets, int startIdx, int count)
		{
			let ptr = mPtr;
			for (var i = startIdx; i < count; i++)
			{
				let ch = ptr[i];
				for (let tag in targets)
				{
					if (ch == tag)
						return i;
				}
			}

			return -1;
		}

		public bool Contains(char8 c)
		{
			for (int i = 0; i < mLength; i++)
				if (mPtr[i] == c)
					return true;
			return false;
		}

		public bool Contains(StringView stringView)
		{
			return IndexOf(stringView) != -1;
		}

		public bool StartsWith(StringView b, StringComparison comparisonType = StringComparison.Ordinal)
		{
			if (mLength < b.mLength)
				return false;
			if (comparisonType == StringComparison.OrdinalIgnoreCase)
				return String.[Friend]EqualsIgnoreCaseHelper(this.Ptr, b.Ptr, b.Length);
			return String.[Friend]EqualsHelper(this.Ptr, b.Ptr, b.mLength);
		}

		public bool EndsWith(StringView b, StringComparison comparisonType = StringComparison.Ordinal)
		{
			if (mLength < b.mLength)
				return false;
			if (comparisonType == StringComparison.OrdinalIgnoreCase)
				return String.[Friend]EqualsIgnoreCaseHelper(Ptr + mLength - b.mLength, b.Ptr, b.mLength);
			return String.[Friend]EqualsHelper(this.Ptr + mLength - b.mLength, b.Ptr, b.mLength);
		}

		public void TrimEnd() mut
		{
			let ptr = Ptr;
			for (int i = mLength - 1; i >= 0; i--)
			{
				char8 c = ptr[i];
				if (c >= (char8)0x80)
				{
					var (c32, idx, len) = GetChar32WithBacktrack(i);
					if (!c32.IsWhiteSpace)
					{
						if (i < mLength - 1)
						{
							mLength = i + 1;
						}	
						return;
					}
					i = idx;
				}
				else if (!c.IsWhiteSpace)
				{
					if (i < mLength - 1)
					{
						mLength = i + 1;
					}
					return;
				}
			}
			Clear();
		}

		public void TrimStart() mut
		{
			let ptr = Ptr;
			for (int i = 0; i < mLength; i++)
			{
				char8 c = ptr[i];
				if (c >= (char8)0x80)
				{
					var (c32, len) = GetChar32(i);
					if (!c32.IsWhiteSpace)
					{
						if (i > 0)
						{
							mPtr += i;
							mLength -= i;
						}	
						return;
					}
					i += len - 1;
				}
				else if (!c.IsWhiteSpace)
				{
					if (i > 0)
					{
						mPtr += i;
						mLength -= i;
					}
					return;
				}
			}
			Clear();
		}

		public void Trim() mut
		{
			TrimStart();
			TrimEnd();
		}

		public bool StartsWith(char8 c)
		{
			if (mLength == 0)
				return false;
			return Ptr[0] == c;
		}

		public bool EndsWith(char8 c)
		{
			if (mLength == 0)
				return false;
			return Ptr[mLength - 1] == c;
		}

		public void QuoteString(String outString)
		{
			String.QuoteString(Ptr, Length, outString);
		}

		public Result<void> UnQuoteString(String outString)
		{
			return String.UnQuoteString(Ptr, Length, outString);
		}

		[NoDiscard]
		public StringView Substring(int pos)
		{
			return .(this, pos);
		}

		[NoDiscard]
		public StringView Substring(int pos, int length)
		{
			return .(this, pos, length);
		}

		public (char32, int) GetChar32(int idx)
		{
			Debug.Assert((uint)idx < (uint)mLength);
			char8* ptr = Ptr;
			char8 c = ptr[idx];
			if (c < '\x80')
				return (c, 1);
			if (((uint8)ptr[idx] & 0xC0) == 0x80)
				return (0, 0); // Invalid UTF8 data
			return UTF8.Decode(ptr + idx, mLength - idx);
		}

		public (char32, int, int) GetChar32WithBacktrack(int idx)
		{
			Debug.Assert((uint)idx < (uint)mLength);
			char8* ptr = Ptr;
			char8 c = ptr[idx];
			if (c < '\x80')
				return (c, idx, 1);
			var idx;
			while (((uint8)ptr[idx] & 0xC0) == 0x80)
			{
				if (idx == 0) // Invalid UTF8 data
					return (0, 0, 0);
				idx--;
			}
			let (c32, len) = UTF8.Decode(ptr + idx, mLength - idx);
			return (c32, idx, len);
		}

		public StringSplitEnumerator Split(char8 c)
		{
			return StringSplitEnumerator(Ptr, Length, c, Int32.MaxValue, StringSplitOptions.None);
		}

		public StringSplitEnumerator Split(char8 separator, int count)
		{
			return StringSplitEnumerator(Ptr, Length, separator, count, StringSplitOptions.None);
		}

		public StringSplitEnumerator Split(char8 separator, StringSplitOptions options)
		{
			return StringSplitEnumerator(Ptr, Length, separator, Int32.MaxValue, options);
		}

		public StringSplitEnumerator Split(char8 separator, int count, StringSplitOptions options)
		{
			return StringSplitEnumerator(Ptr, Length, separator, count, options);
		}

		public StringSplitEnumerator Split(params char8[] separators)
		{
			return StringSplitEnumerator(Ptr, Length, separators, Int32.MaxValue, StringSplitOptions.None);
		}

		public StringSplitEnumerator Split(char8[] separators, int count = Int32.MaxValue, StringSplitOptions options = .None)
		{
			return StringSplitEnumerator(Ptr, Length, separators, count, options);
		}

		public static operator StringView (String str)
		{
			StringView sv;
			if (str != null)
			{
				sv.mPtr = str.Ptr;
				sv.mLength = str.[Friend]mLength;
			}
			else
			{
				sv = default;
			}
			return sv;
		}

		public mixin ToScopeCStr(int maxInlineChars = 128)
		{
			char8* ptr = null;
			if (this.mPtr != null)
			{
				if (mLength < maxInlineChars)
				{
					ptr = scope:mixin char8[mLength + 1]*;
				}
				else
				{
					ptr = new char8[mLength + 1]*;
					defer:mixin delete ptr;
				}
				Internal.MemCpy(ptr, mPtr, mLength);
				ptr[mLength] = 0;
			}
			ptr
		}

		public mixin ToScopedNativeWChar()
		{
			int encodedLen = UTF16.GetEncodedLen(this);
			char16* buf;
			if (encodedLen < 128)
			{
				buf = scope:mixin char16[encodedLen]* ( ? );
			}
			else
			{
				buf = new char16[encodedLen]* ( ? );
				defer:mixin delete buf;
			}

			UTF16.Encode(this, buf, encodedLen);
			buf
		}
	}

	class StringWithAlloc : String
	{
		IRawAllocator mAlloc;

		[AllowAppend]
		private this()
		{
		}

		[AllowAppend]
		public this(IRawAllocator alloc)
		{
			mAlloc = alloc;
		}

		protected override void* Alloc(int size, int align)
 		{
			 return mAlloc.Alloc(size, align);
		}

		protected override void Free(void* ptr)
 		{
			 mAlloc.Free(ptr);
		}
	}

#if TEST
	extension String
	{
		[Test]
		public static void Test_Intern()
		{
			String str = "TestString";
			str.EnsureNullTerminator();

			String strA = scope String("Test");
			strA.Append("String");
			Runtime.Assert(strA == str);
			Runtime.Assert((Object)strA != str);

			String strB = strA.Intern();
			Runtime.Assert(strB == str);
			Runtime.Assert((Object)strB == str);
		}
	}
#endif
}
