/*!
@file Include/Classes/ActiveBasic/Strings/SPrintF.ab
@brief SPrintfとその補助ルーチンが含まれるファイル。

SPrintfで数値変換が行われるとき、呼出の階層は、
SPrintf→FormatInteger, FormatFloat→FormatIntegerEx, FormatFloatExとなる。

また、次のような変換ルーチンが存在する。
@li FormatFloatE
@li FormatFloatF
@li FormatFloatG
@li FormatFloatA
@li FormatIntegerD
@li FormatIntegerU
@li FormatIntegerO
@li FormatIntegerX
@li FormatCharacter
@li FormatString
*/

Namespace ActiveBasic
Namespace Strings

Namespace Detail

/*!
@brief	浮動小数点数を文字列化する低水準な関数。符号、指数、仮数に分けて出力。
@author	Egtra
@date	2007/09/18
@param[in]	x	文字列化する浮動小数点数
@param[out]	e	指数
@param[out]	sign	符号
@return 仮数
仮数は1の位から下へ17桁で、小数点を含まない。そのため、誤差を無視すればVal(仮数) * 10 ^ (e - 17) = Abs(x)が成り立つ。

xに無限大、非数を渡した場合の動作は未定義。
*/
Function FloatToChars(x As Double, ByRef e As Long, ByRef sign As Boolean) As String
	Imports System

	'0を弾く
	If x = 0 Then
		If GetQWord(VarPtr(x) As *QWord) And &h8000000000000000 Then
			sign = True
		Else
			sign = False
		End If

		e = 0
		FloatToChars = "00000000000000000"
		Exit Function
	End If

	'符号の判断（同時に符号を取り除く）
	If x < 0 Then
		sign = True
		x = -x
	Else
		sign = False
	End If

	'1e16 <= x < 1e17へ正規化
	'(元のx) = (正規化後のx) ^ (d - 17)である。
	Dim d = Math.Floor(Math.Log10(x)) As Long
	If d < 16 Then
		x *= ipow(10, +17 - d)
	ElseIf d > 16 Then
		x /= ipow(10, -17 + d)
	End If

	'補正
	While x < 1e16
		x *= 10
		d--
	Wend
	While x >= 1e17
		x /= 10
		d++
	Wend

	d--
	e = d

	FloatToChars = FormatIntegerLU((x As Int64) As QWord, 17, 0, None)
End Function

/*!
@brief	書式化関数群で使用するフラグ。
@author	Egtra
@date	2007/09/18
*/
Const Enum FormatFlags
	'! 何も指定がない。
	None = &h0
	/*!
	符号、+。符号付変換[diAaEeFfGg]のとき、正の値でも符号を付ける。
	AdjustFieldWidthの仕様から、Format関数郡内からAdjustFieldWidthにかけて、
	単に数値が符号付である（負の値である）ことを示す意味でも用いられる。
	*/
	Sign = &h1
	/*! 空白、空白文字。
	符号付変換[diAaEeFfGg]のとき、正の値ならば符号分の空白を開ける。Signが立っているときには無視される。
	*/
	Blank = &h2
	/*! ゼロ、0。
	[diouXxAaEeFfGg]で、フィールドの空きを0で埋める。leftが立っているときには無視される。
	*/
	Zero = &h4
	'! 左揃え、-。フィールド内で左揃えにする。
	LeftSide = &h8
	/*! 代替表記、#。
	@li [OoXx]では、値が0でない場合、先頭に0、0xを付ける。</ul>
	@li [AaEeFfGg]では、精度0でも小数点を付ける。</ul>
	@li [Gg]では、それに加え、小数部末尾の0の省略を行わないようにする。</ul>
	*/
	Alt = &h10
	'! 大文字。使用するアルファベットを大文字にする。[aefgx]を[AEFGX]化する。
	Cap = &h20

	'!BASIC接頭辞。&h, &oなど。
	BPrefix = &h40

	/*!
	内部処理用に予約。
	@note	Minusとして使用されている。
	*/
	Reserved = &h80000000
End Enum

/*!
@brief	浮動小数点数をprintfの%e, %E（指数形式、十進法）相当の変換で文字列化する関数。
@author	Egtra
@date	2007/09/18
@param[in] x	文字列化する浮動小数点数値。
@param[in] precision	精度。小数点以下の桁数。DWORD_MAXのとき、指定なしとして既定値6となる。
@param[in] field	フィールド幅。
@param[in] flags	書式フラグ。
@return	xの文字列表現
*/
Function FormatFloatE(x As Double, precision As DWord, field As DWord, flags As FormatFlags) As String
	FormatFloatE = FormatFloatEx(AddressOf(FormatFloatE_Convert), x, precision, field, flags)
End Function

/*
@brief	浮動小数点数をprintfの%e, %E（指数形式、十進法）相当の変換で文字列化する関数。
@author	Egtra
@date	2008/03/08
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in] x	文字列化する浮動小数点数値。
@param[in] precision	精度。小数点以下の桁数。DWORD_MAXのとき、指定なしとして既定値6となる。
@param[in] field	フィールド幅。
@param[in] flags	書式フラグ。
*/
Sub FormatFloatE(sb As System.Text.StringBuilder, x As Double, precision As DWord, field As DWord, ByRef flags As FormatFlags)
	FormatFloatEx(sb, AddressOf(FormatFloatE_Convert), x, precision, field, flags)
End Sub

/*!
@brief	浮動小数点数をprintfの%e, %E（指数形式、十進法）相当の変換で文字列化する関数。
@author	Egtra
@date	2008/03/07
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in] x	文字列化する浮動小数点数値。
@param[in] precision	精度。小数点以下の桁数。DWORD_MAXのとき、指定なしとして15となる。
@param[in] field	フィールド幅。
@param[in,out] flags	書式フラグ。
@todo	他の実装での末尾桁の扱いを調べる（このコードでは何もしていないので切捨となっている）。
*/
Sub FormatFloatE_Convert(sb As System.Text.StringBuilder, x As Double, precision As DWord, field As DWord, ByRef flags As FormatFlags)
	If precision = DWORD_MAX Then
		precision = 15
	End If
	Dim e As Long, negative As Boolean
	Dim s = FloatToChars(x, e, negative)
	FormatFloatE_Base(sb, s, negative, precision, flags)
	FormatFloatE_Exponent(sb, e, flags)
End Sub

/**
@brief	FormatFloatEの符号・基数部の出力用。
@author	Egtra
@date	2007/10/27
*/
Sub FormatFloatE_Base(sb As System.Text.StringBuilder, s As String, negative As Boolean, precision As DWord, ByRef flags As FormatFlags)
	With sb
		AppendSign(sb, negative, flags)
		.Append(s[0])
		If (flags And Alt) Or precision > 0 Then
			.Append(".")
			Dim outputLen = s.Length - 1
			If outputLen >= precision Then
				.Append(s, 1, precision)
			Else 'sで用意された桁が指定された精度より少ないとき
				.Append(s, 1, outputLen)
				.Append(&h30 As Char, precision - outputLen) '足りない桁は0埋め
			End If
		End If
	End With
End Sub

/**
@brief	FormatFloatEの指数部の出力用。
@author	Egtra
@date	2007/10/27
*/
Sub FormatFloatE_Exponent(sb As System.Text.StringBuilder, e As Long, flags As FormatFlags)
	With sb
		If flags And Cap Then
			.Append("E")
		Else
			.Append("e")
		End If
		FormatIntegerD(sb, e, 2, 0, Sign Or Zero)
	End With
