//
// Created by Eugeny Grishul
//
// See license at http://bamelg.com/license.txt
//

using System.Collections;
using System.Globalization;
using System.Runtime;
using System.Text;

namespace System {
	public sealed partial class Utf16String {
		public const string Empty = "";

		public readonly uint Length;
		public readonly uint Hash;

		public uint ByteLength { get { return Length * sizeof( this[0] ); } }

		public bool IsNullOrEmpty { get { return this == null || Length == 0; } }

		private Utf16String() {
		}

		private Utf16String( uint length ) {
			extra {
				return sizeof( char ) * ( length + 1 );
			}
			body {
				Length = length;
			}
		}

		public Utf16String( char source, uint length ) {
			extra {
				return sizeof( char ) * ( length + 1 );
			}
			body {
				var chars = GetChars();
				Length = length;

				Memory.Fill16( chars, ( ushort ) source, length );

				chars[Length] = '\0';
				UpdateHash();
			}
		}

		public Utf16String( Utf16String source ) {
			extra {
				return sizeof( char ) * ( source.Length + 1 );
			}
			body {
				var chars = GetChars();
				Length = source.Length;
				Hash = source.Hash;

				Memory.Copy( chars, source.GetChars(), ByteLength + sizeof( GetChars()[0] ) ); // copy with '\0'
			}
		}

		public static Utf16String operator +( Utf16String left, Utf16String right ) {
			if( left == null ) return right;
			if( right == null ) return left;

			var resultLength = left.Length + right.Length;
			var result = new Utf16String( resultLength );

			var chars = result.GetChars();

			Memory.Copy( chars, left.GetChars(), left.ByteLength );
			Memory.Copy( chars + left.Length, right.GetChars(), right.ByteLength + sizeof( right.GetChars()[0] ) ); // copy with '\0'

			result.UpdateHash();
			return result;
		}

		public static Utf16String Concat( vararg TypedReference values ) {
			using( var builder = StringBuilder.CachedBuilders.PopScoped() ) {
				builder.Value.Concat( vararg( values, values.Length ) );
				return builder.Value.ToString();
			}
		}

		public static Utf16String operator +( Utf16String left, TypedReference right ) { return Concat( left, right ); }
		public static Utf16String operator +( TypedReference left, Utf16String right ) { return Concat( left, right ); }

		private void UpdateHash() { *cast<uint*>( bitcast<byte*>( this ) + memberinfo( Hash ).ByteOffset ) = CUtf16String.ComputeHashCode( GetChars(), Length ); }

		public override uint GetHashCode() { return Hash; }

		internal char* GetChars() { return this != null ? ( char* )( bitcast<byte*>( this ) + sizeof( thistype ) ) : null; }

		public char this[int index] { get { System.Diagnostics.Debug.Assert( ( uint ) index < Length ); return GetChars()[index]; } }
		public char this[uint index] { get { System.Diagnostics.Debug.Assert( index < Length ); return GetChars()[index]; } }

		public static bool operator ==( Utf16String left, Utf16String right ) {
			if( cast<RuntimeObjectBase>( left ) == cast<RuntimeObjectBase>( right ) ) return true;
			if( ( left == null ) | ( right == null ) ) return false;

			if( left.Hash != right.Hash ) return false;
			if( left.Length != right.Length ) return false;

			return Memory.Compare( left.GetChars(), right.GetChars(), ( int ) left.ByteLength );
		}

		public static bool operator !=( Utf16String left, Utf16String right ) {
			return !( left == right );
		}

		public static Utf16String Format( [VerifyFormatString] Utf16String format, vararg TypedReference parameters ) {
			using( var builder = StringBuilder.CachedBuilders.PopScoped() ) {
				builder.Value.AppendFormat( format, vararg( parameters, parameters.Length ) );
				return builder.Value.ToString();
			}
		}

		public static Utf16String Join( Utf16String separator, Utf16String* values, int count ) {
			if( count == 0 ) return "";
			if( count == 1 ) return values[0];

			uint totalLength = ( count - 1 ) * separator.Length;
			for( int i = 0; i < count; ++i )
				totalLength += ( int ) values[i].Length;

			var result = new Utf16String( totalLength );
			var chars = result.GetChars();

			Memory.Copy<char>( chars, values[0].GetChars(), values[0].Length ); chars += values[0].Length;

			for( int i = 1; i < count; ++i ) {
				Memory.Copy( chars, separator.GetChars(), separator.ByteLength ); chars += separator.Length;
				Memory.Copy( chars, values[i].GetChars(), values[i].ByteLength ); chars += values[i].Length;
			}

			result.UpdateHash();

			return result;
		}

