'Classes/System/Text/StringBuilder.ab

#require <Classes/ActiveBasic/Strings/Strings.ab>

Namespace System
Namespace Text

Class StringBuilder
	'Inherits ISerializable
Public
	Sub StringBuilder()
		initialize(1024)
	End Sub

	Sub StringBuilder(capacity As Long)
		initialize(capacity)
	End Sub

	Sub StringBuilder(s As String)
		initialize(s, 0, s.Length, s.Length * 2)
	End Sub

	Sub StringBuilder(capacity As Long, maxCapacity As Long)
		initialize(capacity, maxCapacity)
	End Sub

	Sub StringBuilder(s As String, capacity As Long)
		initialize(s, 0, s.Length, capacity)
	End Sub

	Sub StringBuilder(s As String, startIndex As Long, length As Long, capacity As Long)
		initialize(s, startIndex, length, capacity)
	End Sub

	'Methods

	Function Append(x As Boolean) As StringBuilder
		Append(Str$(x))
		Return This
	End Function

	Function Append(x As StrChar) As StringBuilder
		EnsureCapacity(size + 1)
		separateBuffer()
		chars[size] = x
		size++
		Return This
	End Function

#ifdef __STRING_IS_NOT_UNICODE
	Function Append(x As Word) As StringBuilder
		Append(Str$(x As DWord))
		Return This
	End Function
#else
	Function Append(x As SByte) As StringBuilder
		Append(Str$(x As Long))
		Return This
	End Function
#endif
	
	Function Append(x As Byte) As StringBuilder
		Append(Str$(x As DWord))
		Return This
	End Function

	Function Append(x As Integer) As StringBuilder
		Append(Str$(x As Long))
		Return This
	End Function

	Function Append(x As Long) As StringBuilder
		Append(Str$(x))
		Return This
	End Function

	Function Append(x As DWord) As StringBuilder
		Append(Str$(x))
		Return This
	End Function

	Function Append(x As Int64) As StringBuilder
		Append(Str$(x))
		Return This
	End Function

	Function Append(x As QWord) As StringBuilder
		Append(Str$(x))
		Return This
	End Function

	Function Append(x As Single) As StringBuilder
		Append(Str$(x))
		Return This
	End Function

	Function Append(x As Double) As StringBuilder
		Append(Str$(x))
		Return This
	End Function

	Function Append(x As Object) As StringBuilder
		Append(x.ToString)
		Return This
	End Function

	Function Append(c As StrChar, n As Long) As StringBuilder
		EnsureCapacity(size + n)
		ActiveBasic.Strings.ChrFill(VarPtr(chars[size]), n As SIZE_T, c)
		size += n
		Return This
	End Function

	Function Append(s As String) As StringBuilder
		If Not String.IsNullOrEmpty(s) Then
			appendCore(s, 0, s.Length)
		End If
		Return This
	End Function

	Function Append(s As String, startIndex As Long, count As Long) As StringBuilder
		If s = 0 Then
			If startIndex = 0 And count = 0 Then
				Return This
			Else
				'Throw ArgumentNullException
			End If
		End If
		StringBuilder.rangeCheck2(s.Length, startIndex, count)
		appendCore(s, startIndex, count)
		Return This
	End Function

	Function Append(s As *StrChar, startIndex As Long, count As Long) As StringBuilder
		If s = 0 Then
			If startIndex = 0 And count = 0 Then
				Return This
			Else
				'Throw ArgumentNullException
			End If
		ElseIf startIndex < 0 Or count < 0 Then
			'Throw ArgumentOutOfRangeException
		End If
		appendCore(s, startIndex, count)
		Return This
	End Function
Private
	Sub appendCore(s As *StrChar, start As Long, count As Long)
		EnsureCapacity(size + count)
		separateBuffer()
		ActiveBasic.Strings.ChrCopy(VarPtr(chars[size]), VarPtr(s[start]), count As SIZE_T)
		size += count
	End Sub

	Sub appendCore(s As String, start As Long, count As Long)
		EnsureCapacity(size + count)
		separateBuffer()
		s.CopyTo(start, chars, size, count)
		size += count
	End Sub