End Sub

/*!
@brief	浮動小数点数をprintfの%f（小数形式、十進法）相当の変換で文字列化する関数。
@author	Egtra
@date	2007/10/23
@param[in]	x	文字列化する浮動小数点数値。
@param[in]	precision	精度。小数点以下の桁数。DWORD_MAXのとき、指定なしとして既定値15となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
@return xの文字列表現
*/
Function FormatFloatF(x As Double, precision As DWord, field As DWord, flags As FormatFlags) As String
	FormatFloatF = FormatFloatEx(AddressOf(FormatFloatF_Convert), x, precision, field, flags)
End Function

/*
@brief	浮動小数点数をprintfの%f（小数形式、十進法）相当の変換で文字列化する関数。
@author	Egtra
@date	2008/03/08
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in] x	文字列化する浮動小数点数値。
@param[in] precision	精度。小数点以下の桁数。DWORD_MAXのとき、指定なしとして既定値6となる。
@param[in] field	フィールド幅。
@param[in] flags	書式フラグ。
*/
Sub FormatFloatF(sb As System.Text.StringBuilder, x As Double, precision As DWord, field As DWord, ByRef flags As FormatFlags)
	FormatFloatEx(sb, AddressOf(FormatFloatF_Convert), x, precision, field, flags)
End Sub

/*!
@brief	浮動小数点数をprintfの%f（小数形式、十進法）相当の変換で文字列化する関数。
@author	Egtra
@date	2008/03/07
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in]	x	文字列化する浮動小数点数値。
@param[in]	precision	精度。小数点以下の桁数。DWORD_MAXのとき、指定なしとして既定値15となる。
@param[in]	field	フィールド幅。
@param[in,out]	flags	書式フラグ。
*/
Sub FormatFloatF_Convert(sb As System.Text.StringBuilder, x As Double, precision As DWord, field As DWord, ByRef flags As FormatFlags)
	If precision = DWORD_MAX Then
		precision = 15
	End If
	Dim e As Long, negative As Boolean
	Dim s = FloatToChars(x, e, negative)
	FormatFloatF_Core(sb, s, e, negative, precision, flags)
End Sub

/**
@author	Egtra
@date	2007/10/27
*/
Sub FormatFloatF_Core(sb As System.Text.StringBuilder, s As String, e As Long, negative As Boolean, precision As DWord, ByRef flags As FormatFlags)
	With sb
		AppendSign(sb, negative, flags)

		Dim intPartLen = e + 1
		Dim outputDigit = 0 As DWord
		If intPartLen >= 17 Then
			'有効桁が全て整数部に収まる場合
			.Append(s)
			.Append(&h30 As Char, intPartLen - 17)
			outputDigit = 17
		ElseIf intPartLen > 0 Then
			'有効桁の一部が整数部にかかる場合
			.Append(s, 0, intPartLen)
			outputDigit = intPartLen
		Else
			'有効桁が全く整数部にかからない場合
			.Append(&h30 As Char)
		End If

		If precision > 0 Or (flags And Alt) Then
			.Append(".")

			Dim lastDigit = s.Length - outputDigit
			If lastDigit >= precision Then '変換して得られた文字列の桁数が精度以上ある場合
				Dim zeroDigit = 0
				If intPartLen < 0 Then
					'1.23e-4 = 0.000123のように指数が負のため小数点以下に0が続く場合
					zeroDigit = System.Math.Min(-intPartLen As DWord, precision)
					.Append(&h30 As Char, zeroDigit As Long)
				End If
				.Append(s, outputDigit, (precision - zeroDigit) As Long)
			Else
				.Append(s, outputDigit, lastDigit)
				.Append(&h30 As Char, (precision - lastDigit) As Long) '残りの桁は0埋め
			End If
		End If
	End With
End Sub

/*!
@brief	浮動小数点数をprintfの%g, %G（小数・指数、十進法）相当の変換で文字列化する関数。
@author	Egtra
@date	2007/10/23
@param[in]	x	文字列化する浮動小数点数値。
@param[in]	precision	精度。小数点以下の桁数。DWORD_MAXまたは0のとき、指定なしとして既定値15となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
@return xの文字列表現
@todo	下位桁の扱いの調査。
*/
Function FormatFloatG(x As Double, precision As DWord, field As DWord, flags As FormatFlags) As String
	FormatFloatG = FormatFloatEx(AddressOf(FormatFloatG_Convert), x, precision, field, flags)
End Function

/*
@brief	浮動小数点数をprintfの%g, %G（小数・指数、十進法）相当の変換で文字列化する関数。
@author	Egtra
@date	2008/03/08
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in] x	文字列化する浮動小数点数値。
@param[in] precision	精度。小数点以下の桁数。DWORD_MAXのとき、指定なしとして既定値6となる。
@param[in] field	フィールド幅。
@param[in] flags	書式フラグ。
*/
Sub FormatFloatG(sb As System.Text.StringBuilder, x As Double, precision As DWord, field As DWord, ByRef flags As FormatFlags)
	FormatFloatEx(sb, AddressOf(FormatFloatG_Convert), x, precision, field, flags)
End Sub

/*!
@brief	浮動小数点数をprintfの%g, %G（小数・指数、十進法）相当の変換で文字列化する関数。
@author	Egtra
@date	2007/10/23
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in]	x	文字列化する浮動小数点数値。
@param[in]	precision	精度。小数点以下の桁数。DWORD_MAXまたは0のとき、指定なしとして既定値15となる。
@param[in]	field	フィールド幅。
@param[in,out]	flags	書式フラグ。
@todo	下位桁の扱いの調査。
*/
Sub FormatFloatG_Convert(sb As System.Text.StringBuilder, x As Double, precision As DWord, field As DWord, ByRef flags As FormatFlags)
	'GではE/Fと違い整数部も有効桁数に数えるのでその分を引いておく。
	If precision = DWORD_MAX Or precision = 0 Then
		precision = 14
	Else
		precision--
	End If
	Dim lastLength = sb.Length
	Dim e As Long, negative As Boolean
	Dim s = FloatToChars(x, e, negative)
	If -5 < e And e < precision Then
		FormatFloatF_Core(sb, s, e, negative, -e + precision, flags)
		FormatFloatG_RemoveLowDigit(sb, lastLength, flags)
	Else
		FormatFloatE_Base(sb, s, negative, precision, flags)
		FormatFloatG_RemoveLowDigit(sb, lastLength, flags)
		FormatFloatE_Exponent(sb, e, flags)
	End If
End Sub

/*!
@brief	FormatFloatG/A用の小数点以下末尾の0を削除するルーチン
@author	Egtra
@date	2007/10/27
@param[in, out]	sb 文字列バッファ
@param[in]	flags フラグ
flagsでAltが立っているとき、この関数は何もしない。
*/
Sub FormatFloatG_RemoveLowDigit(sb As System.Text.StringBuilder, start As Long, flags As FormatFlags)
	Imports ActiveBasic.Strings
	
	Dim count = sb.Length
	If (flags And Alt) = 0 Then
		Dim p = StrPtr(sb)
		Dim point = ChrFind(VarPtr(p[start]), (count - start) As SIZE_T, Asc("."))
		If point = -1 Then
			Debug
		End If

		Dim i As Long
		For i = count - 1 To point + 1 Step -1
			If sb[i] <> &h30 Then
				Exit For
			End If
		Next
		If i <> point Then
			i++
		End If
		sb.Length = i
	End If