		public static Utf16String Join( Utf16String separator, List<Utf16String> values ) { return Join( separator, values.GetBuffer(), values.Count ); }
		public static Utf16String Join( Utf16String separator, Utf16String[] values ) { return Join( separator, &values[0], values.Length ); }

		public bool StartsWith( string value ) {
			if( value == null || value.Length == 0 ) return false;
			if( Length < value.Length ) return false;

			return Memory.Compare( GetChars(), value.GetChars(), ( int ) value.ByteLength );
		}

		public bool StartsWith( char value ) {
			if( Length < 1 ) return false;

			return this[0] == value;
		}

		public bool EndsWith( string value ) {
			if( value == null || value.Length == 0 ) return false;
			if( Length < value.Length ) return false;

			return Memory.Compare( GetChars() + ( Length - value.Length ), value.GetChars(), ( int ) value.ByteLength );
		}

		public bool EndsWith( char value ) {
			if( Length < 1 ) return false;

			return this[Length - 1] == value;
		}

		public bool StartsWithAny( vararg char values ) {
			if( Length < 1 ) return false;

			var firstChar = this[0];
			foreach( var value in values )
				if( firstChar == value ) return true;

			return false;
		}

		public bool EndsWithAny( vararg char values ) {
			if( Length < 1 ) return false;

			var lastChar = this[Length - 1];
			foreach( var value in values )
				if( lastChar == value ) return true;

			return false;
		}

		public Utf16String Trim() {
			if( Length == 0 ) return this;
			uint startIndex = 0, endIndex = Length - 1;

			for( uint i = 0; i < Length; ++i, ++startIndex ) {
				if( this[i].IsWhiteSpace ) continue;
				break;
			}

			for( uint i = Length - 1; i >= startIndex; --i, --endIndex ) {
				if( this[i].IsWhiteSpace ) continue;
				break;
			}

			if( startIndex > endIndex ) return "";
			return ( Utf16String ) new CUtf16StringSpan( GetChars() + startIndex, ( uint )( endIndex - startIndex + 1 ) );
		}

		public override Utf16String ToString() { return this; }

		public List<Utf16String> Split( Utf16String separator ) {
			return Split( separator, int.MaxValue, StringSplitOptions.None );
		}

		public List<Utf16String> Split( Utf16String separator, int maxCount ) {
			return Split( separator, maxCount, StringSplitOptions.None );
		}

		public List<Utf16String> Split( Utf16String separator, int maxCount, StringSplitOptions options ) {
			if( separator == null || separator.Length == 0 ) {
				return null;
			}

			var lastIndex = 0U;
			var chars = GetChars();
			var separatorChars = separator.GetChars();

			var result = new List<Utf16String>();

			var i = 0U;
			for( ; i + separator.Length < Length + 1; ++i ) {
				if( !Memory.Compare( chars + i, separatorChars, ( int )( sizeof( chars[0] ) * separator.Length ) ) ) goto next_char;

				if( i - lastIndex == 0 ) {
					if( options != StringSplitOptions.RemoveEmptyEntries )
						result.Add( "" );
				}
				else
					result.Add( string.FromUtf16( chars + lastIndex, i - lastIndex ) );

				lastIndex = i + separator.Length;
				i = i + separator.Length - 1;

			next_char:
			}

			if( i >= lastIndex + separator.Length ) {
				var element = ( string ) new CUtf16StringSpan( chars + lastIndex, Length - lastIndex );
				result.Add( element );
			}
			else {
				if( result.Count > 0 ) {
					if( options != StringSplitOptions.RemoveEmptyEntries )
						result.Add( "" );
				}
				else
					result.Add( this );
			}

			return result;
		}

		public int LastIndexOf( char value ) {
			return LastIndexOf( value, ( int ) Length - 1, ( int ) Length );
		}

		public int LastIndexOf( char value, int startIndex ) {
			return LastIndexOf( value, startIndex, startIndex + 1 );
		}

