' Classes/System/String.ab

Namespace System

	Class String
		Implements /*ICloneable, IConvertible, IComparable<String>, IEnumerable<Char>, IEquatable<String>*/

		m_Length As Long
		Chars As *Char

		Sub validPointerCheck(p As VoidPtr, size = 1 As Long)
			If p = 0 Then
				Throw New ArgumentException
			ElseIf IsBadReadPtr(p, size As ULONG_PTR) Then
				Throw New ArgumentException
			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 < 0 Then
				Throw New ArgumentOutOfRangeException("start or length or both")
			End If
			validPointerCheck(VarPtr(initStr[start]), length)
			Assign(VarPtr(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 < 0 Then
				Throw New ArgumentOutOfRangeException("String constructor: One or more arguments are out of range value.", "start or length or both")
			End If
			validPointerCheck(initStr + start, length)
			Assign(initStr + start, length)
		End Sub

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

		Sub String(sb As 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 *Char
			Return Chars
		End Function

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

		Const Function Operator + (y As PCSTR) As String
			If y = 0 Then
				Return This
			Else
				Return Concat(y, lstrlenA(y))
			End If
		End Function

		Const Function Operator + (y As PCWSTR) As String
			If y = 0 Then
				Return This
			Else
				Return Concat(y, lstrlenW(y))
			End If
		End Function

		Const Function Operator + (y As String) As String
			If ActiveBasic.IsNothing(y) Then
				Return This
			Else
				Return Concat(y.Chars, y.m_Length)
			End If
		End Function

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

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

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

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

		Const Function Operator == (y As *Char) As Boolean
			Return CompareOrdinal(This, y) = 0
		End Function

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

		Const Function Operator <> (y As *Char) As Boolean
			Return CompareOrdinal(This, y) <> 0
		End Function

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

		Const Function Operator < (y As *Char) As Boolean
			Return CompareOrdinal(This, y) < 0
		End Function

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

		Const Function Operator > (y As *Char) As Boolean
			Return CompareOrdinal(This, y) > 0
		End Function

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

		Const Function Operator <= (y As *Char) As Boolean
			Return CompareOrdinal(This, y) <= 0
		End Function

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

		Const Function Operator >= (y As *Char) As Boolean
			Return CompareOrdinal(This, y) >= 0
		End Function

		/*!
		@brief 単語順での文字列比較
		@auther Egtra
		*/
		Static Function Compare(x As String, y As String) As Long
			Return Compare(x, y, False)
		End Function

		Static Function Compare(x As String, y As String, ignoreCase As Boolean) As Long
			Dim lhs = removeNull(x)
			Dim rhs = removeNull(y)
			Return compareImpl(lhs.Chars, lhs.Length, rhs.Chars, rhs.Length, ignoreCase)
		End Function

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

		Static Function Compare(x As String, indexX As Long, y As String, indexY As Long, length As Long, ignoreCase As Boolean) As Long
			Dim lhs = removeNull(x)
			Dim rhs = removeNull(y)
			If lhs.Length > indexX Or indexX < 0 Then
				Throw New ArgumentOutOfRangeException("indexX")
			ElseIf rhs.Length > indexY Or indexY < 0 Then
				Throw New ArgumentOutOfRangeException("indexY")
			ElseIf length < 0 Then
				Throw New ArgumentOutOfRangeException("length")
			End If
			Dim cmpLen = Math.Min(Math.Min(lhs.Length - indexX, rhs.Length - indexY), length)
			Return compareImpl(VarPtr(lhs.Chars[indexX]), cmpLen, VarPtr(rhs.Chars[indexY]), cmpLen, ignoreCase)
		End Function

		/*!
		@brief 序数での文字列比較
		*/
		Static Function CompareOrdinal(x As String, y As String) As Long
			Dim lhs = removeNull(x)
			Dim rhs = removeNull(y)
			Return compareOrdinalImpl(lhs.Chars, lhs.Length, rhs.Chars, rhs.Length)
		End Function

		Static Function CompareOrdinal(x As String, indexX As Long, y As String, indexY As Long, length As Long) As Long
			Dim lhs = removeNull(x)
			Dim rhs = removeNull(y)
			If lhs.Length > indexX Or indexX < 0 Then
				Throw New ArgumentOutOfRangeException("indexX")
			ElseIf rhs.Length > indexY Or indexY < 0 Then
				Throw New ArgumentOutOfRangeException("indexY")
			ElseIf length < 0 Then
				Throw New ArgumentOutOfRangeException("length")
			End If
			Dim cmpLen = Math.Min(Math.Min(lhs.Length - indexX, rhs.Length - indexY), length)
			Return compareOrdinalImpl(VarPtr(lhs.Chars[indexX]), cmpLen, VarPtr(rhs.Chars[indexY]), cmpLen)
		End Function

		Static Function CompareOrdinal(x As String, y As *Char) As Long
			Dim lhs = removeNull(x)
			Return compareOrdinalImpl(lhs.Chars, lhs.Length, y, lstrlen(y))
		End Function
		
	Private
		Static Function compareImpl(x As *Char, lenX As Long, y As *Char, lenY As Long, ignoreCase As Boolean) As Long
			Dim flags = 0 As DWord
			If ignoreCase Then
				flags = NORM_IGNORECASE
			End If
			Dim ret = CompareString(LOCALE_USER_DEFAULT, ignoreCase, x, lenX, y, lenY)
			Select Case ret
				Case CSTR_LESS_THAN
					compareImpl = -1
				Case CSTR_EQUAL
					compareImpl = 0
				Case CSTR_GREATER_THAN
					compareImpl = 1
				Case Else
					ActiveBasic.Windows.ThrowWithLastError("String.Compare")
			End Select
		End Function

		Static Function compareOrdinalImpl(x As *Char, lenX As Long, y As *Char, lenY As Long) As Long
			Return ActiveBasic.Strings.ChrCmp(x, lenX As SIZE_T, y, lenY 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
			If Not Object.Equals(This.GetType(), y.GetType()) Then
				Throw New ArgumentException("String.CompareTo: y is not String.", "y")
			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 Not ActiveBasic.IsNothing(s) Then
				If Object.Equals(This.GetType(), s.GetType()) Then
					Return This.Equals(s As String)
				End If
			End If
			Return False
		End Function

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

		Sub Assign(text As PCSTR, textLengthA As Long)
#ifdef UNICODE
			Dim textLengthW = MultiByteToWideChar(CP_THREAD_ACP, 0, text, textLengthA, 0, 0)
			AllocStringBuffer(textLengthW)
			MultiByteToWideChar(CP_THREAD_ACP, 0, text, textLengthA, Chars, textLengthW)
			Chars[textLengthW] = 0
#else
			AssignFromCharPtr(text, textLengthA)
#endif
		End Sub

		Sub Assign(text As PCWSTR, textLengthW As Long)
#ifdef UNICODE
			AssignFromCharPtr(text, textLengthW)
#else
			Dim textLengthA = WideCharToMultiByte(CP_THREAD_ACP, 0, text, textLengthW, 0, 0, 0, 0)
			AllocStringBuffer(textLengthA)
			WideCharToMultiByte(CP_THREAD_ACP, 0, text, textLengthW, Chars, textLengthA, 0, 0)
			Chars[textLengthA] = 0
#endif
		End Sub

	Private
		Static Function ConcatChar(text1 As *Char, text1Length As Long, text2 As *Char, text2Length As Long) As String
			ConcatChar = New String()
			With ConcatChar
				.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 UNICODE
			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 As SIZE_T)
				MultiByteToWideChar(CP_THREAD_ACP, 0, text, len, VarPtr(.Chars[m_Length]), lenW)
				.Chars[m_Length + lenW] = 0
			End With
#else
			Return ConcatChar(This.Chars, m_Length, text, len)
#endif
		End Function

		Const Function Concat(text As PCWSTR, len As Long) As String
#ifdef UNICODE
			Return ConcatChar(This.Chars, m_Length, text, len)
#else
			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
#endif
		End Function

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

		Static Function Concat(x As String, y As String, z As String) As String
			Dim sb = New Text.StringBuilder(removeNull(x).Length + removeNull(y).Length + removeNull(z).Length)
			sb.Append(x).Append(y).Append(z)
			Concat = sb.ToString
		End Function

		Static Function Concat(x As String, y As String, z As String, w As String) As String
			Dim sb = New Text.StringBuilder(removeNull(x).Length + removeNull(y).Length + removeNull(z).Length + removeNull(w).Length)
			sb.Append(x).Append(y).Append(z).Append(w)
			Concat = sb.ToString
		End Function

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

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

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

		Const Function Contains(c As Char) As Boolean
			Return IndexOf(c) >= 0
		End Function

		Const Function Contains(s As String) As Boolean
			If ActiveBasic.IsNothing(s) Then
				Throw New ArgumentNullException("String.Contains: An argument is null value.", "s")
			ElseIf s = "" Then
				Return True
			Else
				Return IndexOf(s, 0, m_Length) >= 0
			End If
		End Function

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

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

		Const Function IndexOf(c As Char, start As Long, count As Long) As Long
			rangeCheck(start, count)
			Return indexOfCore(c, start, count)
		End Function
	Private
		Const Function indexOfCore(c As Char, start As Long, count As Long) As Long
			indexOfCore = ActiveBasic.Strings.ChrFind(VarPtr(Chars[start]), count, c) As Long
			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 ActiveBasic.IsNothing(s) Then
				Throw New ArgumentNullException("String.IndexOf: An argument is out of range value.", "s")
			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(c As Char) As Long
			Return lastIndexOf(c, m_Length - 1, m_Length)
		End Function

		Const Function LastIndexOf(c As Char, start As Long) As Long
			rangeCheck(start)
			Return lastIndexOf(c, start, start + 1)
		End Function

		Const Function LastIndexOf(c As Char, start As Long, count As Long) As Long
			rangeCheck(start)
			Dim lastFindPos = start - (count - 1)
			If Not (m_Length > lastFindPos And lastFindPos >= 0) Then
				Throw New ArgumentOutOfRangeException("String.LastIndexOf: An argument is out of range value.", "count")
			End If
			Return lastIndexOf(c, start, count)
		End Function
	Private
		Const Function lastIndexOf(c As Char, start As Long, count As Long) As Long
			Dim lastFindPos = start - (count - 1)
			Dim i As Long
			For i = start To lastFindPos Step -1
				If Chars[i] = c Then
					Return i
				End If
			Next
			Return -1
		End Function

	Public
		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, start As Long, count As Long) As Long
			If ActiveBasic.IsNothing(s) Then
				Throw New ArgumentNullException("String.LastIndexOf: An argument is out of range value.", "s")
			End If

			If start < 0 Or start > m_Length - 1 Or _
				count < 0 Or count > start + 2 Then
				Throw New ArgumentOutOfRangeException("String.LastIndexOf: One or more arguments are out of range value.", "start or count or both")
			End If
			Dim length = s.m_Length
			If length > m_Length Then Return -1
			If length = 0 Then Return start

			Dim i As Long, j As Long
			For i = start To  start - 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(c As Char) As Boolean
			Return IndexOf(c) = 0
		End Function

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

		Const Function EndsWith(c As Char) As Boolean
			Return LastIndexOf(c) = m_Length - 1
		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 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 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 Char, newChar As Char) As String
			Dim sb = New 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 Text.StringBuilder(This)
			sb.Replace(oldStr, newStr)
			Return sb.ToString
		End Function

		Function Split(separator As System.Collections.Generic.List<String>) As System.Collections.Generic.List<String>
			Return Split(separator, -1, StringSplitOptions.None)
		End Function

		Function Split(separator As System.Collections.Generic.List<String>, options As StringSplitOptions) As System.Collections.Generic.List<String>
			Return Split(separator, -1, options)
		End Function

		Function Split(separator As System.Collections.Generic.List<String>, count As Long, options As StringSplitOptions) As System.Collections.Generic.List<String>
			Dim split As System.Collections.Generic.List<String>
			Dim index As Long, t1Index As Long, t2Index As Long
			Dim s As String, substring As String
			Dim flag As Boolean

			Do
				t1Index = Length
				flag = True
				Foreach s In separator
					t2Index = IndexOf(s, index)
					If t2Index > -1 Then
						t1Index = Math.Min(t1Index, t2Index)
						flag = False
					End If
				Next

				substring = Substring(index, t1Index - index)
				If Not ( IsNullOrEmpty(substring) and (options = StringSplitOptions.RemoveEmptyEntries) ) Then
					split.Add(substring)
				End If

				If flag Then Return split
				If split.Count = count Then Return split
				
				index = t1Index + 1
			Loop
		End Function

		Static Function Join(separator As String, strings As System.Collections.Generic.List<String>) As String
			Return Join(separator, strings, 0, strings.Count)
		End Function

		Static Function Join(separator As String, strings As System.Collections.Generic.List<String>, startIndex As Long, count As Long) As String
			If (startIndex+count > strings.Count) or (startIndex < 0) or (count < 1) Then
				Throw New ArgumentOutOfRangeException("String.Join: One or more arguments are out of range value.", "startIndex or count")
			End If

			Dim string As String
			Dim i As Long
			For i = startIndex To startIndex + count - 2
				string += strings[i] + separator
			Next
			Return string + strings[i]
		End Function

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

		Const Function ToUpper() As String
			Dim sb = New Text.StringBuilder(m_Length)
			sb.Length = m_Length
			Dim i As Long
			For i = 0 To ELM(m_Length)
				sb[i] = ActiveBasic.CType.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
/*
		Function Clone() As Object
			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 *Char, 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 UNICODE
			Dim size = m_Length
#else
			Dim size = (m_Length + 1) >> 1
#endif
			Return _System_GetHashFromWordArray(Chars As *Word, size) Xor m_Length
		End Function

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

		Function PadLeft(total As Long, c As Char) As String
			If total < 0 Then
				Throw New ArgumentOutOfRangeException("String.PadLeft: An arguments is out of range value.", "total")
			End If
			If total >= m_Length Then
				Return This
			End If
			Dim sb = New 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 Char)
		End Function

		Function PadRight(total As Long, c As Char) As String
			If total < 0 Then
				Throw New ArgumentOutOfRangeException("String.PadRight: An arguments is out of range value.", "total")
			End If
			If total >= m_Length Then
				Return This
			End If
			Dim sb = New Text.StringBuilder(total)
			sb.Append(This)
			sb.Append(c, total - m_Length)
			Return sb.ToString
		End Function
	Private
		Sub AllocStringBuffer(textLength As Long)
			If textLength < 0 Then
				Throw New ArgumentException
			End If
			Chars = GC_malloc_atomic(SizeOf(Char) * (textLength + 1))
			m_Length = textLength
		End Sub

		Sub AssignFromCharPtr(text As *Char, 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
				Throw New ArgumentOutOfRangeException("String: An argument is out of range value.", "index")
			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
				Throw New ArgumentOutOfRangeException("String: One or more arguments are out of range value.", "start or length or both")
			End If
		End Sub

		Static Function removeNull(s As String) As String
			If ActiveBasic.IsNothing(s) Then
				removeNull = Empty
			Else
				removeNull = s
			End If
		End Function
	End Class

	Enum StringSplitOptions
		None
		RemoveEmptyEntries
	End Enum

End Namespace