End Sub

/*!
@brief	浮動小数点数をprintfの%a, %A（指数形式、十六進法）相当の変換で文字列化する関数。
@author	Egtra
@date	2007/09/18
@param[in] x	文字列化する浮動小数点数値。
@param[in] precision	精度。小数点以下の桁数。DWORD_MAXのとき、指定なしとして既定値13となる。
@param[in] field	フィールド幅。
@param[in] flags	書式フラグ。
@return	xの文字列表現

C99では、末尾の0を取り除いても良いとあるので、
このルーチンでは取り除くことにしている。
*/
Function FormatFloatA(x As Double, precision As DWord, field As DWord, flags As FormatFlags) As String
	FormatFloatA = FormatFloatEx(AddressOf(FormatFloatA_Convert), x, precision, field, flags)
End Function

/*
@brief	浮動小数点数をprintfの%a, %A（指数形式、十六進法）相当の変換で文字列化する関数。
@author	Egtra
@date	2008/03/08
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in] x	文字列化する浮動小数点数値。
@param[in] precision	精度。小数点以下の桁数。DWORD_MAXのとき、指定なしとして既定値6となる。
@param[in] field	フィールド幅。
@param[in] flags	書式フラグ。
*/
Sub FormatFloatA(sb As System.Text.StringBuilder, x As Double, precision As DWord, field As DWord, ByRef flags As FormatFlags)
	FormatFloatEx(sb, AddressOf(FormatFloatA_Convert), x, precision, field, flags)
End Sub

/*!
@brief	浮動小数点数をprintfの%a, %A（指数形式、十六進法）相当の変換で文字列化する関数。
@author	Egtra
@date	2008/03/07
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in] x	文字列化する浮動小数点数値。
@param[in] precision	精度。小数点以下の桁数。DWORD_MAXのとき、指定なしとして既定値13となる。
@param[in] field	フィールド幅。
@param[in,out] flags	書式フラグ。

C99では、末尾の0を取り除いても良いとあるので、
このルーチンでは取り除くことにしている。
*/
Sub FormatFloatA_Convert(sb As System.Text.StringBuilder, x As Double, precision As DWord, field As DWord, ByRef flags As FormatFlags)
	If precision = DWORD_MAX Then
		precision = 13
	End If
	Dim lastLength = sb.Length
	Dim pqw = VarPtr(x) As *QWord

	With sb
		Dim sign = (GetQWord(pqw) And &H8000000000000000) As Boolean
		pqw[0] And= &h7fffffffffffffff

		AppendSign(sb, sign, flags)

		If flags And BPrefix Then
			.Append("&H")
		Else
			.Append("0X")
		End If

		Dim biasedExp = (GetQWord(pqw) >> 52) As DWord And &h7FF
		Dim exp As Long
		If biasedExp = 0 Then
			If GetQWord(pqw) <> 0 Then
				exp = -1022 '非正規化数への対応
			Else
				exp = 0
			End If
			.Append("0")
		Else
			exp = biasedExp - 1023
			.Append("1")
		End If

		If precision > 0 Or (flags And Alt) Then
			.Append(".")
			Dim fraction = GetQWord(pqw) And &h000fffffffffffff
			If precision < 13 Then
				Dim dropped = 13 - precision
				fraction >>= dropped * 4
				FormatIntegerLX(sb, fraction, precision, 0, flags And Cap)
			Else
				FormatIntegerLX(sb, fraction, 13, 0, flags And Cap)
				.Append(&h30, precision - 13)
			End If
		End If
		FormatFloatG_RemoveLowDigit(sb, lastLength, flags)
		.Append("P")
		FormatIntegerD(sb, exp, 1, 0, Sign)
		AdjustAlphabet(sb, flags, lastLength)
	End With
End Sub

/*!
@brief 先頭に符号もしくはその分の空白を出力する。FormatFloat用。
@author	Egtra
@date 2007/10/23
@param[in, out] sb	出力先
@param[in] negative	符号
@param[in, out] flags	フラグ。negative = Trueなら、Signを立てて返す。
*/
Sub AppendSign(sb As System.Text.StringBuilder, negative As Boolean, ByRef flags As FormatFlags)
	With sb
		If negative Then
			.Append("-")
			flags Or= Sign
		Else
			If flags And Sign Then
				.Append("+")
			ElseIf flags And Blank Then
				.Append(" ")
			End If
		End If
	End With
End Sub

/*!
@brief	符号無し整数をprintfの%u（十進法表現）相当の変換で文字列化する関数。
@author	Egtra
@date	2007/09/18
@param[in]	x	文字列化する整数値。
@param[in]	d	精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
@return xの文字列表現
*/
Function FormatIntegerU(x As DWord, d As DWord, field As DWord, flags As FormatFlags) As String
	Return FormatIntegerEx(TraitsIntegerU[0], x, d, field, flags And (Not (Sign Or Blank)))
End Function

/*!
@brief	符号無し整数をprintfの%u（十進法表現）相当の変換で文字列化する関数。
@author	Egtra
@date	2008/03/07
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in]	x	文字列化する整数値。
@param[in]	d	精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
*/
Sub FormatIntegerU(sb As System.Text.StringBuilder, x As DWord, d As DWord, field As DWord, flags As FormatFlags)
	FormatIntegerEx(sb, TraitsIntegerU[0], x As QWord, d, field, flags And (Not (Sign Or Blank)))
End Sub

/*!
@brief	FormatIntegerUのQWord版
@author	Egtra
@date	2007/10/26
*/
Function FormatIntegerLU(x As QWord, d As DWord, field As DWord, flags As FormatFlags) As String
	Return FormatIntegerEx(TraitsIntegerU[1], x, d, field, flags And (Not (Sign Or Blank)))
End Function

/*!
@brief	FormatIntegerUのQWord版
@author	Egtra
@date	2008/03/07
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in]	x	文字列化する整数値。
@param[in]	d	精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
*/
Sub FormatIntegerLU(sb As System.Text.StringBuilder, x As QWord, d As DWord, field As DWord, flags As FormatFlags)
	FormatIntegerEx(sb, TraitsIntegerU[0], x, d, field, flags And (Not (Sign Or Blank)))
End Sub

/*!
@brief	符号有り整数をprintfの%d（十進法表現）相当の変換で文字列化する関数。
@author	Egtra
@date	2007/10/13
@param[in]	x	文字列化する整数値。
@param[in]	d	精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
@return xの文字列表現
*/
Function FormatIntegerD(x As Long, d As DWord, field As DWord, flags As FormatFlags) As String
	Return FormatIntegerEx(TraitsIntegerD[0], (x As Int64) As QWord, d, field, flags)
End Function

/*!
@brief	符号有り整数をprintfの%d（十進法表現）相当の変換で文字列化する関数。
@author	Egtra
@date	2008/03/07
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in]	x	文字列化する整数値。
@param[in]	d	精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
*/
Sub FormatIntegerD(sb As System.Text.StringBuilder, x As Long, d As DWord, field As DWord, flags As FormatFlags)
	FormatIntegerEx(sb, TraitsIntegerD[0], (x As Int64) As QWord, d, field, flags)
End Sub