		public int LastIndexOf( char value, int startIndex, int count ) {
			if( startIndex == -1 && count == 0 ) return -1;
			if( !Assert.IsFalse( startIndex < 0 | ( uint ) startIndex >= Length ) ) return -1;

			if( count == 0 ) return -1;
			if( !Assert.IsFalse( ( uint ) count > Length | startIndex - count < -1 ) ) return -1;

			var chars = GetChars();
			var endIndex = startIndex - count;

			for( var i = startIndex; i > endIndex; --i )
				if( chars[i] == value )
					return i;

			return -1;
		}

		public bool Contains( char value ) {
			return IndexOf( value ) != 0;
		}

		public int IndexOf( char value ) {
			return IndexOf( value, 0, ( int ) Length );
		}

		public int IndexOf( char value, int startIndex ) {
			return IndexOf( value, startIndex, ( int ) Length - startIndex );
		}

		public int IndexOf( char value, int startIndex, int count ) {
			if( startIndex == 0 && count == 0 ) return -1;
			if( !Assert.IsFalse( startIndex < 0 | ( uint ) startIndex >= Length ) ) return -1;

			if( count <= 0 ) return -1;
			if( !Assert.IsFalse( ( uint ) count > Length | ( uint ) startIndex + ( uint ) count > Length ) ) return -1;

			var chars = GetChars();
			var endIndex = startIndex + count;

			for( var i = startIndex; i < endIndex; ++i ) {
				if( chars[i] == value ) return i;
			}

			return -1;
		}

		public string TrimStart( char value ) {
			var chars = GetChars();

			if( chars[0] != value ) return this;

			for( var i = 1; i < ( int ) Length; ++i ) {
				if( chars[i] != value )
					return Substring( i );
			}

			return Empty;
		}

		public string TrimEnd( char value ) {
			if( Length == 0 ) return this;

			var chars = GetChars();
			if( chars[Length - 1] != value ) return this;

			for( var i = ( int ) Length - 2; i >= 0; --i ) {
				if( chars[i] != value )
					return Substring( 0, i + 1 );
			}

			return Empty;
		}

		public string Substring( int startIndex ) {
			return Substring( startIndex, ( int ) Length - startIndex );
		}

		public string Substring( int startIndex, int length ) {
			if( !Assert.IsFalse( startIndex < 0 | ( uint ) startIndex > Length ) )
				return Empty;

			if( !Assert.IsFalse( length < 0 | ( uint ) startIndex + ( uint ) length > Length ) )
				return Empty;

			if( startIndex == 0 & ( uint ) length == Length )
				return this;

			if( length == 0 )
				return Empty;

			return FromUtf16( GetChars() + startIndex, ( uint ) length );
		}

		public string Replace( char find, char replace ) {
			if( IsNullOrEmpty ) return this;

			var chars = GetChars();
			var startIndex = 0;
			var endIndex = ( int ) Length;

			for( var i = startIndex; i < endIndex; ++i ) {
				if( chars[i] == find ) {
					var result = new string( Length );
					var resultChars = result.GetChars();

					Memory.Copy( resultChars, chars, ByteLength );
					resultChars[i] = replace; ++i;

					for( ; i < endIndex; ++i ) {
						if( resultChars[i] == find )
							resultChars[i] = replace;
					}

					result.UpdateHash();
					return result;
				}
			}

			return this;
		}

