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

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

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

		public readonly uint Length; // Number of characters needed to encode this string in UTF16
		public readonly uint ByteLength;
		public readonly uint Hash;

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

		private Utf8String() {
		}

		private Utf8String( uint byteLength, uint length = 0 ) {
			extra {
				return sizeof( byte ) * ( byteLength + 1 );
			}
			body {
				ByteLength = byteLength;
				Length = length;
			}
		}

		public Utf8String( Utf8String value ) {
			extra {
				return value.ByteLength + 1;
			}
			body {
				Length = value.Length;
				ByteLength = value.ByteLength;
				Hash = value.Hash;

				Memory.Copy( GetChars(), value.GetChars(), ByteLength + 1 );
			}
		}

		public override uint GetHashCode() { return Hash; }

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

		public static bool operator ==( Utf8String left, Utf8String 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 || left.ByteLength != right.ByteLength ) return false;

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

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

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

			var resultLength = left.Length + right.Length;
			var resultByteLength = left.ByteLength + right.ByteLength;
			var result = new Utf8String( resultByteLength, resultLength );

			var chars = result.GetChars();

			Memory.Copy( chars, left.GetChars(), left.ByteLength );
			Memory.Copy( chars + left.ByteLength, right.GetChars(), right.ByteLength + 1 );
			chars[resultByteLength] = '\0';

			result.UpdateHash();
			return result;
		}

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

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

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

		/// @ Conversion from SBCS
		public static explicit operator Utf8String( SbcsString value ) { return FromSBCS( value ); }
		public static explicit operator Utf8String( CString value ) { return FromSBCS( value ); }
		public static explicit operator Utf8String( CStringSpan value ) { return FromSBCS( value ); }

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

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

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

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

		public static Utf8String FromSBCS( byte* sbcsText, uint sbcsTextLength = 0 ) { return FromSBCS( Environment.DefaultCodePage, sbcsText, sbcsTextLength ); }
		public static Utf8String FromSBCS( CodePageID codePage, byte* sbcsText, uint sbcsTextLength = 0 ) {
			if( sbcsText == null ) return null;
			if( sbcsText[0] == 0 & sbcsTextLength == 0 ) return "";

			uint characterCount16, bytesCount;
			Unicode.GetSbcsByteCountInUtf8( codePage, sbcsText, sbcsTextLength, characterCount16, bytesCount );

			var result = new Utf8String( bytesCount, characterCount16 );
			var resultChars = result.GetChars();

			Unicode.ConvertSbcsCharactersToUtf8( Environment.DefaultCodePage, resultChars, bytesCount, sbcsText, sbcsTextLength );

			result.UpdateHash();
			return result;
		}

		/// @}

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

		public static Utf8String FromUtf8( CUtf8String value ) { return FromUtf8( value.GetChars(), 0U ); }
		public static Utf8String FromUtf8( CUtf8StringSpan value ) { return FromUtf8( value.GetChars(), value.ByteLength ); }
		public static Utf8String FromUtf8( byte* chars, uint byteLength = 0 ) {
			if( chars == null ) return null;
			if( chars[0] == 0 & byteLength == 0 ) return "";

			uint length;
			Unicode.GetUtf8CodeUnitCount( chars, byteLength, length, 0U );

			var result = new Utf8String( byteLength, length );
			var resultChars = result.GetChars();

			Memory.Copy( resultChars, chars, byteLength );
			resultChars[byteLength] = 0;

			result.UpdateHash();
			return result;
		}

		/// @}

		/// @{ UTF16 Conversion
		public static explicit operator Utf8String( Utf16String value ) { return FromUtf16( value ); }
		public static explicit operator Utf8String( CUtf16String value ) { return FromUtf16( value ); }
		public static explicit operator Utf8String( CUtf16StringSpan value ) { return FromUtf16( value ); }

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

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

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

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

			uint bytesCount, charsCount;
			Unicode.GetUtf16ByteCountInUtf8( memory, limitChars, charsCount, bytesCount );

			var result = new Utf8String( bytesCount );
			*cast<uint*>( bitcast<byte*>( result ) + memberinfo( Length ).ByteOffset ) = charsCount;

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

			chars[bytesCount] = '\0';

			result.UpdateHash();
			return result;
		}

		/// @}

		/// @{ UTF32 Conversion
		public static explicit operator Utf8String( Utf32String value ) { return FromUtf32( value ); }
		public static explicit operator Utf8String( CUtf32String value ) { return FromUtf32( value ); }
		public static explicit operator Utf8String( CUtf32StringSpan value ) { return FromUtf32( value ); }

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

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

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

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

			uint bytesCount, charsCount;
			Unicode.GetUtf32ByteCountInUtf8( memory, limitChars, charsCount, bytesCount );

			var result = new Utf8String( bytesCount );
			*cast<uint*>( bitcast<byte*>( result ) + memberinfo( Length ).ByteOffset ) = charsCount;

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

			chars[bytesCount] = '\0';

			result.UpdateHash();
			return result;
		}

		/// @}

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

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

			return string.Compare( ( string ) left, ( string ) right, options );
		}

		public int CompareTo( string value ) {
			if( this == null & value == null ) return 0;
			if( this == null ^ value == null ) return value == null ? 1 : -1;

			return cast<string>( this ).CompareTo( ( string ) value );
		}

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

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

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

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

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

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

		public bool EndsWith( Utf8String value ) {
			if( value.IsNullOrEmpty ) return false;

			if( cast<RuntimeObjectBase>( this ) == cast<RuntimeObjectBase>( value ) ) return true;
			if( value.ByteLength > ByteLength ) return false;

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

		public bool StartsWith( Utf8String value ) {
			if( value.IsNullOrEmpty ) return false;

			if( cast<RuntimeObjectBase>( this ) == cast<RuntimeObjectBase>( value ) ) return true;
			if( value.ByteLength > ByteLength ) return false;

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

		// TODO
		public bool Contains( Utf8String value ) {
			if( value.IsNullOrEmpty ) return false;
			return ( ( string ) this ).Contains( ( string ) value );
		}

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

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

		public CUtf8String( byte* location ) { this = bitcast<thistype>( location ); }
		public CUtf8String( sbyte* location ) { this = bitcast<thistype>( location ); }

		public uint Length {
			get {
				uint result;
				Unicode.GetUtf8CodeUnitCount( GetChars(), 0U, result, 0U );
				return result;
			}
		}

		public uint ByteLength {
			get {
				return CString.ComputeLength( GetChars() );
			}
		}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		public static uint GetHashAndByteLength( byte* chars, uint& resultLength ) {
			return CString.GetHashAndByteLength( chars, resultLength );
		}

		[ForceInline]
		public byte* GetChars() { return bitcast<byte*>( this ); }

		public static uint ComputeHashCode( byte* chars, uint byteLength ) {
			return CString.ComputeHashCode( chars, byteLength );
		}
	}

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

		public byte* Location;
		public uint ByteLength;

		public CUtf8StringSpan( byte* location, uint byteLength ) { Location = location; ByteLength = byteLength; }

		public uint GetHashCode() { return CUtf8String.ComputeHashCode( Location, ByteLength ); }

		public byte* GetChars() { return Location; }

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

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

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

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

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

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

		/// Dangerous operator. Result will correct while 'value' is alive.
		public static implicit operator CUtf8StringSpan( Utf8String value ) { return value != null ? new CUtf8StringSpan( value.GetChars(), value.ByteLength ) : CUtf8StringSpan.Null; }
		public static implicit operator CUtf8StringSpan( CUtf8String value ) { return new CUtf8StringSpan( value.GetChars(), value.ByteLength ); }

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

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

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

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

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

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

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

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