/*!
@brief	FormatIntegerDのInt64版
@author	Egtra
@date	2007/10/26
*/
Function FormatIntegerLD(x As Int64, d As DWord, field As DWord, flags As FormatFlags) As String
	Return FormatIntegerEx(TraitsIntegerD[1], x As QWord, d, field, flags)
End Function

/*!
@brief	FormatIntegerDのInt64版
@author	Egtra
@date	2007/10/26
*/
Sub FormatIntegerLD(sb As System.Text.StringBuilder, x As Int64, d As DWord, field As DWord, flags As FormatFlags)
	FormatIntegerEx(sb, TraitsIntegerD[1], x As QWord, d, field, flags)
End Sub

/*!
@author	Egtra
@date	2007/10/26
*/
Dim TraitsIntegerU[1] As IntegerConvertTraits
With TraitsIntegerU[0]
	.Convert = AddressOf(IntegerU_Convert)
	.Prefix = AddressOf(IntegerU_Prefix)
End With

With TraitsIntegerU[1]
	.Convert = AddressOf(IntegerLU_Convert)
	.Prefix = AddressOf(IntegerU_Prefix)
End With

/*!
@author	Egtra
@date	2007/10/28
*/
Dim TraitsIntegerD[1] As IntegerConvertTraits
With TraitsIntegerD[0]
	.Convert = AddressOf(IntegerD_Convert)
	.Prefix = AddressOf(IntegerD_Prefix)
End With

With TraitsIntegerD[1]
	.Convert = AddressOf(IntegerLD_Convert)
	.Prefix = AddressOf(IntegerD_Prefix)
End With

/*!
@brief	負数を表すフラグ。FormatIntegerD, LDからIntegerDU_Prefixまでの内部処理用。
@author	Egtra
@date	2007/10/26
*/
Const Minus = Reserved

/*!
@author	Egtra
@date	2007/10/26
*/
Function IntegerU_Convert(buf As *Char, xq As QWord, flags As FormatFlags) As DWord
	Dim x = xq As DWord
	Dim i = MaxSizeLO
	While x <> 0
		buf[i] = (x As Int64 Mod 10 + &h30) As Char 'Int64への型変換は#117対策
		x \= 10
		i--
	Wend
	Return i
End Function

/*!
@brief	IntegerU_ConvertのQWord版
@author	Egtra
@date	2007/10/26
@bug	#117のため、現在Int64の最大値を超える値を正しく処理できない。
*/
Function IntegerLU_Convert(buf As *Char, x As QWord, flags As FormatFlags) As DWord
	Dim i = MaxSizeLO
	While x <> 0
		buf[i] = (x As Int64 Mod 10 + &h30) As Char 'Int64への型変換は#117対策
		x \= 10
		i--
	Wend
	Return i
End Function

/*!
@author	Egtra
@date	2007/10/26
*/
Function IntegerU_Prefix(x As QWord, flags As FormatFlags) As String
End Function

/*!
@author	Egtra
@date	2007/10/28
*/
Function IntegerD_Convert(buf As *Char, xq As QWord, flags As FormatFlags) As DWord
	Return IntegerU_Convert(buf, Abs((xq As DWord) As Long) As DWord, flags)
End Function

/*!
@brief	IntegerD_ConvertのInt64版
@author	Egtra
@date	2007/10/28
*/
Function IntegerLD_Convert(buf As *Char, x As QWord, flags As FormatFlags) As DWord
	Return IntegerLU_Convert(buf, Abs(x As Int64) As QWord, flags)
End Function

/*!
@author	Egtra
@date	2007/10/28
*/
Function IntegerD_Prefix(x As QWord, flags As FormatFlags) As String
	If (x As Int64) < 0 Then
		IntegerD_Prefix = "-"
	ElseIf flags And Sign Then
		IntegerD_Prefix = "+"
	ElseIf flags And Blank Then
		IntegerD_Prefix = " "
	End If
End Function



/*!
@brief	QWordの最大値の八進法表現1777777777777777777777の文字数 - 1 + 1。IntegerO_Convert内で使用。
@author	Egtra
@date	2007/10/26
上の式で1を加えているのは、八進接頭辞の分。
*/
Const MaxSizeLO = 22

/*!
@author	Egtra
@date	2007/10/22
*/
Dim TraitsIntegerO[1] As IntegerConvertTraits
With TraitsIntegerO[0]
	.Convert = AddressOf(IntegerO_Convert)
	.Prefix = AddressOf(IntegerO_Prefix)
End With

With TraitsIntegerO[1]
	.Convert = AddressOf(IntegerLO_Convert)
	.Prefix = AddressOf(IntegerO_Prefix)
End With

/*!
@brief	符号無し整数をprintfの%o（八進法表現）相当の変換で文字列化する関数。
@author	Egtra
@date	2007/10/19
@param[in]	x	文字列化する整数値。
@param[in]	d	精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
@return xの文字列表現
*/
Function FormatIntegerO(x As DWord, d As DWord, field As DWord, flags As FormatFlags) As String
	Return FormatIntegerEx(TraitsIntegerO[0], x, d, field, flags)
End Function

/*!
@brief	FormatIntegerOのQWord版。
@author	Egtra
@date	2007/10/26
*/
Function FormatIntegerLO(x As QWord, d As DWord, field As DWord, flags As FormatFlags) As String
	Return FormatIntegerEx(TraitsIntegerO[1], x, d, field, flags)
End Function

/*!
@author	Egtra
@date	2007/10/22
*/
Function IntegerO_Convert(buf As *Char, xq As QWord, flags As FormatFlags) As DWord
	Dim x = xq As DWord
	Dim i = MaxSizeLO
	While x <> 0
		buf[i] = ((x And &o7) + &h30) As Char
		x >>= 3
		i--
	Wend
	If flags And Alt Then
		buf[i] = &h30
		i--
	End If
	Return i
End Function

/*!
@brief	IntegerO_ConvertのQWord版。
@author	Egtra
@date	2007/10/26
*/
Function IntegerLO_Convert(buf As *Char, x As QWord, flags As FormatFlags) As DWord
	Dim i = MaxSizeLO
	While x <> 0
		buf[i] = ((x And &o7) + &h30) As Char
		x >>= 3
		i--
	Wend
	If flags And Alt Then
		buf[i] = &h30
		i--
	End If
	Return i
End Function

/*!
@author	Egtra
@date	2007/10/22
@note	#フラグ (Alt)の処理は、IntegerO/LO_Convert内で行うので、ここで処理することはない。
*/
Function IntegerO_Prefix(x As QWord, flags As FormatFlags) As String
	If flags And BPrefix Then
		If x <> 0 Then
			IntegerO_Prefix = "&O"
		End If
	End If
End Function

/*!
@author	Egtra
@date	2007/10/24
*/
Dim TraitsIntegerX[1] As IntegerConvertTraits
With TraitsIntegerX[0]
	.Convert = AddressOf(IntegerX_Convert)
	.Prefix = AddressOf(IntegerX_Prefix)
End With

With TraitsIntegerX[1]
	.Convert = AddressOf(IntegerLX_Convert)
	.Prefix = AddressOf(IntegerX_Prefix)
End With

/*!
@brief	整数をprintfの%x, %X（十六進法）相当の変換で文字列化する関数。
@author	Egtra
@date	2007/10/19
@param[in]	x	文字列化する整数値。
@param[in]	d	精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
@return xの文字列表現
*/
Function FormatIntegerX(x As DWord, d As DWord, field As DWord, flags As FormatFlags) As String
	Return FormatIntegerEx(TraitsIntegerX[0], x, d, field, flags)