		public string Replace( string find, string replace ) {
			if( IsNullOrEmpty || find.IsNullOrEmpty ) return this;

			var copyInterval = new List<uint>();

			char* chars = GetChars(), findChars = find.GetChars(), replaceChars = replace.GetChars();
			var lastPosition = 0U;
			var copyLastBytes = 0U;

			for( var i = 0U; i < Length; ++i ) {
				if( chars[i] == findChars[0] && i <= Length - find.Length ) {
					for( var j = 1U; j < find.Length; ++j ) {
						if( chars[i + j] != findChars[j] )
							goto next_char;
					}

					copyInterval.Add( lastPosition == 0 ? i : i + 1 - lastPosition - find.Length );
					lastPosition = i + 1;

					i += find.Length - 1;
				}
				else if( i + 1 == Length ) {
					copyLastBytes = Length - lastPosition - find.Length + 1;
				}

			next_char:
				continue;
			}

			if( copyInterval.Count == 0 ) return this;

			var resultLength = Length + ( ( int ) replace.Length - ( int ) find.Length ) * copyInterval.Count;
			var resultString = new string( resultLength );
			var result = resultString.GetChars();
			var destinationPos = 0U;
			var sourcePos = 0U;

			foreach( var interval in copyInterval ) {
				Memory.Copy( result + destinationPos, chars + sourcePos, interval * sizeof( char ) );
				Memory.Copy( result + destinationPos + interval, replaceChars, replace.Length * sizeof( char ) );

				destinationPos += interval + replace.Length;
				sourcePos += interval + find.Length;
			}

			Memory.Copy( result + destinationPos, chars + sourcePos, copyLastBytes * sizeof( char ) );

			resultString.UpdateHash();
			return resultString;
		}

		/// @ Conversion from SBCS

		public static explicit operator Utf16String( SbcsString value ) { return FromSBCS( value ); }
		public static explicit operator Utf16String( CString value ) { return FromSBCS( value ); }
		public static explicit operator Utf16String( CStringSpan value ) { return FromSBCS( value ); }

		public static Utf16String FromSBCS( CString value ) { return FromSBCS( Environment.DefaultCodePage, value ); }
		public static Utf16String FromSBCS( CodePageID codePage, CString value ) { return FromSBCS( codePage, value.GetChars(), 0 ); }

		public static Utf16String FromSBCS( CStringSpan value ) { return FromSBCS( Environment.DefaultCodePage, value ); }
		public static Utf16String FromSBCS( CodePageID codePage, CStringSpan value ) { return FromSBCS( codePage, value.GetChars(), value.Length ); }

		public static Utf16String FromSBCS( SbcsString value ) {
			if( value == null ) return null;
			if( value.Length == 0 ) return "";

			return FromSBCS( value.CodePage, value.GetChars(), value.Length );
		}

		public static Utf16String FromSBCS( CodePageID codePage, byte* memory, uint limitChars = 0 ) {
			if( memory == null ) return null;
			if( memory[0] == 0 & limitChars == 0 ) return "";

			if( limitChars == 0 )
				limitChars = CString.ComputeLength( memory );

			var result = new Utf16String( limitChars );
			var chars = result.GetChars();

			Encoding.GetChars( codePage, chars, limitChars, memory, limitChars );

			result.UpdateHash();
			return result;
		}

		/// @}

		/// @ Conversion from UTF8
		public static explicit operator Utf16String( Utf8String value ) { return FromUtf8( value ); }
		public static explicit operator Utf16String( CUtf8String value ) { return FromUtf8( value ); }
		public static explicit operator Utf16String( CUtf8StringSpan value ) { return FromUtf8( value ); }

		public static Utf16String FromUtf8( CUtf8String value ) { return FromUtf8( value.GetChars(), 0 ); }
		public static Utf16String FromUtf8( CUtf8StringSpan value ) { return FromUtf8( value.GetChars(), value.ByteLength ); }

		public static Utf16String FromUtf8( Utf8String value ) {
			if( value == null ) return null;
			if( value.Length == 0 ) return "";

			return FromUtf8( value.GetChars(), value.ByteLength );
		}

		public static Utf16String FromUtf8( byte* memory, uint limitBytes ) {
			if( memory == null ) return null;
			if( memory[0] == 0 & limitBytes == 0 ) return "";

			uint encodedCharsCount, charsCount;
			Unicode.GetUtf8ByteCountInUtf16( memory, limitBytes, charsCount, encodedCharsCount );

			var result = new Utf16String( encodedCharsCount );

			var chars = result.GetChars();
			Unicode.ConvertUtf8CharactersToUtf16( chars, memory, limitBytes );

			chars[encodedCharsCount] = '\0';

			result.UpdateHash();
			return result;
		}

		/// @}

		/// @ Conversion from UTF16

		public static explicit operator Utf16String( CUtf16String value ) { return FromUtf16( value ); }
		public static explicit operator Utf16String( CUtf16StringSpan value ) { return FromUtf16( value ); }

		public static Utf16String FromUtf16( CUtf16String value ) { return FromUtf16( value.GetChars(), 0 ); }
		public static Utf16String FromUtf16( CUtf16StringSpan value ) { return FromUtf16( value.GetChars(), value.Length ); }

