'Classes/System/Text/StringBuilder.ab

Namespace System
Namespace Text

Class StringBuilder
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 Char) As StringBuilder
		EnsureCapacity(size + 1)
		separateBuffer()
		chars[size] = x
		size++
		Return This
	End Function
#ifdef UNICODE
	Function Append(x As SByte) As StringBuilder
		ActiveBasic.Strings.Detail.FormatIntegerD(This, x, DWORD_MAX, 0, 0)
		Return This
	End Function
#endif
	Function Append(x As Byte) As StringBuilder
		ActiveBasic.Strings.Detail.FormatIntegerU(This, x, DWORD_MAX, 0, 0)
		Return This
	End Function

	Function Append(x As Integer) As StringBuilder
		ActiveBasic.Strings.Detail.FormatIntegerD(This, x, DWORD_MAX, 0, 0)
		Return This
	End Function
#ifndef UNICODE
	Function Append(x As Word) As StringBuilder
		ActiveBasic.Strings.Detail.FormatIntegerU(This, x, DWORD_MAX, 0, 0)
		Return This
	End Function
#endif
	Function Append(x As Long) As StringBuilder
		ActiveBasic.Strings.Detail.FormatIntegerD(This, x, DWORD_MAX, 0, 0)
		Return This
	End Function

	Function Append(x As DWord) As StringBuilder
		ActiveBasic.Strings.Detail.FormatIntegerU(This, x, DWORD_MAX, 0, 0)
		Return This
	End Function

	Function Append(x As Int64) As StringBuilder
		ActiveBasic.Strings.Detail.FormatIntegerLD(This, x, DWORD_MAX, 0, 0)
		Return This
	End Function

	Function Append(x As QWord) As StringBuilder
		ActiveBasic.Strings.Detail.FormatIntegerLU(This, x, DWORD_MAX, 0, 0)
		Return This
	End Function

	Function Append(x As Single) As StringBuilder
		ActiveBasic.Strings.Detail.FormatFloatG(This, x, DWORD_MAX, 0, 0)
		Return This
	End Function

	Function Append(x As Double) As StringBuilder
		ActiveBasic.Strings.Detail.FormatFloatG(This, x, DWORD_MAX, 0, 0)
		Return This
	End Function

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

	Function Append(c As Char, 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
		Return Append(StrPtr(s), startIndex, count)
	End Function

	Function Append(s As *Char, startIndex As Long, count As Long) As StringBuilder
		If s = 0 Then
			If startIndex = 0 And count = 0 Then
				Return This
			Else
				Throw New ArgumentNullException("s")
			End If
		ElseIf startIndex < 0 Or count < 0 Then
			Throw New ArgumentOutOfRangeException("startIndex or count or both")
		End If
		appendCore(s, startIndex, count)
		Return This
	End Function
Private
	Sub appendCore(s As *Char, 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 Char, destIndex As Long, count As Long)
		If dest = 0 Then
			Throw New ArgumentNullException("sourceIndex")
		ElseIf size < sourceIndex + count Or sourceIndex < 0 Or destIndex < 0 Or count < 0 Then
			Throw New ArgumentOutOfRangeException("startIndex or count or both")
		End If

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

	Function EnsureCapacity(c As Long) As Long
		If c < 0 Or c > MaxCapacity Then
			Throw New ArgumentOutOfRangeException("c")
		ElseIf c > Capacity Then
			Dim p = GC_malloc_atomic((c + 1) * SizeOf (Char)) As *Char
			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 UNICODE
		Dim n = size
#else
		Dim n = (size + 1) >> 1
#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 Char) As StringBuilder
		Insert(i, VarPtr(x), 0, 1)
		Return This
	End Function
#ifdef UNICODE
	Function Insert(i As Long, x As SByte) As StringBuilder
		rangeCheck(i)
		insertCore(i, Str$(x As Long))
		Return This
	End Function
#else
	Function Insert(i As Long, x As Word) As StringBuilder
		rangeCheck(i)
		insertCore(i, Str$(x As DWord))
		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("StringBuilder.Insert: An argument is out of range value.", "n")
		End If
		Dim len = x.Length
		Dim lenTotal = len * n
		Dim newSize = size + lenTotal
		EnsureCapacity(newSize)
		separateBuffer()

		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 *Char, index As Long, count As Long) As StringBuilder
		rangeCheck(i)
		If x = 0 Then
			Throw New ArgumentNullException("StringBuilder.Insert: An argument is null", "x")
		ElseIf index < 0 Or count < 0 Then
			Throw New ArgumentOutOfRangeException("StringBuilder.Append: One or more arguments are out of range value.", "index or count or both")
		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 Char, newChar As Char) 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 Char, newChar As Char, 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 Char, newChar As Char, start As Long, count As Long)
		separateBuffer()
		Dim i As Long
		Dim last = start + count - 1'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 ActiveBasic.IsNothing(oldStr) Then
			Throw New ArgumentNullException("StringBuilder.Replace: An argument is null", "oldStr")
		ElseIf oldStr.Length = 0 Then
			Throw New ArgumentException("StringBuilder.Replace: The argument 'oldStr' is empty string. ", "oldStr")
		End If

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

Public
	Override Function ToString() As String
		separateBuffer()
		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 Char
		Return Chars[i]
	End Function

	Sub Operator []=(i As Long, c As Char)
		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 New ArgumentOutOfRangeException("StringBuilder.Capacity: An argument is out of range value.", "c")
		End If
		EnsureCapacity(c)
	End Sub

	Const Function Chars(i As Long) As Char
		If i >= Length Or i < 0 Then
			Throw New IndexOutOfRangeException("StringBuilder.Chars: The index argument 'i' is out of range value.")
		End If
		Return chars[i]
	End Function

	Sub Chars(i As Long, c As Char)
		If i >= Length Or i < 0 Then
			Throw New ArgumentOutOfRangeException("StringBuilder.Chars: An argument is out of range value.", "i")
		End If
		separateBuffer()
		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 Char)
		End If
		size = i
	End Sub

	Const Function MaxCapacity() As Long
		Return maxCapacity
	End Function

	/*!
	@brief 内部用
	代わりにStrPtr使ってください。
	*/
	Function __Chars() As *Char
		Return chars
	End Function

	/*!
	@brief 内部用
	使わないでください。
	*/
	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 New ArgumentOutOfRangeException("StringBuilder constructor", "capacity or maxCapacity or both")
		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 (Char))
	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 = length
		End If
	End Sub

	Sub rangeCheck(index As Long)
		If index < 0 Or index > size Then
			Throw New ArgumentOutOfRangeException("StringBuilder")
		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 New ArgumentOutOfRangeException("StringBuilder", "startIndex or count or both")
		End If
	End Sub

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

	chars As *Char
	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 *Char
	Return sb.__Chars
End Function