End Function

/*!
@brief	FormatIntegerXのQWord版。
@author	Egtra
@date	2007/10/22
*/
Function FormatIntegerLX(x As QWord, d As DWord, field As DWord, flags As FormatFlags) As String
	Return FormatIntegerEx(TraitsIntegerX[1], x, d, field, flags)
End Function

/*!
@brief	FormatIntegerXのQWord, StringBuilder版。
@author	Egtra
@date	2008/03/07
*/
Sub FormatIntegerLX(sb As System.Text.StringBuilder, x As QWord, d As DWord, field As DWord, flags As FormatFlags)
	FormatIntegerEx(sb, TraitsIntegerX[1], x, d, field, flags)
End Sub

/*
@brief	0からFまでの文字を収めた表
@author	egtra
*/
Dim HexadecimalTable[&h10] = [&h30, &h31, &h32, &h33, &h34, &h35, &h36, &h37, &h38, &h39, &h41, &h42, &h43, &h44, &h45, &h46] As Byte

/*!
@author	Egtra
@date	2007/10/22
*/
Function IntegerX_Convert(buf As *Char, xq As QWord, flags As FormatFlags) As DWord
	Dim i = MaxSizeLO
	Dim x = xq As DWord
	While x <> 0
		buf[i] = HexadecimalTable[x And &h0f]
		x >>= 4
		i--
	Wend
	Return i
End Function

/*!
@brief	IntegerX_ConvertのQWord版。
@author	Egtra
@date	2007/10/22
*/
Function IntegerLX_Convert(buf As *Char, x As QWord, flags As FormatFlags) As DWord
	Dim i = MaxSizeLO
	While x <> 0
		buf[i] = HexadecimalTable[x And &h0f]
		x >>= 4
		i--
	Wend
	Return i
End Function

/*!
@author	Egtra
@date	2007/10/24
*/
Function IntegerX_Prefix(x As QWord, flags As FormatFlags) As String
	If x <> 0 Then
		If flags And Alt Then
			IntegerX_Prefix = "0X"
		ElseIf flags And BPrefix Then
			IntegerX_Prefix = "&H"
		End If
	End If
End Function

/*!
@brief	FormatIntegerExへ渡す変換特性を表す構造体型。
@author	Egtra
@date	2007/10/22

FormatIntegerの都合上、このファイル内で宣言しているIntegerConvertTraits型の
変数は全て配列となっている；[0]が32ビット変換、[1]が64ビット変換である。
*/
Type IntegerConvertTraits
	'!変換を行う関数へのポインタ。
	Convert As *Function(buf As *Char, x As QWord, flags As FormatFlags) As DWord
	'!接頭辞を取得する関数へのポインタ。
	Prefix As *Function(x As QWord, flags As FormatFlags) As String
End Type

/*!
@brief	整数変換全てを行う関数。
@author	Egtra
@date 2007/10/22
@param[in] tr	特性情報。
@param[in] x	変換元の数値。
@param[in] d	精度。ここでは最低限出力する桁数。
@param[in] field	フィールド幅。
@param[in] flags	フラグ。
@return 変換された文字列
*/
Function FormatIntegerEx(ByRef tr As IntegerConvertTraits, x As QWord, d As DWord, field As DWord, flags As FormatFlags) As String
	Dim sb = New System.Text.StringBuilder(32)
	FormatIntegerEx(sb, tr, x, d, field, flags)
	FormatIntegerEx = sb.ToString
End Function

/*!
@brief	整数変換全てを行う関数。これを雛形とし、形式毎の差異はIntegerConvertTraitsで表現する。
@author	Egtra
@date 2008/03/06
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in] tr	特性情報。
@param[in] x	変換元の数値。
@param[in] d	精度。ここでは最低限出力する桁数。
@param[in] field	フィールド幅。
@param[in] flags	フラグ。
*/
Sub FormatIntegerEx(sb As System.Text.StringBuilder, ByRef tr As IntegerConvertTraits, x As QWord, d As DWord, field As DWord, flags As FormatFlags)
	If d = DWORD_MAX Then
		d = 1
	Else
		'精度が指定されているとき、ゼロフラグは無視される。
		'仕様上、左揃えのときも無視されるが、それはAdjustFieldWidthが行ってくれる。
		flags And= Not Zero
	End If

	Dim lastLength = sb.Length

	With sb
		Dim prefixFunc = tr.Prefix
		Dim prefix = prefixFunc(x, flags)
		sb.Append(prefix)

		Dim prefixLen = 0 As DWord
		If String.IsNullOrEmpty(prefix) = False Then
			prefixLen = prefix.Length As DWord
		End If

		'バッファの量は最も必要文字数の多くなるUInt64の8進法変換にあわせている
		Dim buf[MaxSizeLO] As Char
		Dim convertFunc = tr.Convert
		Dim bufStartPos = convertFunc(buf, x, flags)

		Dim len = (MaxSizeLO - bufStartPos) As Long
		If len < 0 Then
			Debug
		End If
		If len < d Then
			.Append(&h30 As Char, d - len)
		End If
		.Append(buf, bufStartPos + 1, len)
		AdjustFieldWidth(sb, field, flags And (Not (Sign Or Blank)), prefixLen, lastLength)
		AdjustAlphabet(sb, flags, lastLength)
	End With
End Sub

/*!
@brief 浮動小数点数変換全てを行う関数。これを雛形とし、実際の変換関数は引数converterで与える。
@author Egtra
@date 2008/03/07
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in] converter 変換関数。
@param[in] x 変換元の数値。
@param[in] d 精度。ここでは最低限出力する桁数。
@param[in] field フィールド幅。
@param[in] flags フラグ。
*/
Sub FormatFloatEx(sb As System.Text.StringBuilder, converter As FormatFloatProc, x As Double, precision As DWord, field As DWord, flags As FormatFlags)
	Dim lastLength = sb.Length
	If Math.IsNaN(x) Then
		sb.Append("NAN")
		AdjustAlphabet(sb, flags, lastLength)
	ElseIf Math.IsInf(x) Then
		AppendSign(sb, (GetQWord(VarPtr(x)) >> 63) As Boolean, flags)
		sb.Append("INFINITY")
		AdjustAlphabet(sb, flags, lastLength)
	Else
		converter(sb, x, precision, field, flags)
	End If
	AdjustFieldWidth(sb, field, flags, 0, lastLength)
End Sub


/*!
@brief 浮動小数点数変換全てを行う関数。
@author Egtra
@date 2008/03/08
@param[in] converter 変換関数。
@param[in] x 変換元の数値。
@param[in] d 精度。ここでは最低限出力する桁数。
@param[in] field フィールド幅。
@param[in] flags フラグ。
@return 変換された文字列。
*/
Function FormatFloatEx(converter As FormatFloatProc, x As Double, precision As DWord, field As DWord, flags As FormatFlags) As String
	Dim sb = New System.Text.StringBuilder
	FormatFloatEx(sb, converter, x, precision, field, flags)
	FormatFloatEx = sb.ToString
End Function

/*!
@brief	書式化の仕上げとして、Capフラグが指定されていないときに小文字化する作業を行う。
@author	Egtra
@date 2008/03/06
@param[in,out] sb 書式化した文字列を格納しているバッファ。
@param[in] flags フラグ。
@param[in] offset 書式変換した部分の開始位置。
*/
Sub AdjustAlphabet(sb As System.Text.StringBuilder, flags As FormatFlags, offset As Long)
	If (flags And Cap) = 0 Then
		Dim len = sb.Length
		Dim i As Long
		For i = offset To ELM(len)
			sb[i] = CType.ToLower(sb[i])
		Next
	End If