Public
	'AppendFormat

	Function AppendLine() As StringBuilder
		separateBuffer()
		Append(Environment.NewLine)
		Return This
	End Function

	Function AppendLine(s As String) As StringBuilder
		EnsureCapacity(Capacity + s.Length + Environment.NewLine.Length)
		separateBuffer()
		Append(s)
		Append(Environment.NewLine)
		Return This
	End Function

	Const Sub CopyTo(sourceIndex As Long, ByRef dest[] As StrChar, destIndex As Long, count As Long)
		If dest = 0 Then
			'Throw ArgumentNullException
		ElseIf size < sourceIndex + count Or sourceIndex < 0 Or destIndex < 0 Or count < 0 Then
			'Throw ArgumentOutOfRangeException
		End If

		memcpy(VarPtr(dest[destIndex]), VarPtr(chars[sourceIndex]), count * SizeOf (StrChar))
	End Sub

	Function EnsureCapacity(c As Long) As Long
		If c < 0 Or c > MaxCapacity Then
			'Throw ArgumentOutOfRangeException
		ElseIf c > Capacity Then
			Dim p = GC_malloc_atomic((c + 1) * SizeOf (StrChar)) As *StrChar
			If p = 0 Then
				'Throw OutOfMemoryException
				Debug
			End If
			ActiveBasic.Strings.ChrCopy(p, chars, size As SIZE_T)
			chars = p
			capacity = c
			stringized = False
		End If
	End Function

	'Override Function Equals(o As Object) As Boolean

	Const Function Equals(s As StringBuilder) As Boolean
		Return ActiveBasic.Strings.StrCmp(chars, s.chars) = 0 _
			And capacity = s.capacity _
			And maxCapacity = s.maxCapacity
	End Function

	Override Function GetHashCode() As Long
#ifdef __STRING_IS_NOT_UNICODE
		Dim n = (size + 1) >> 1
#else
		Dim n = size
#endif
		Return _System_GetHashFromWordArray(chars As *Word, n As SIZE_T) Xor capacity Xor maxCapacity
	End Function

	Function Insert(i As Long, x As Boolean) As StringBuilder
		rangeCheck(i)
		insertCore(i, Str$(x))
		Return This
	End Function

	Function Insert(i As Long, x As StrChar) As StringBuilder
		Insert(i, VarPtr(x), 0, 1)
		Return This
	End Function
#ifdef __STRING_IS_NOT_UNICODE
	Function Insert(i As Long, x As Word) As StringBuilder
		rangeCheck(i)
		insertCore(i, Str$(x As DWord))
		Return This
	End Function
#else
	Function Insert(i As Long, x As SByte) As StringBuilder
		rangeCheck(i)
		insertCore(i, Str$(x As Long))
		Return This
	End Function
