' Classes/System/String.ab

#require <basic/function.sbp>
#require <Classes/System/Text/StringBuilder.ab>
#require <Classes/ActiveBasic/Strings/Strings.ab>

#ifdef __STRING_IS_NOT_ALWAYS_UNICODE
TypeDef StrChar = Char
#ifndef UNICODE
#define __STRING_IS_NOT_UNICODE
#endif
#else
TypeDef StrChar = WCHAR
#ifndef UNICODE
#define __STRING_UNICODE_WINDOWS_ANSI
#endif
#endif

Namespace System

	Class String
	'	Inherits IComparable, ICloneable, IConvertible, IEnumerable

		m_Length As Long
		Chars As *StrChar

		Sub validPointerCheck(p As VoidPtr, size = 1 As Long)
			If p As ULONG_PTR < &h10000 Then
				'Throw ArgumentException
				Debug
			ElseIf IsBadReadPtr(p, size As ULONG_PTR) Then
				'Throw ArgumentException
				Debug
			End If
		End Sub
	Public
		Static Const Empty = New String

		Sub String()
'			Chars = 0
'			m_Length = 0
		End Sub

		Sub String(initStr As PCWSTR)
			validPointerCheck(initStr)
			Assign(initStr, lstrlenW(initStr))
		End Sub

		Sub String(initStr As PCWSTR, length As Long)
			validPointerCheck(initStr, length)
			Assign(initStr, length)
		End Sub

		Sub String(initStr As PCWSTR, start As Long, length As Long)
			If start < 0 Or length Or start + length < 0 Then
				'Throw New ArgumentOutOfRangeException
			End If
			validPointerCheck(initStr + start, length)
			Assign(initStr + start, length)
		End Sub

		Sub String(initStr As PCSTR)
			validPointerCheck(initStr)
			Assign(initStr, lstrlenA(initStr))
		End Sub

		Sub String(initStr As PCSTR, length As Long)
			validPointerCheck(initStr, length)
			Assign(initStr, length)
		End Sub

		Sub String(initStr As PCSTR, start As Long, length As Long)
			If start < 0 Or length Or start + length < 0 Then
				'Throw New ArgumentOutOfRangeException
			End If
			validPointerCheck(initStr + start, length)
			Assign(initStr + start, length)
		End Sub
		
		Sub String(initStr As String)
			If Not String.IsNullOrEmpty(initStr) Then
				Assign(initStr.Chars, initStr.m_Length)
			End If
		End Sub

		Sub String(initChar As StrChar, length As Long)
			AllocStringBuffer(length)
			ActiveBasic.Strings.ChrFill(Chars, length, initChar)
			Chars[length] = 0
		End Sub

		Sub String(sb As System.Text.StringBuilder)
			Chars = StrPtr(sb)
			m_Length = sb.Length
			sb.__Stringized()
		End Sub

		Const Function Length() As Long
			Return m_Length
		End Function

		Function Operator() As *StrChar
			Return Chars
		End Function

		Const Function Operator [] (n As Long) As StrChar
			rangeCheck(n)
			Return Chars[n]
		End Function

		Const Function Operator + (y As PCSTR) As String
			Return Concat(y, lstrlenA(y))
		End Function

		Const Function Operator + (y As PCWSTR) As String
			Return Concat(y, lstrlenW(y))
		End Function

		Const Function Operator + (y As String) As String
			Return Concat(y.Chars, y.m_Length)
		End Function

		Const Function Operator & (y As PCSTR) As String
			Return This + y
		End Function

		Const Function Operator & (y As PCWSTR) As String
			Dim tempString = This + y
			Return tempString
		End Function

		Const Function Operator & (y As String) As String
			Dim tempString = This + y
			Return tempString
		End Function

		Const Function Operator == (y As String) As Boolean
			Return String.Compare(This, y) = 0
		End Function

		Const Function Operator == (y As *StrChar) As Boolean
			Return String.Compare(This, y) = 0
		End Function

		Const Function Operator <> (y As String) As Boolean
			Return String.Compare(This, y) <> 0
		End Function

		Const Function Operator <> (y As *StrChar) As Boolean
			Return String.Compare(This, y) <> 0
		End Function

		Const Function Operator < (y As String) As Boolean
			Return String.Compare(This, y) < 0
		End Function

		Const Function Operator < (y As *StrChar) As Boolean
			Return String.Compare(This, y) < 0
		End Function

		Const Function Operator > (y As String) As Boolean
			Return String.Compare(This, y) > 0
		End Function

		Const Function Operator > (y As *StrChar) As Boolean
			Return String.Compare(This, y) > 0
		End Function

		Const Function Operator <= (y As String) As Boolean
			Return String.Compare(This, y) <= 0
		End Function

		Const Function Operator <= (y As *StrChar) As Boolean
			Return String.Compare(This, y) <= 0
		End Function

		Const Function Operator >= (y As String) As Boolean
			Return String.Compare(This, y) >= 0
		End Function

		Const Function Operator >= (y As *StrChar) As Boolean
			Return String.Compare(This, y) >= 0
		End Function

		Static Function Compare(x As String, y As String) As Long
			Return CompareOrdinal(x, y)
		End Function

	Public
		Static Function Compare(x As String, indexX As Long, y As String, indexY As Long, length As Long) As Long
			Return String.CompareOrdinal(x, indexX, y, indexY, length)
		End Function

		Static Function CompareOrdinal(x As String, y As String) As Long
			Return String.CompareOrdinal(x.Chars, y.Chars)
		End Function

		Static Function CompareOrdinal(x As String, indexX As Long, y As String, indexY As Long, length As Long) As Long
			Return String.CompareOrdinal(x.Chars, indexX, y.Chars, indexY, length)
		End Function
	Private
		Static Function Compare(x As String, y As *StrChar) As Long
			Return String.CompareOrdinal(x, y)
		End Function

		Static Function CompareOrdinal(x As String, y As *StrChar) As Long
			Return String.CompareOrdinal(x.Chars, y)
		End Function

		Static Function CompareOrdinal(x As *StrChar, y As *StrChar) As Long
			If x = 0 Then
				If y = 0 Then
					Return 0
				Else
					Return -1
				End If
			ElseIf y = 0 Then
				Return 1
			End If
			Return ActiveBasic.Strings.StrCmp(x, y)
		End Function

		Static Function CompareOrdinal(x As *StrChar, indexX As Long, y As *StrChar, indexY As Long, length As Long) As Long
			If x = 0 Then
				If y = 0 Then
					Return 0
				Else
					Return -1
				End If
			ElseIf y = 0 Then
				Return 1
			End If
			Return ActiveBasic.Strings.ChrCmp(VarPtr(x[indexX]), VarPtr(y[indexY]), length As SIZE_T)
		End Function
	Public
		Function CompareTo(y As String) As Long
			Return String.Compare(This, y)
		End Function

		Function CompareTo(y As Object) As Long
			Dim s = y As String
	'		If y is not String Then
	'			Throw New ArgumentException
	'		End If
			Return CompareTo(y As String)
		End Function

		Function Equals(s As String) As Boolean
			Return This = s
		End Function

		Override Function Equals(s As Object) As Boolean
			If This.GetType = s.GetType Then
				Return This.Equals(s As String)
			End If
			Return False
		End Function

		Const Function StrPtr() As *StrChar
			Return Chars
		End Function