		public static Utf16String FromUtf16( char* chars, uint limitChars = 0 ) {
			if( chars == null ) return null;
			if( chars[0] == 0 & limitChars == 0 ) return "";

			if( limitChars == 0 )
				limitChars = CUtf16String.ComputeLength( chars );

			var result = new thistype( limitChars );
			var resultChars = result.GetChars();

			Memory.Copy( resultChars, chars, limitChars * sizeof( char ) );
			resultChars[limitChars] = '\0';

			result.UpdateHash();
			return result;
		}

		/// @}

		/// @ Conversion from UTF32

		public static explicit operator Utf16String( Utf32String value ) { return FromUtf32( value ); }
		public static explicit operator Utf16String( CUtf32String value ) { return FromUtf32( value ); }
		public static explicit operator Utf16String( CUtf32StringSpan value ) { return FromUtf32( value ); }

		public static Utf16String FromUtf32( CUtf32String value ) { return FromUtf32( value.GetChars(), 0 ); }
		public static Utf16String FromUtf32( CUtf32StringSpan value ) { return FromUtf32( value.GetChars(), value.Length ); }

		public static Utf16String FromUtf32( Utf32String value ) {
			if( value == null ) return null;
			if( value.Length == 0 ) return "";

			return FromUtf32( value.GetChars(), value.Length );
		}

		public static Utf16String FromUtf32( uint* memory, uint limitChars = 0 ) {
			if( memory == null ) return null;
			if( memory[0] == 0 & limitChars == 0 ) return "";

			uint charsCount16;
			Unicode.GetUtf32ByteCountInUtf16( memory, limitChars, charsCount16 );

			var result = new Utf16String( charsCount16 );

			var chars = result.GetChars();
			Unicode.ConvertUtf32CharactersToUtf16( chars, memory, limitChars );

			chars[charsCount16] = '\0';

			result.UpdateHash();
			return result;
		}

		/// @}

		public ushort[] ToCharArray() {
			var result = new[Length] ushort;
			Memory.Copy( &result[0], GetChars(), ByteLength );
			return result;
		}

		public static int Compare( thistype left, thistype right, CompareOptions options ) {
			return _implCompare( left, right, options );
		}

		public int CompareTo( string value ) {
			return _implCompare( this, value, CompareOptions.None );
		}

		public string ToLower() {
			var chars = GetChars();

			for( uint i = 0; i < Length; ++i ) {
				if( chars[i].IsUpper ) {
					var result = new string( Length );
					var resultChars = result.GetChars();

					Memory.Copy( resultChars, chars, i * sizeof( chars[i] ) );
					for( ; i < Length; ++i )
						resultChars[i] = char.ToLower( chars[i] );

					result.UpdateHash();
					return result;
				}
			}

			return this;
		}

		public string ToUpper() {
			var chars = GetChars();

			for( uint i = 0; i < Length; ++i ) {
				if( chars[i].IsLower ) {
					var result = new string( Length );
					var resultChars = result.GetChars();

					Memory.Copy( resultChars, chars, i * sizeof( chars[i] ) );
					for( ; i < Length; ++i )
						resultChars[i] = char.ToUpper( chars[i] );

					result.UpdateHash();
					return result;
				}
			}

			return this;
		}

		public bool Contains( thistype value ) {
			if( value.IsNullOrEmpty ) return false;

			return IndexOf( value ) != -1;
		}

		public int IndexOf( thistype value ) {
			return IndexOf( value, 0 );
		}

		public int IndexOf( thistype value, int startIndex ) {
			if( value.IsNullOrEmpty ) return -1;
			if( startIndex < 0 || startIndex + value.Length > Length ) return -1;

			for( var i = ( uint ) startIndex; i + value.Length < Length + 1; ++i ) {
				if( this[i] == value[0] ) {
					uint j;

					for( j = 1; j < value.Length; ++j )
						if( this[i + j] != value[j] )
							break;

					if( j == value.Length ) return ( int ) i;
				}
			}

			return -1;
		}