#endif

	Function Insert(i As Long, x As Byte) As StringBuilder
		rangeCheck(i)
		insertCore(i, Str$(x))
		Return This
	End Function

	Function Insert(i As Long, x As Integer) As StringBuilder
		rangeCheck(i)
		insertCore(i, Str$(x))
		Return This
	End Function

	Function Insert(i As Long, x As Long) As StringBuilder
		rangeCheck(i)
		insertCore(i, Str$(x))
		Return This
	End Function

	Function Insert(i As Long, x As DWord) As StringBuilder
		rangeCheck(i)
		insertCore(i, Str$(x))
		Return This
	End Function

	Function Insert(i As Long, x As Int64) As StringBuilder
		rangeCheck(i)
		insertCore(i, Str$(x))
		Return This
	End Function

	Function Insert(i As Long, x As Single) As StringBuilder
		rangeCheck(i)
		insertCore(i, Str$(x))
		Return This
	End Function

	Function Insert(i As Long, x As Double) As StringBuilder
		rangeCheck(i)
		insertCore(i, Str$(x))
		Return This
	End Function

	Function Insert(i As Long, s As String) As StringBuilder
		rangeCheck(i)
		insertCore(i, s)
		Return This
	End Function

	Function Insert(i As Long, o As Object) As StringBuilder
		rangeCheck(i)
		insertCore(i, o.ToString)
		Return This
	End Function

	Function Insert(index As Long, x As String, n As Long) As StringBuilder
		rangeCheck(index)
		If n < 0 Then
			'Throw New ArgumentOutOfRangeException
			Debug
		End If
		Dim len = x.Length
		Dim lenTotal = len * n
		Dim newSize = size + lenTotal
		EnsureCapacity(newSize)
		separateBuffer()

		' TODO: fix me!（定義されていない変数iが使われています）
		'ActiveBasic.Strings.ChrMove(VarPtr(chars[i + lenTotal]), VarPtr(chars[i]), (size - index) As SIZE_T)

		Dim i As Long
		For i = 0 To ELM(n)
			x.CopyTo(0, chars, size + i * len, len)
		Next
		size = newSize
		Return This
	End Function


	Function Insert(i As Long, x As *StrChar, index As Long, count As Long) As StringBuilder
		rangeCheck(i)
		If x = 0 Then
			'Throw New ArgumentNullException
			Debug
		ElseIf index < 0 Or count < 0 Then
			'Throw New ArgumentNullException
			Debug
		End If

		Dim newSize = size + count
		EnsureCapacity(newSize)
		separateBuffer()
		ActiveBasic.Strings.ChrMove(VarPtr(chars[i + count]), VarPtr(chars[i]), (size - i) As SIZE_T)
		ActiveBasic.Strings.ChrCopy(VarPtr(chars[i]), VarPtr(x[index]), count As SIZE_T)
		size = newSize
		Return This
	End Function

Private
	Sub insertCore(i As Long, s As String)
		Dim newSize = size + s.Length
		EnsureCapacity(newSize)
		separateBuffer()
		ActiveBasic.Strings.ChrMove(VarPtr(chars[i + s.Length]), VarPtr(chars[i]), (size - i) As SIZE_T)
		s.CopyTo(0, chars, i, s.Length)
		size = newSize
	End Sub
Public

	Function Remove(startIndex As Long, length As Long) As StringBuilder
		rangeCheck(startIndex, length)
		separateBuffer()

		Dim moveStart = startIndex + length
		ActiveBasic.Strings.ChrMove(
			VarPtr(chars[startIndex]), VarPtr(chars[moveStart]), (size - moveStart) As SIZE_T)
		size -= length
		Return This
	End Function

	Function Replace(oldChar As StrChar, newChar As StrChar) As StringBuilder
		replaceCore(oldChar, newChar, 0, size)
		Return This
	End Function

	Function Replace(oldStr As String, newStr As String) As StringBuilder
		replaceCore(oldStr, newStr, 0, size)
		Return This
	End Function

	Function Replace(oldChar As StrChar, newChar As StrChar, startIndex As Long, count As Long) As StringBuilder
		rangeCheck(startIndex, count)
		replaceCore(oldChar, newChar, startIndex, count)
		Return This
	End Function

	Function Replace(oldStr As String, newStr As String, startIndex As Long, count As Long) As StringBuilder
		rangeCheck(startIndex, count)
		replaceCore(oldStr, newStr, startIndex, count)
		Return This
	End Function
Private
	Sub replaceCore(oldChar As StrChar, newChar As StrChar, start As Long, count As Long)
		separateBuffer()
		Dim i As Long
		Dim last = ELM(start + count)
		For i = start To last
			If chars[i] = oldChar Then
				chars[i] = newChar
			End If
		Next
	End Sub

	Sub replaceCore(oldStr As String, newStr As String, start As Long, count As Long)
		If Object.ReferenceEquals(oldStr, Nothing) Then
			'Throw ArgumentNullException
			Debug
		ElseIf oldStr.Length = 0 Then
			'Throw ArgumentException
			Debug
		End If

		Dim s = New StringBuilder(capacity, maxCapacity)
		Dim curPos = 0 As Long
		Do
			Dim nextPos = ActiveBasic.Strings.ChrFind(VarPtr(chars[curPos]) As *StrChar, size As SIZE_T, StrPtr(oldStr), oldStr.Length As SIZE_T)
			If nextPos = -1 As SIZE_T Then
				Exit Sub
			End If
			
			s.appendCore(chars, curPos, nextPos)
			s.Append(newStr)
			curPos += nextPos + oldStr.Length
		Loop
		chars = s.chars
		size = s.size
	End Sub