Private

		Sub Assign(text As PCSTR, textLengthA As Long)
#ifdef __STRING_IS_NOT_UNICODE
			AssignFromStrChar(text, textLengthA)
#else
			Dim textLengthW = MultiByteToWideChar(CP_THREAD_ACP, 0, text, textLengthA, 0, 0)
			If AllocStringBuffer(textLengthW) <> 0 Then
				MultiByteToWideChar(CP_THREAD_ACP, 0, text, textLengthA, Chars, textLengthW)
				Chars[textLengthW] = 0
			End If
#endif
		End Sub

		Sub Assign(text As PCWSTR, textLengthW As Long)
#ifdef __STRING_IS_NOT_UNICODE
			Dim textLengthA = WideCharToMultiByte(CP_THREAD_ACP, 0, text, textLengthW, 0, 0, 0, 0)
			If AllocStringBuffer(textLengthA) <> 0 Then
				WideCharToMultiByte(CP_THREAD_ACP, 0, text, textLengthW, Chars, textLengthA, 0, 0)
				Chars[textLengthA] = 0
			End If
#else
			AssignFromStrChar(text, textLengthW)
#endif
		End Sub

	Private
		Static Function ConcatStrChar(text1 As *StrChar, text1Length As Long, text2 As *StrChar, text2Length As Long) As String
			ConcatStrChar = New String()
			With ConcatStrChar
				.AllocStringBuffer(text1Length + text2Length)
				ActiveBasic.Strings.ChrCopy(.Chars, text1, text1Length As SIZE_T)
				ActiveBasic.Strings.ChrCopy(VarPtr(.Chars[text1Length]), text2, text2Length As SIZE_T)
				.Chars[text1Length + text2Length] = 0
			End With
		End Function
	Public
		Const Function Concat(text As PCSTR, len As Long) As String
#ifdef __STRING_IS_NOT_UNICODE
			Return ConcatStrChar(This.Chars, m_Length, text, len)
