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

using System.Runtime;
using System.Text;

namespace System {
	public sealed partial class Utf32String {
		public const Utf32String 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 Utf32String() {
		}

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

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

				Memory.Fill32( chars, source, length );

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

		public Utf32String( Utf32String source ) {
			extra {
				return sizeof( uint ) * ( 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 Utf32String Concat( vararg TypedReference values ) {
			using( var builder = StringBuilder.CachedBuilders.PopScoped() ) {
				builder.Value.Concat( vararg( values, values.Length ) );
				return builder.Value.ToUtf32String();
			}
		}

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

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

		public override uint GetHashCode() { return Hash; }

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

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

		public static bool operator ==( Utf32String left, Utf32String 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 !=( Utf32String left, Utf32String right ) {
			return !( left == right );
		}

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

			var resultLength = left.Length + right.Length;
			var result = new Utf32String( 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] ) );

			result.UpdateHash();
			return result;
		}

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

		/// @ Conversion from SBCS

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

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

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

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

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

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

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

			var result = new Utf32String( limitBytes );
			var chars = result.GetChars();
			var table = Encoding.GetConversionTable( codePage );

			for( var i = 0U; i < limitBytes; ++i )
				chars[i] = table[memory[i]];

			result.UpdateHash();
			return result;
		}
		/// @}

		/// @ Conversion from UTF8

		public static explicit operator Utf32String( Utf8String value ) { return FromUtf8( value ); }
		public static explicit operator Utf32String( CUtf8String value ) { return FromUtf8( value ); }
		public static explicit operator Utf32String( CUtf8StringSpan value ) { return FromUtf8( value ); }

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

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

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

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

			uint charsCount;
			Unicode.GetUtf8ByteCountInUtf32( memory, limitBytes, charsCount );

			var result = new Utf32String( charsCount );

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

			chars[charsCount] = '\0';

			result.UpdateHash();
			return result;
		}

		/// @}

		/// @ Conversion from UTF16

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

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

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

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

		public static Utf32String FromUtf16( char* memory, uint limitChars ) {
			if( memory == null ) return null;
			if( memory[0] == '\0' & limitChars == 0 ) return "";

			uint charsCount;
			Unicode.GetUtf16ByteCountInUtf32( memory, limitChars, charsCount );

			var result = new Utf32String( charsCount );

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

			chars[charsCount] = '\0';

			result.UpdateHash();
			return result;
		}

		/// @}

		/// @ Conversion from UTF32

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

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

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

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

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

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

			result.UpdateHash();
			return result;
		}

		/// @}

		public thistype ToUpper() {
			if( IsNullOrEmpty ) return this;

			using( var builder = StringBuilder.CachedBuilders.PopScoped() ) {
				builder.Value.Append( this );
				builder.Value.ToUpper();
				return builder.Value.ToUtf32String();
			}
		}

		public thistype ToLower() {
			if( IsNullOrEmpty ) return this;

			using( var builder = StringBuilder.CachedBuilders.PopScoped() ) {
				builder.Value.Append( this );
				builder.Value.ToLower();
				return builder.Value.ToUtf32String();
			}
		}

		public override string ToString() { return ( string ) this; }

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

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

		public int IndexOf( thistype value, int startIndex ) {
			if( !Assert.IsFalse( startIndex < 0 || ( uint ) startIndex >= Length || value == "" ) )
				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;
		}
	}

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

		public CUtf32String( uint* location ) {
			this = bitcast<thistype>( location );
		}

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

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

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

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

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

		public static void Concatenate( CUtf32String destinationString, CUtf32String 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( CUtf32String leftString, CUtf32String 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( CUtf32String leftString, CUtf32String 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( CUtf32String 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( CUtf32String leftString, CUtf32String rightString ) {
			var left = leftString.GetChars();
			var right = rightString.GetChars();

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

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

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

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

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

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

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

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

			var result = 0U;

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

			return result;
		}

		public static uint ComputeLength( uint* 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( uint* 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( uint* 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( uint );
			return result;
		}
	}

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

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

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

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

		public uint* GetChars() { return Location; }

		public static bool AreEqual( CUtf32StringSpan& leftString, CUtf32StringSpan& 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 ==( CUtf32StringSpan& left, CUtf32StringSpan& right ) { return AreEqual( left, right ); }
		public static bool operator !=( CUtf32StringSpan& left, CUtf32StringSpan& right ) { return !AreEqual( left, right ); }

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

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

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

		public bool StartsWith( CUtf32StringSpan 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( CUtf32StringSpan 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( Utf32String 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( Utf32String 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 );
		}
	}
}