End Sub

/*!
@brief	書式化の仕上げとして、変換部分がフィールド幅まで満たされるように空白などを挿入する。
@author	Egtra
@date	2007/10/13
@param[in,out] sb	対象文字列
@param[in] field	フィールド幅
@param[in] hasSign	符号を持っている（負の値か）か否か
@param[in] flags	フラグ
@param[in] prefixLen	（あれば）接頭辞の文字数。ゼロ埋めする際、この数だけ挿入位置を後ろにする。
@param[in] offset	変換した部分へのオフセット。AppendではなくInsertを行う際に用いられる。
sbが"-1"のように負符号を持っている場合は、呼出元でSignフラグ（またはBlank）を立てること。
*/
Sub AdjustFieldWidth(sb As System.Text.StringBuilder, field As DWord, flags As FormatFlags, prefixLen = 0 As DWord, offset = 0 As Long)
	With sb
		Dim len = .Length - offset
		If len < field Then
			Dim embeddedSize = field - len
			If flags And LeftSide Then
				.Append(&h20, embeddedSize)
			Else
				If (flags And Zero) <> 0 Then
					offset += prefixLen
					If (flags And Blank) Or (flags And Sign) Then
						offset++
					End If
					.Insert(offset, String$(embeddedSize, "0"))
				Else
					.Insert(offset, String$(embeddedSize, " "))
				End If
			End If
		End If
	End With
End Sub

/*!
@brief	文字列をprintfの%s相当の変換で書式化する関数。
@author	Egtra
@date	2007/10/27
@param[in]	x	文字列。
@param[in]	d	精度、最大の文字数。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
@return 書式化された文字列。
*/
Function FormatString(x As String, d As DWord, field As DWord, flags As FormatFlags) As String
	Imports System
	Dim sb = New System.Text.StringBuilder(
		Math.Max(Math.Min(x.Length As DWord, d), field) As Long + 1)
	FormatString(sb, x, d, field, flags)
	AdjustFieldWidth(sb, field, flags And LeftSide)
	FormatString = sb.ToString()
End Function

/*!
@brief	文字列をprintfの%s相当の変換で書式化する関数。
@author	Egtra
@date	2008/03/07
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in] x 文字列。
@param[in] d 精度、最大の文字数。
@param[in] field フィールド幅。
@param[in] flags 書式フラグ。
@return 書式化された文字列。
*/
Sub FormatString(sb As System.Text.StringBuilder, x As String, d As DWord, field As DWord, flags As FormatFlags)
	Dim len = sb.Length
	sb.Append(x, 0, System.Math.Min(x.Length As DWord, d) As Long)
	AdjustFieldWidth(sb, field, flags And LeftSide, 0, len)
End Sub

/*!
@brief	文字をprintfの%c相当の変換で書式化する関数。
@author	Egtra
@date	2007/10/27
@param[in]	x	文字。
@param[in]	d	精度、最大の文字数。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
@return 書式化された文字列。
*/
Function FormatCharacter(x As Char, d As DWord, field As DWord, flags As FormatFlags) As String
	Dim sb = New System.Text.StringBuilder(field + 2)
	FormatCharacter(sb, x, d, field, flags)
	FormatCharacter = sb.ToString()
End Function

/*!
@brief	文字列をprintfの%s相当の変換で書式化する関数。
@author	Egtra
@date	2008/03/07
@param[in,out] sb 書式化した文字列を追加するバッファ。
@param[in] x 文字。
@param[in] d 精度、最大の文字数。
@param[in] field フィールド幅。
@param[in] flags 書式フラグ。
@return 書式化された文字列。
*/
Sub FormatCharacter(sb As System.Text.StringBuilder, x As Char, d As DWord, field As DWord, flags As FormatFlags)
	Dim len = sb.Length
	sb.Append(x)
	AdjustFieldWidth(sb, field, flags And LeftSide, 0, len)
End Sub

/*!
@author	Egtra
@date	2007/10/28
*/
TypeDef FormatFloatProc = *Sub(sb As System.Text.StringBuilder, x As Double, precision As DWord, fieldWidth As DWord, ByRef flags As FormatFlags)

/*!
@brief	SPrintfから呼ばれる浮動小数点数用書式文字列化関数
@author	Egtra
@date	2007/10/28
*/
Sub FormatFloat(s As System.Text.StringBuilder, formatProc As FormatFloatProc,
	param As Object, precision As DWord, field As DWord, flags As FormatFlags)

	Dim x As Double
	Dim typeName = param.GetType().FullName
	If typeName = "System.Double" Then
		x = param As System.Double
	ElseIf typeName = "System.Single" Then
		x = param As System.Single
	End If
	FormatFloatEx(s, formatProc, x, precision, field, flags)
End Sub

/*!
@brief	SPrintfから呼ばれる整数用書式文字列化関数
@author	Egtra
@date	2007/10/28
*/
Sub FormatInteger(s As System.Text.StringBuilder, traits As *IntegerConvertTraits,
	param As Object, signed As Boolean, typeWidth As Long, precision As DWord, field As DWord, flags As FormatFlags)

	Dim x As QWord
	Dim typeName = param.GetType().FullName
	If typeName = "System.UInt64" Then
		x = param As System.UInt64
	ElseIf typeName = "System.Int64" Then
		x = (param As System.Int64) As QWord
	ElseIf typeName = "System.UInt32" Then
		x = param As System.UInt32
	ElseIf typeName = "System.Int32" Then
		x = (param As System.Int32) As QWord
	ElseIf typeName = "System.UInt16" Then
		x = param As System.UInt16
	ElseIf typeName = "System.Int16" Then
		x = (param As System.Int16) As QWord
	ElseIf typeName = "System.UInt8" Then
		x = param As System.Byte
	ElseIf typeName = "System.Int8" Then
		x = (param As System.SByte) As QWord
	End If
	'一旦縮めた後、符号・ゼロ拡張させる。
	'また、64ビット整数なら64ビット変換Traitsを選択する。
	If signed Then
		If typeWidth = 1 Then
			traits = VarPtr(traits[1])
		ElseIf typeWidth = 0 Then
			x = (((x As DWord) As Long) As Int64) As QWord
		ElseIf typeWidth = -1 Then
			x = (((x As Word) As Integer) As Int64) As QWord
		ElseIf typeWidth = -2 Then
			x = (((x As Byte) As SByte) As Int64) As QWord
		End If
	Else
		If typeWidth = 1 Then
			traits = VarPtr(traits[1])
		ElseIf typeWidth = 0 Then
			x = x As DWord
		ElseIf typeWidth = -1 Then
			x = x As Word
		ElseIf typeWidth = -2 Then
			x = x As Byte
		End If
	End If
	FormatIntegerEx(s, ByVal traits, x, precision, field, flags)
End Sub

'Format関数群ここまで
'----