#else
			With Concat
				Dim lenW = MultiByteToWideChar(CP_THREAD_ACP, 0, text, len, 0, 0)
				Concat = New String
				.AllocStringBuffer(m_Length + lenW)
				ActiveBasic.Strings.ChrCopy(.Chars, This.Chars, m_Length)
				MultiByteToWideChar(CP_THREAD_ACP, 0, text, len, VarPtr(.Chars[m_Length]), lenW)
				.Chars[m_Length + lenW] = 0
			End With
#endif
		End Function

		Const Function Concat(text As PCWSTR, len As Long) As String
#ifdef __STRING_IS_NOT_UNICODE
			With Concat
				Concat = New String
				Dim lenA = WideCharToMultiByte(CP_THREAD_ACP, 0, text, len, 0, 0, 0, 0)
				.AllocStringBuffer(m_Length + lenA)
				ActiveBasic.Strings.ChrCopy(.Chars, This.Chars, m_Length As SIZE_T)
				WideCharToMultiByte(CP_THREAD_ACP, 0, text, len, VarPtr(.Chars[m_Length]), lenA, 0, 0)
				.Chars[m_Length + lenA] = 0
			End With
#else
			Return ConcatStrChar(This.Chars, m_Length, text, len)
#endif
		End Function

		Static Function Concat(x As String, y As String) As String
			If String.IsNullOrEmpty(x) Then
				Return y
			Else
				Return x.Concat(y.Chars, y.m_Length)
			End If
		End Function

		Static Function Concat(x As Object, y As Object) As String
			Return String.Concat(x.ToString, y.ToString)
		End Function

		Const Function Contains(s As String) As Boolean
			If Object.ReferenceEquals(s, Nothing) Then
				'Throw New ArgumentNullException
			End If
			Return IndexOf(s, 0, m_Length) >= 0
		End Function

		Const Function IndexOf(c As StrChar) As Long
			Return indexOfCore(c, 0, m_Length)
		End Function

		Const Function IndexOf(c As StrChar, start As Long) As Long
			rangeCheck(start)
			Return indexOfCore(c, start, m_Length - start)
		End Function

		Const Function IndexOf(c As StrChar, start As Long, count As Long) As Long
			rangeCheck(start, count)
			Return indexOfCore(c, start, count)
		End Function
	Private
		Const Function indexOfCore(c As StrChar, start As Long, count As Long) As Long
			indexOfCore = ActiveBasic.Strings.ChrFind(VarPtr(Chars[start]), count, c)
			If indexOfCore <> -1 Then
				indexOfCore += start
			End If
		End Function
	Public
		Const Function IndexOf(s As String) As Long
			Return IndexOf(s, 0, m_Length)
		End Function

		Const Function IndexOf(s As String, startIndex As Long) As Long
			Return IndexOf(s, startIndex, m_Length - startIndex)
		End Function

		Const Function IndexOf(s As String, startIndex As Long, count As Long) As Long
			rangeCheck(startIndex, count)
			If Object.ReferenceEquals(s, Nothing) Then
				'Throw New ArgumentNullException
				Debug
			End If

			Dim length = s.Length
			If length = 0 Then Return startIndex

			Dim i As Long, j As Long
			For i = startIndex To startIndex + count - 1
				For j = 0 To length - 1
					If Chars[i + j] = s[j] Then
						If j = length - 1 Then Return i
					Else
						Exit For
					End If
				Next
			Next
			Return -1
		End Function

		Const Function LastIndexOf(s As String) As Long
			Return LastIndexOf(s, m_Length - 1, m_Length)
		End Function

		Const Function LastIndexOf(s As String, startIndex As Long) As Long
			Return LastIndexOf(s, startIndex, startIndex + 1)
		End Function

		Const Function LastIndexOf(s As String, startIndex As Long, count As Long) As Long
			If Object.ReferenceEquals(s, Nothing) Then
				'Throw New ArgumentNullException
				Debug
			End If

			If startIndex < 0 Or startIndex > m_Length - 1 Or _
				count < 0 Or count > startIndex + 2 Then
				'Throw New ArgumentOutOfRangeException
				Debug
			End If
			Dim length = s.Length
			If length > m_Length Then Return -1
			If length = 0 Then Return startIndex

			Dim i As Long, j As Long
			For i = startIndex To  startIndex - count + 1 Step -1
				For j = length - 1 To 0 Step -1
					If Chars[i + j] = s[j] Then
						If j = 0 Then Return i
					Else
						Exit For
					End If
				Next
			Next
			Return -1
		End Function

		Const Function StartsWith(s As String) As Boolean
			Return IndexOf(s) = 0
		End Function

		Const Function EndsWith(s As String) As Boolean
			Return LastIndexOf(s) = m_Length - s.Length
		End Function

		Const Function Insert(startIndex As Long, text As String) As String
			Dim sb = New System.Text.StringBuilder(This)
			sb.Insert(startIndex, text)
			Return sb.ToString
		End Function

		Const Function Substring(startIndex As Long) As String
			rangeCheck(startIndex)
			Return Substring(startIndex, m_Length - startIndex)
		End Function

		Const Function Substring(startIndex As Long, length As Long) As String
			rangeCheck(startIndex, length)
			Return New String(Chars, startIndex, length)
		End Function

		Const Function Remove(startIndex As Long) As String
			rangeCheck(startIndex)
			Remove = Substring(0, startIndex)
		End Function

		Const Function Remove(startIndex As Long, count As Long) As String
			Dim sb = New System.Text.StringBuilder(This)
			sb.Remove(startIndex, count)
			Remove = sb.ToString
		End Function

		Static Function IsNullOrEmpty(s As String) As Boolean
			If Not Object.ReferenceEquals(s, Nothing) Then
				If s.m_Length > 0 Then
					Return False
				End If
			End If
			Return True
		End Function

		Const Function Replace(oldChar As StrChar, newChar As StrChar) As String
			Dim sb = New System.Text.StringBuilder(This)
			sb.Replace(oldChar, newChar)
			Replace = sb.ToString
		End Function

		Const Function Replace(oldStr As String, newStr As String) As String
			Dim sb = New System.Text.StringBuilder(This)
			sb.Replace(oldStr, newStr)
			Return sb.ToString
		End Function

		Const Function ToLower() As String
			Dim sb = New System.Text.StringBuilder(m_Length)
			sb.Length = m_Length
			Dim i As Long
			For i = 0 To ELM(m_Length)
				sb[i] = _System_ASCII_ToLower(Chars[i])
			Next
			Return sb.ToString
		End Function

		Const Function ToUpper() As String
			Dim sb = New System.Text.StringBuilder(m_Length)
			sb.Length = m_Length
			Dim i As Long
			For i = 0 To ELM(m_Length)
				sb[i] = _System_ASCII_ToUpper(Chars[i])
			Next
			Return sb.ToString
		End Function

		Override Function ToString() As String
			ToString = This
		End Function

		Const Function Clone() As String
			Clone = This
		End Function

		Static Function Copy(s As String) As String
			Copy = New String(s.Chars, s.m_Length)
		End Function

		Sub CopyTo(sourceIndex As Long, destination As *StrChar, destinationIndex As Long, count As Long)
			ActiveBasic.Strings.ChrCopy(VarPtr(destination[destinationIndex]), VarPtr(Chars[sourceIndex]), count As SIZE_T)
		End Sub

		Override Function GetHashCode() As Long