		private static int _implCompare( thistype left, thistype right, CompareOptions options ) {
			if( left == null & right == null ) return 0;
			if( left == null ^ right == null ) return right == null ? 1 : -1;

			if( options == CompareOptions.None ) {
				var minLength = Math.Min( left.Length, right.Length );

				for( var i = 0U; i < minLength; ++i ) {
					var leftChar = left[i];
					var rightChar = right[i];

					if( leftChar == rightChar ) continue;

					return leftChar > rightChar ? 1 : -1;
				}

				if( left.Length == right.Length ) return 0;
				return left.Length.CompareTo( right.Length );
			}

			if( ( options & CompareOptions.IgnoreCase ) != 0 ) {
				var minLength = Math.Min( left.Length, right.Length );

				for( var i = 0U; i < minLength; ++i ) {
					var leftChar = char.ToLower( left[i] );
					var rightChar = char.ToLower( right[i] );

					if( leftChar == rightChar ) continue;

					return leftChar > rightChar ? 1 : -1;
				}

				if( left.Length == right.Length ) return 0;
				return left.Length.CompareTo( right.Length );
			}

			Assert.Unreachable();
			return 0;
		}
	}

	[PrimitiveType( Size = sizeof( uintptr ) )]
	public partial struct CUtf16String {
		public const thistype Null = bitcast<thistype>( ( uintptr ) 0 );
		public bool IsNullOrEmpty { get { return GetChars() == null || GetChars()[0] == 0; } }

		public CUtf16String( char* location ) {
			this = bitcast<thistype>( location );
		}

		public uint Length {
			get { return ComputeLength( GetChars() ); }
		}

		public uint ByteLength {
			get { return Length * 2; }
		}

		public uint Hash {
			get { return GetHashCode(); }
		}

		public uint GetHashCode() { return GetHashAndByteLength( GetChars(), 0U ); }

		public static bool operator ==( CUtf16String left, CUtf16String right ) { return AreEqual( left, right ); }
		public static bool operator !=( CUtf16String left, CUtf16String right ) { return !AreEqual( left, right ); }

		public static void Concatenate( CUtf16String destinationString, CUtf16String sourceString ) {
			var destination = destinationString.GetChars();
			var source = sourceString.GetChars();

			var length = destinationString.Length;
			var length2 = sourceString.Length;

			for( var i = 0U; i <= length2; ++i )
				destination[i + length] = source[i];
		}

		public static bool AreEqual( CUtf16String leftString, CUtf16String rightString ) {
			var left = leftString.GetChars();
			var right = rightString.GetChars();

			if( ( left == null ) & ( right == null ) ) return true;
			if( ( left == null ) | ( right == null ) ) return false;

			while( *left != '\0' && ( *left == *right ) ) {
				++left;
				++right;
			}

			return ( *left - *right ) == 0;
		}

		public static bool AreEqual( CUtf16String leftString, CUtf16String rightString, int length ) {
			var left = leftString.GetChars();
			var right = rightString.GetChars();

			if( ( left == null ) & ( right == null ) ) return true;
			if( ( left == null ) | ( right == null ) ) return false;

			int n = length;

			while( --n >= 0 && *left == *right++ )
				if( *left++ == '\0' )
					return true;

			return n < 0 ? true : *left - *--right == 0;
		}

		public bool StartsWith( CUtf16String prefix ) {
			var left = GetChars();
			var right = prefix.GetChars();

			if( ( left == null ) | ( right == null ) ) return false;

			while( *left != '\0' && ( *left == *right ) ) {
				++left;
				++right;
			}

			return *right == 0;
		}

		static void Copy( CUtf16String leftString, CUtf16String rightString ) {
			var left = leftString.GetChars();
			var right = rightString.GetChars();

			while( ( *left++ = *right++ ) != 0 )
				continue;
		}

		static void Copy( CUtf16String leftString, CUtf16String rightString, int maxLength ) {
			if( maxLength == 0 ) return;

			var left = leftString.GetChars();
			var right = rightString.GetChars();

			while( maxLength != 0 && ( *left++ = *right++ ) != 0 )
				--maxLength;

			if( maxLength == 0 ) *--left = ( char ) 0;
		}

		/// Dangerous operator. Result will correct while 'value' is alive.
		public static implicit operator CUtf16String( Utf16String value ) { return new CUtf16String( value.GetChars() ); }

		public char* GetChars() { return bitcast<char*>( this ); }