/*!
@brief	文字列から数値への変換。さらに変換に使われなかった文字列の位置を返す。
@author	Egtra
@date	2007/11/11
@param[in] s	変換する文字
@param[out] p	変換に使われなかった部分の先頭を指すポインタ
@return	変換して得られた数値。変換できなければ0。
*/
Function StrToLong(s As *Char, ByRef p As *Char) As Long
	Dim negative As Boolean
	Dim i = 0 As Long
	If s[i] = &h2d Then 'Asc("-")
		i++
		negative = True
	End If
	Do
		Dim c = s[i]
		If Not CType.IsDigit(c) Then Exit Do
		StrToLong *= 10
		StrToLong += ((c As DWord) And &h0f) As Long
		i++
	Loop
	If negative Then
		StrToLong = -StrToLong
	End If
	p = VarPtr(s[i])
End Function

/*!
@brief	フィールド幅、精度用の数値読取
@author	Egtra
@date	2007/11/11
@param[in, out] fmt	読み途中の書式指定
@param[in] params
@param[in, out] paramsCount
@param[out]	ret	読み取った数値。読み取られなかったときの値は不定。
@retval True	読取を行った
@retval False	行わなかった
fmt[0]が*のときにはparamsから1つ読み取る。
そうでなく、fmtに数字（先頭に-符号があっても可）が並んでいれば、それを読み取る。
*/
Function ReadInt(ByRef fmt As *Char, params As *Object, ByRef paramsCount As SIZE_T, ByRef ret As Long) As Boolean
	If fmt[0] = &h2a Then '*
		fmt = VarPtr(fmt[1]) 'po
		ret = params[paramsCount] As System.Int32
		paramsCount++
		ReadInt = True
	Else
		Dim p As *Char
		ret = StrToLong(fmt, p)
		If fmt <> p Then
			fmt = p
			ReadInt = True
		Else
			ReadInt = False
		End If
	End If
End Function

/*!
@brief	フラグ指定の読み込み
@author	Egtra
@date	2007/10/28
@param[in, out] fmt
@param[out] flags
@retval True	読み込みが完了した。
@retval False	読み取り中に文字列が終了した（ヌル文字が現れた）。
*/
Function ReadFlags(ByRef fmt As *Char, ByRef flags As FormatFlags) As Boolean
	ReadFlags = False
	Do
		Select Case fmt[0]
			Case &h23 '#
				flags Or= Alt
			Case &h30 '0
				flags Or= Zero
			Case &h20 '空白
				flags Or= Blank
			Case &h2b '+
				flags Or= Sign
			Case &h2d '-
				flags Or = LeftSide
			Case &h26 '&
				flags Or= BPrefix
			Case 0
				Exit Function
			Case Else
				Exit Do
		End Select
		fmt = VarPtr(fmt[1]) 'po
	Loop
	ReadFlags = True
End Function

/*!
@brief	フィールド幅指定の読み込み
@author	Egtra
@date	2007/10/29
@param[in, out] fmt
@param[in] params
@param[in, out] paramsCount
@param[out] fieldWidth
@param[in, out] flags
*/
Sub ReadFieldWidth(ByRef fmt As *Char, params As *Object, ByRef paramsCount As SIZE_T,
	ByRef fieldWidth As DWord, ByRef flags As FormatFlags)
	Dim t As Long
	If ReadInt(fmt, params, paramsCount, t) Then
		If t < 0 Then
			flags Or= LeftSide
			fieldWidth = -t As DWord
		Else
			fieldWidth = t As DWord
		End If
	Else
		fieldWidth = 0
	End If
End Sub

/*!
@brief	精度の読み込み
@author	Egtra
@date	2007/10/29
@param[in, out] fmt
@param[in] params
@param[in, out] paramsCount
@return	読み取った精度。指定がなかったときには、DWORD_MAX。
*/
Function ReadPrecision(ByRef fmt As *Char,
	params As *Object, ByRef paramsCount As SIZE_T) As DWord

	If fmt[0] = &h2e Then '.
		fmt = VarPtr(fmt[1]) 'po
		Dim t As Long
		ReadPrecision = 0
		If ReadInt(fmt, params, paramsCount, t) Then
			If t > 0 Then
				ReadPrecision = t As DWord
			End If
		End If
	Else
		ReadPrecision = DWORD_MAX
	End If
End Function

#ifdef _WIN64
Const PtrLength = 1
#else
Const PtrLength = 0
#endif

/*!
@biref	長さ指定の読み込み
@author	Egtra
@date	2007/10/29
@param[in, out] fmt
@param[out] lengthSpec
*/
Sub ReadLength(ByRef fmt As *Char, ByRef lengthSpec As Long)
	Do
		Select Case fmt[0]
			Case &h6c 'l
				lengthSpec++
			Case &h68 'h
				lengthSpec--
			Case &h6a 'j (u)intmax_t = QWord, Int64
				lengthSpec = 1
			Case &h74 't ptrdiff_t
				lengthSpec = PtrLength
			Case &h7a 'z (s)size_t
				lengthSpec = PtrLength
			Case &h70 'p VoidPtr 本来は変換指定子だが、ここで先読み
				lengthSpec = PtrLength
				Exit Sub 'fmtを進められると困るので、ここで脱出
			Case Else
				Exit Sub
		End Select
		fmt = VarPtr(fmt[1]) 'po
	Loop
End Sub

/*!
@brief efg変換用に、精度が指定されていないときに既定の精度を設定する。
@auther Egtra
@date 2008/03/07
*/
Sub AdjustPrecision(ByRef precision As DWord)
	If precision = DWORD_MAX Then
		precision = 6
	End If
End Sub

/*!
@biref	Cのsprintfのような書式文字列出力関数
@author	Egtra
@date	2007/10/27
@param[in] format	書式文字列。詳細は開発Wiki参照。
@param[in, out] params	変換対象の配列。n = 0のときにはNULLも可。
@param[in] n	paramsの個数。
@return	書式化された文字列。
@todo	%nへの対応
*/
Function SPrintf(format As String, params As *Object, n As SIZE_T) As String
	Dim i = 0 As SIZE_T
	Dim paramsCount = 0 As SIZE_T
	Dim fmt = StrPtr(format)
	Dim s = New System.Text.StringBuilder
	Do
		Dim last = format.Length - (((fmt - StrPtr(format)) \ SizeOf (Char)) As LONG_PTR) As Long 'po
		Dim pos = ChrFind(fmt, last, &h25 As Char) As Long '&h25 = %
		If pos = -1 Then
			s.Append(fmt, 0, last)
			Exit Do
		End If
		'%以前の部分
		s.Append(fmt, 0, pos)
		fmt = VarPtr(fmt[pos + 1]) 'po
		'フラグの読取
		Dim flags = None As FormatFlags
		If ReadFlags(fmt, flags) = False Then
			Exit Do
		End If
		'フィールド幅
		Dim fieldWidth As DWord
		ReadFieldWidth(fmt, params, i, fieldWidth, flags)
		'精度
		Dim precision = ReadPrecision(fmt, params, i)
		'幅指定の読取
		Dim typeWidth As Long
		ReadLength(fmt, typeWidth)

		Select Case fmt[0]
			Case &h64 'd
				FormatInteger(s, TraitsIntegerD, params[i], True, typeWidth, precision, fieldWidth, flags)
			Case &h69 'i
				FormatInteger(s, TraitsIntegerD, params[i], True, typeWidth, precision, fieldWidth, flags)
			Case &h75 'u
				FormatInteger(s, TraitsIntegerU, params[i], False, typeWidth, precision, fieldWidth, flags)
			Case &h4f 'O
				flags Or= Cap
				Goto *O
			Case &h6f 'o
			*O
				FormatInteger(s, TraitsIntegerO, params[i], False, typeWidth, precision, fieldWidth, flags)
			Case &h58 'X
				flags Or= Cap
				Goto *X
			Case &h78 'x
			*X
				FormatInteger(s, TraitsIntegerX, params[i], False, typeWidth, precision, fieldWidth, flags)