#ifdef __STRING_IS_NOT_UNICODE
			Dim size = (m_Length + 1) >> 1
#else
			Dim size = m_Length
#endif
			Return _System_GetHashFromWordArray(Chars As *Word, size) Xor size
		End Function

		Function PadLeft(total As Long) As String
			PadLeft(total, &h30 As StrChar)
		End Function

		Function PadLeft(total As Long, c As StrChar) As String
			If total < 0 Then
				'Throw New ArgumentException
			End If
			If total >= m_Length Then
				Return This
			End If
			Dim sb = New System.Text.StringBuilder(total)
			sb.Append(c, total - m_Length)
			sb.Append(This)
			Return sb.ToString
		End Function

		Function PadRight(total As Long) As String
			PadRight(total, &h30 As StrChar)
		End Function

		Function PadRight(total As Long, c As StrChar) As String
			If total < 0 Then
				'Throw New ArgumentException
			End If
			If total >= m_Length Then
				Return This
			End If
			Dim sb = New System.Text.StringBuilder(total)
			sb.Append(This)
			sb.Append(c, total - m_Length)
			Return sb.ToString
		End Function
	Private
		Function AllocStringBuffer(textLength As Long) As *StrChar
			If textLength < 0 Then
				Return 0
			End If
			AllocStringBuffer = GC_malloc_atomic(SizeOf(StrChar) * (textLength + 1))
			If AllocStringBuffer = 0 Then
				'Throw New OutOfMemoryException
			End If
			m_Length = textLength
			Chars = AllocStringBuffer
		End Function

		Sub AssignFromStrChar(text As *StrChar, textLength As Long)
			AllocStringBuffer(textLength)
			ActiveBasic.Strings.ChrCopy(Chars, text, textLength As SIZE_T)
			Chars[m_Length] = 0
		End Sub

		Const Sub rangeCheck(index As Long)
			If index < 0 Or index > m_Length Then
				Debug 'ArgumentOutOfRangeException
			End If
		End Sub
		
		Const Sub rangeCheck(start As Long, length As Long)
			If start < 0 Or start > This.m_Length Or length < 0 Then
				Debug 'ArgumentOutOfRangeException
			End If
		End Sub
	End Class

End Namespace