Public
	Override Function ToString() As String
		chars[size] = 0
		Return New String(This)
	End Function

	Const Function ToString(startIndex As Long, length As Long) As String
		rangeCheck(startIndex, length)
		Return New String(chars, startIndex, length)
	End Function

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

	Sub Operator []=(i As Long, c As StrChar)
		Chars[i] = c
	End Sub

	'Properties
	Const Function Capacity() As Long
		Return capacity
	End Function

	Sub Capacity(c As Long)
		If c < size Or c > MaxCapacity Then 'sizeとの比較でcが負の場合も対応
			'Throw ArgumentOutOfRangeException
		End If
		EnsureCapacity(c)
	End Sub

	Const Function Chars(i As Long) As StrChar
		If i >= Length Or i < 0 Then
			'Throw IndexOutOfRangeException
			Debug
		End If
		Return chars[i]
	End Function

	Sub Chars(i As Long, c As StrChar)
		If i >= Length Or i < 0 Then
			'Throw ArgumentOutOfRangeException
			Debug
		End If
		chars[i] = c
	End Sub

	Const Function Length() As Long
		Return size
	End Function

	Sub Length(i As Long)
		EnsureCapacity(i) 'iが適切な値かどうかの確認はこの中で行う
		If size < i Then
			ActiveBasic.Strings.ChrFill(VarPtr(chars[size]), (i - size + 1) As SIZE_T, 0 As StrChar)
		End If
		size = i
	End Sub

	Const Function MaxCapacity() As Long
		Return maxCapacity
	End Function

	Function __Chars() As *StrChar
		Return chars
	End Function

	Sub __Stringized()
		stringized = True
	End Sub

Private
	Sub initialize(capacity As Long, maxCapacity = LONG_MAX As Long)
		If capacity < 0 Or maxCapacity < 1 Or maxCapacity < capacity Then
			'Throw ArgumentOutOfRangeException
		End If

		If capacity = 0 Then
			This.capacity = 1024
		Else
			This.capacity = capacity
		End If

		This.maxCapacity = maxCapacity
		This.size = 0
		This.chars = GC_malloc_atomic((This.capacity + 1) * SizeOf (StrChar))
		If chars = 0 Then
			'Throw OutOfMemoryException
			Debug
		End If

	End Sub

	Sub initialize(s As String, startIndex As Long, length As Long, capacity As Long, maxCapacity = LONG_MAX As Long)
		StringBuilder.rangeCheck2(s.Length, startIndex, length)

		initialize(Math.Max(capacity, length), maxCapacity)

		If Not String.IsNullOrEmpty(s) Then
			s.CopyTo(startIndex, chars, 0, length)
			size = s.Length
		End If
	End Sub

	Sub rangeCheck(index As Long)
		If index < 0 Or index > size Then
			'Throw ArgumentOutOfRangeException
			Debug
		End If
	End Sub

	Sub rangeCheck(startIndex As Long, count As Long)
		StringBuilder.rangeCheck2(size, startIndex, count)
	End Sub

	Static Sub rangeCheck2(length As Long, startIndex As Long, count As Long)
		'length < 0は判定に入っていないことに注意
		If startIndex < 0 Or count < 0 Or startIndex + count > length Then
			'Throw ArgumentOutOfRangeException
			Debug
		End If
	End Sub

	Sub separateBuffer()
		If stringized Then
			Dim newChars = GC_malloc_atomic(SizeOf (StrChar) * capacity) As *StrChar
			ActiveBasic.Strings.ChrCopy(newChars, chars, capacity As SIZE_T)
			chars = newChars
			stringized = False
		End If
	End Sub

	chars As *StrChar
	maxCapacity As Long
	capacity As Long
	size As Long
	stringized As Boolean
End Class

End Namespace 'Text
End Namespace 'System

'暫定
Function StrPtr(sb As System.Text.StringBuilder) As *StrChar
	Return sb.__Chars
End Function