'現状ではVoidPtrを引数にする手段は無いはず
'			Case &h58 'p
'				FormatInteger(s, TraitsIntegerX, params[i], False, typeWidth, precision, fieldWidth, flags Or Cap)
			Case &h45 'E
				flags Or= Cap
				Goto *E
			Case &h65 'e
			*E
				AdjustPrecision(precision)
				FormatFloat(s, AddressOf(FormatFloatE_Convert), params[i], precision, fieldWidth, flags)
			Case &h46 'F
				flags Or= Cap
				Goto *F
			Case &h66 'f
			*F
				AdjustPrecision(precision)
				FormatFloat(s, AddressOf(FormatFloatF_Convert), params[i], precision, fieldWidth, flags)
			Case &h47 'G
				flags Or= Cap
				Goto *G
			Case &h67 'g
			*G
				AdjustPrecision(precision)
				FormatFloat(s, AddressOf(FormatFloatG_Convert), params[i], precision, fieldWidth, flags)
			Case &h41 'A
				flags Or= Cap
				Goto *A
			Case &h61 'a
			*A
				FormatFloat(s, AddressOf(FormatFloatA), params[i], precision, fieldWidth, flags)
			Case &h73 's
				FormatString(s, params[i] As String, precision, fieldWidth, flags)
			Case &h63 'c
				FormatCharacter(s, params[i] As BoxedStrChar, precision, fieldWidth, flags)
'			Case &h6e 'n
			Case &h25 '%
				s.Append(&h25 As Char)
				i--
			Case 0
				Exit Do
		End Select
		fmt = VarPtr(fmt[1]) 'po
		i++
	Loop
	SPrintf = s.ToString
End Function

End Namespace 'Detail

/*!
@brief	Cのsprintfのような書式化関数10引数版
@author	Egtra
@date	2007/10/27
@param[in] format	書式指定
@param[in] paramN	引数
@return 書式化された文字列
*/

Function SPrintf(format As String, param0 As Object,
	param1 As Object, param2 As Object, param3 As Object,
	param4 As Object, param5 As Object, param6 As Object,
	param7 As Object, param8 As Object, param9 As Object) As String

	Dim params = VarPtr(param0) As *Object

	SPrintf = ActiveBasic.Strings.Detail.SPrintf(format, params, 10)
End Function

/*!
@brief	Cのsprintfのような書式化関数9引数版
@author	Egtra
@date	2007/10/27
@param[in] format	書式指定
@param[in] paramN	引数
@return 書式化された文字列
*/
Function SPrintf(format As String, param0 As Object,
	param1 As Object, param2 As Object, param3 As Object,
	param4 As Object, param5 As Object, param6 As Object,
	param7 As Object, param8 As Object) As String

	Dim params = VarPtr(param0) As *Object

	SPrintf = ActiveBasic.Strings.Detail.SPrintf(format, params, 9)
End Function

/*!
@brief	Cのsprintfのような書式化関数8引数版
@author	Egtra
@date	2007/10/27
@param[in] format	書式指定
@param[in] paramN	引数
@return 書式化された文字列
*/
Function SPrintf(format As String, param0 As Object,
	param1 As Object, param2 As Object, param3 As Object,
	param4 As Object, param5 As Object, param6 As Object,
	param7 As Object) As String

	Dim params = VarPtr(param0) As *Object

	SPrintf = ActiveBasic.Strings.Detail.SPrintf(format, params, 8)
End Function

/*!
@brief	Cのsprintfのような書式化関数7引数版
@author	Egtra
@date	2007/10/27
@param[in] format	書式指定
@param[in] paramN	引数
@return 書式化された文字列
*/
Function SPrintf(format As String, param0 As Object,
	param1 As Object, param2 As Object, param3 As Object,
	param4 As Object, param5 As Object, param6 As Object) As String

	Dim params = VarPtr(param0) As *Object

	SPrintf = ActiveBasic.Strings.Detail.SPrintf(format, params, 7)
End Function

/*!
@brief	Cのsprintfのような書式化関数6引数版
@author	Egtra
@date	2007/10/27
@param[in] format	書式指定
@param[in] paramN	引数
@return 書式化された文字列
*/
Function SPrintf(format As String, param0 As Object,
	param1 As Object, param2 As Object, param3 As Object,
	param4 As Object, param5 As Object) As String

	Dim params = VarPtr(param0) As *Object

	SPrintf = ActiveBasic.Strings.Detail.SPrintf(format, params, 6)
End Function

/*!
@brief	Cのsprintfのような書式化関数5引数版
@author	Egtra
@date	2007/10/27
@param[in] format	書式指定
@param[in] paramN	引数
@return 書式化された文字列
*/
Function SPrintf(format As String, param0 As Object,
	param1 As Object, param2 As Object, param3 As Object,
	param4 As Object) As String

	Dim params = VarPtr(param0) As *Object

	SPrintf = ActiveBasic.Strings.Detail.SPrintf(format, params, 5)
End Function

/*!
@brief	Cのsprintfのような書式化関数4引数版
@author	Egtra
@date	2007/10/27
@param[in] format	書式指定
@param[in] paramN	引数
@return 書式化された文字列
*/
Function SPrintf(format As String, param0 As Object,
	param1 As Object, param2 As Object, param3 As Object) As String

	Dim params = VarPtr(param0) As *Object

	SPrintf = ActiveBasic.Strings.Detail.SPrintf(format, params, 4)
End Function

/*!
@brief	Cのsprintfのような書式化関数3引数版
@author	Egtra
@date	2007/10/27
@param[in] format	書式指定
@param[in] paramN	引数
@return 書式化された文字列
*/
Function SPrintf(format As String, param0 As Object,
	param1 As Object, param2 As Object) As String

	Dim params = VarPtr(param0) As *Object

	SPrintf = ActiveBasic.Strings.Detail.SPrintf(format, params, 3)
End Function

/*!
@brief	Cのsprintfのような書式化関数2引数版
@author	Egtra
@date	2007/10/27
@param[in] format	書式指定
@param[in] paramN	引数
@return 書式化された文字列
*/
Function SPrintf(format As String, param0 As Object,
	param1 As Object) As String

	Dim params = VarPtr(param0) As *Object

	SPrintf = ActiveBasic.Strings.Detail.SPrintf(format, params, 2)
End Function

/*!
@brief	Cのsprintfのような書式化関数1引数版
@author	Egtra
@date	2007/10/27
@param[in] format	書式指定
@param[in] paramN	引数
@return 書式化された文字列
*/
Function SPrintf(format As String, param0 As Object) As String
	Dim params = VarPtr(param0) As *Object
	SPrintf = ActiveBasic.Strings.Detail.SPrintf(format, params, 1)
End Function

/*!
@brief	Cのsprintfのような書式化関数0引数版
@author	Egtra
@date	2007/10/27
@param[in] format	書式指定
@param[in] paramN	引数
@return 書式化された文字列
*/
Function SPrintf(format As String) As String
	SPrintf = ActiveBasic.Strings.Detail.SPrintf(format, 0, 0)
End Function

End Namespace 'Strings
End Namespace 'ActiveBasic