		public char this[int index] { get { return GetChars()[index]; } }
		public char this[uint index] { get { return GetChars()[index]; } }

		public static uint ComputeLength( char* chars ) {
			if( chars == null ) return 0U;
			var result = 0U;

			while( *chars++ != 0 )
				result++;

			return result;
		}

		public static uint ComputeLength( char* chars, uint limitLength ) {
			if( chars == null ) return 0U;
			var result = 0U;

			while( *chars++ != 0 ) {
				++result;
				if( result >= limitLength ) break;
			}

			return result;
		}

		public static uint ComputeHashCode( char* chars, uint length ) {
			if( chars == null ) return 0U;

			var result = 0U;

			for( var i = 0U; i < length; ++i )
				result = result * 41 + chars[i];

			return result;
		}

		public static uint GetHashAndByteLength( char* chars, uint& resultLength ) {
			if( chars == null ) { resultLength = 0; return 0U; }

			var result = 0U;
			var length = 0U;

			for( int i = 0; chars[i] != '\0'; ++i, ++length )
				result = result * 41 + chars[i];

			resultLength = length * sizeof( char );
			return result;
		}
	}

	public partial struct CUtf16StringSpan {
		public static readonly CUtf16StringSpan Null;
		public bool IsNullOrEmpty { get { return Location == null || Length == 0; } }

		public char* Location;
		public uint Length;
		public uint ByteLength { get { return Length * sizeof( char ); } }

		public CUtf16StringSpan( char* location, uint length ) { Location = location; Length = length; }

		public uint GetHashCode() { return CUtf16String.ComputeHashCode( Location, Length ); }

		public char* GetChars() { return Location; }

		public static bool AreEqual( CUtf16StringSpan& leftString, CUtf16StringSpan& rightString ) {
			var left = leftString.Location;
			var right = rightString.Location;

			if( left == right && leftString.Length == rightString.Length ) return true;
			if( ( left == null ) | ( right == null ) ) return false;

			return Memory.Compare( left, right, ( int ) leftString.ByteLength );
		}

		public static bool operator ==( CUtf16StringSpan& left, CUtf16StringSpan& right ) { return AreEqual( left, right ); }
		public static bool operator !=( CUtf16StringSpan& left, CUtf16StringSpan& right ) { return !AreEqual( left, right ); }

		public static bool operator ==( Utf16String left, CUtf16StringSpan& right ) { return AreEqual( left, right ); }
		public static bool operator !=( Utf16String left, CUtf16StringSpan& right ) { return !AreEqual( left, right ); }

		public static bool operator ==( CUtf16StringSpan& left, Utf16String right ) { return AreEqual( left, right ); }
		public static bool operator !=( CUtf16StringSpan& left, Utf16String right ) { return !AreEqual( left, right ); }

		/// Dangerous operator. Result will correct while 'value' is alive.
		public static implicit operator CUtf16StringSpan( Utf16String value ) { return value != null ? new CUtf16StringSpan( value.GetChars(), value.Length ) : CUtf16StringSpan.Null; }
		public static implicit operator CUtf16StringSpan( CUtf16String value ) { return new CUtf16StringSpan( value.GetChars(), value.Length ); }

		public bool StartsWith( CUtf16StringSpan value ) {
			if( value == null || value.Length == 0 ) return false;
			if( Length < value.Length ) return false;

			return Memory.Compare( GetChars(), value.GetChars(), ( int ) value.ByteLength );
		}

		public bool EndsWith( CUtf16StringSpan value ) {
			if( value == null || value.Length == 0 ) return false;
			if( Length < value.Length ) return false;

			return Memory.Compare( GetChars() + ( Length - value.Length ), value.GetChars(), ( int ) value.ByteLength );
		}

		public bool StartsWith( Utf16String value ) {
			if( value == null || value.Length == 0 ) return false;
			if( Length < value.Length ) return false;

			return Memory.Compare( GetChars(), value.GetChars(), ( int ) value.ByteLength );
		}

		public bool EndsWith( Utf16String value ) {
			if( value == null || value.Length == 0 ) return false;
			if( Length < value.Length ) return false;

			return Memory.Compare( GetChars() + ( Length - value.Length ), value.GetChars(), ( int ) value.ByteLength );
		}
	}
}