/*!
@file	Classes/System/Text/Encoding.ab
@brief	Encodingクラスとそれに関係するクラスなどの宣言・定義
*/

Namespace System
Namespace Text

/*!
@brief	各種の文字符号化（エンコーディング）を行うためのクラス
@date	2007/12/07
@auther	Egtra

なお、このクラスで、文字列の長さやバッファの大きさを指定するときには、
1 WCHAR = 2バイト（UTF-16符号単位）、1 Byte = 1バイトの単位で指定する。

*/
Class Encoding
	Implements ICloneable
Public
	/*!
	@brief	簡易的複製を作成する。
	*/
	Virtual Function Clone() As Object
		Clone = This
	End Function

'	Override Function Equals(y As Object) As Boolean
'	End Function

'	Override Function GetHashCode() As Long
'	End Function

Public
	Sub Encoding()
	End Sub

	/*!
	@brief	符号化して得られる文字列の長さを計算する。
	@param[in] src	対象文字列
	@param[in] srcCount	srcの長さ（要素数単位）
	@return	符号化して得られる文字列の長さ（要素数単位）
	@date	2007/12/08
	*/
	Function GetBytesCount(src As *WCHAR, srcCount As Long) As Long
		If src = 0 Then
			Throw New ArgumentNullException("src")
		ElseIf srcCount < 0 Then
			Throw New ArgumentOutOfRangeException("srcCount")
		End If
		Return GetBytesCountCore(src, srcCount)
	End Function
#ifdef UNICODE
	/*!
	@brief	符号化して得られる文字列の長さを計算する。
	@param[in] src	対象文字列
	@return	符号化して得られる文字列の長さ（要素数単位）
	@date	2007/12/08
	*/
	Function GetBytesCount(src As String) As Long
		If ActiveBasic.IsNothing(src) Then
			Throw New ArgumentNullException("src")
		End If
		Return GetBytesCountCore(StrPtr(src), src.Length)
	End Function
#endif

Protected
	/*!
	@brief	GetBytesCountの実装を行う。
	@param[in] src	対象文字列
	@param[in] srcCount	srcの長さ（要素数単位）
	@return	符号化して得られる文字列の長さ（要素数単位）
	@date	2007/12/08
	*/
	Virtual Function GetBytesCountCore(src As *WCHAR, srcCount As Long) As Long
		Dim s = New IO.Detail.WriteCountingNullStream
		GetEncoder().Encode(src, srcCount, s, True)
		GetBytesCountCore = s.WriteCount
		If GetBytesCountCore < 0 Then
			Throw New OverflowException
		End If
	End Function
Public
	/*!
	@brief	符号化する。
	@param[in] src	入力
	@param[in] srcCount	srcの長さ（要素数単位）
	@param[out] dst	出力
	@param[in] dstCount	dstのバッファの大きさ（要素数単位）
	@return dstに書き込まれた要素数
	@date	2007/12/08
	*/
	Function GetBytes(src As *WCHAR, srcCount As Long, dst As *Byte, dstCount As Long) As Long
		If src = 0 Then
			Throw New ArgumentNullException("src")
		ElseIf dst = 0 Then
			Throw New ArgumentNullException("dst")
		ElseIf srcCount < 0 Then
			Throw New ArgumentOutOfRangeException("srcCount")
		ElseIf dstCount < 0 Then
			Throw New ArgumentOutOfRangeException("dstCount")
		End If

		Return GetBytesCore(src, srcCount, dst, dstCount)
	End Function

Protected
	/*!
	@brief	GetBytesの処理を行う。
	@param[in] src	入力
	@param[in] srcCount	srcの長さ（要素数単位）
	@param[out] dst	出力
	@param[in] dstCount	dstのバッファの大きさ（要素数単位）
	@return dstに書き込まれた要素数
	@exception ArgumentException	バッファの大きさが足りない
	@date	2007/12/08
	*/
	Virtual Function GetBytesCore(src As *WCHAR, srcCount As Long, dst As *Byte, dstCount As Long) As Long
		Dim s = New IO.MemoryStream
		GetEncoder().Encode(src, srcCount, s, True)
		Dim size = s.Position()
		If size > (dstCount As QWord) Then
			Throw New ArgumentException("dstCount")
		End If
		GetBytesCore = size As Long
		memcpy(dst, s.ToPointer(), size As SIZE_T)
	End Function
Public
	/*!
	@brief	復号して得られる文字列の長さを計算する。
	@param[in] src	対象文字列
	@param[in] srcCount	srcの長さ（要素数単位）
	@return	復号して得られる文字列の長さ（要素数単位）
	@date	2007/12/08
	*/
	Function GetCharsCount(src As *Byte, srcCount As Long) As Long
		If src = 0 Then
			Throw New ArgumentNullException("src")
		ElseIf srcCount < 0 Then
			Throw New ArgumentOutOfRangeException("srcCount")
		End If
		Return GetCharsCountCore(src, srcCount)
	End Function

Protected
	/*!
	@brief	GetCharsCountの処理を行う。
	@param[in] src	対象文字列
	@param[in] srcCount	srcの長さ（要素数単位）
	@return	符号化して得られる文字列の長さ（要素数単位）
	@date	2007/12/08
	*/
	Virtual Function GetCharsCountCore(src As *Byte, srcCount As Long) As Long
		Dim s = New IO.MemoryStream(src, srcCount)
		Dim l = New Collections.Generic.List<WCHAR>
		GetDecoder().Decode(l, s)
		GetCharsCountCore = l.Count
	End Function
Public
	/*!
	@brief	復号する。
	@param[in] src	入力
	@param[in] srcCount	srcの長さ（要素数単位）
	@param[out] dst	出力
	@param[in] dstCount	srcのバッファの大きさ（要素数単位）
	@return dstに書き込まれた要素数
	@date	2007/12/08
	*/
	Function GetChars(src As *Byte, srcCount As Long, dst As *WCHAR, dstCount As Long) As Long
		If dst = 0 Then
			Throw New ArgumentNullException("dst")
		ElseIf src = 0 Then
			Throw New ArgumentNullException("src")
		ElseIf dstCount < 0 Then
			Throw New ArgumentOutOfRangeException("dstCount")
		ElseIf srcCount < 0 Then
			Throw New ArgumentOutOfRangeException("srcCount")
		End If

		Return GetCharsCore(src, srcCount, dst, dstCount)
	End Function

Protected
	/*!
	@brief	GetCharsの処理を行う。
	@param[in] src	入力
	@param[in] srcCount	srcの長さ（要素数単位）
	@param[out] dst	出力
	@param[in] dstCount	dstのバッファの大きさ（要素数単位）
	@return dstに書き込まれた要素数
	@exception ArgumentException	バッファの大きさが足りない
	@date	2007/12/08
	*/
	Virtual Function GetCharsCore(src As *Byte, srcCount As Long, dst As *WCHAR, dstCount As Long) As Long
		Dim s = New IO.MemoryStream(src, srcCount)
		Dim l = New Collections.Generic.List<WCHAR>
		GetDecoder().Decode(l, s)
		If l.Count > dstCount Then
			Throw New ArgumentException("dstCount")
		End If
		Dim p As *WCHAR
		p = l
		memcpy(dst, p, (l.Count As SIZE_T) * 2)
	End Function
Public
#ifdef UNICODE
	/*!
	@brief	復号し、Stringで結果を返す。
	@param[in] src	入力
	@param[in] srcCount	srcの長さ（要素数単位）
	@return	変換結果の文字列
	@date	2007/12/08
	*/
	Function GetString(src As *Byte, srcCount As Long) As String
		If src = 0 Then
			Throw New ArgumentNullException("src")
		ElseIf srcCount < 0 Then
			Throw New ArgumentOutOfRangeException("srcCount")
		End If
		GetString = getStringCore(src, srcCount)
	End Function

Private
	Function getStringCore(src As *Byte, srcCount As Long) As String
		Dim sb = New StringBuilder
		Dim bufCount = GetMaxCharCount(srcCount)
		sb.Length = bufCount
		Dim len = GetCharsCore(src, srcCount, StrPtr(sb), bufCount)
		sb.Length = len
		getStringCore = sb.ToString()
	End Function
#endif

Public
	/*!
	@brief	復号器を取得する。
	*/
	Abstract Function GetDecoder() As Decoder

	/*!
	@brief	符号器を取得する。
	*/
	Abstract Function GetEncoder() As Encoder

	/*!
	@brief	ある長さの文字列を符号化して得られるバイト列の最大の長さを返す。
	*/
	Abstract Function GetMaxByteCount(srcCount As Long) As Long

	/*!
	@brief	ある長さのバイト列を復号して得られる文字列の最大の長さを返す。
	*/
	Abstract Function GetMaxCharCount(srcCount As Long) As Long

	/*!
	@brief	符号化された文字列の先頭につける識別文字列の取得
	ようするにUTFのBOM。
	*/
	Virtual Function GetPreamble() As *Byte
		Return 0
	End Function

	/*!
	@brief	GetPreambleの配列の要素数。
	*/
	Virtual Function GetPreambleLength() As Long
		Return 0
	End Function

Public
'	Abstract Function BodyName() As String
'	Abstract Function HeaderName() As String

	/*!
	@brief	コードページの取得。
	*/
'	Abstract Function CodePage() As Long
	/*!
	@brief	最も厳密に対応するWindowsコードページの取得。
	*/
'	Abstract Function WindowsCodePage() As Long

Public
	/*!
	@brief	この符号化形式の名前の取得。
	*/
'	Abstract Function EncodingName() As String

	/*!
	@brief	この符号化形式のIANA登録名の取得。
	*/
'	Abstract Function WebName() As String

'	Abstract Function IsBrowserDisplay() As Boolean
'	Abstract Function IsBrowserSave() As Boolean
'	Abstract Function IsMailNewsDisplay() As Boolean
'	Abstract Function IsMailNewsSave() As Boolean

	/*!
	@brief	この符号化形式が、1バイト文字だけを使う（複数バイト文字を使わない）かどうか。
	*/
'	Abstract Function IsSingleByte() As Boolean

	'GetPreamble

	/*!
	@brief	ある符号化文字列から別の符号化文字列へ変換する。
	@param[in] srcEncoding	入力の符号化方式
	@param[in] dstEncoding	出力の符号化方式
	@param[in] bytes	入力文字列
	@param[in] size	バイト単位での文字列の長さ
	@return	出力文字列
	@exception ArgumentNullException	srcEncoding, dstEncoding, bytesの少なくとも1つ以上がNothing/NULLのとき。
	@exception ArgumentOutOfRangeException	sizeが明らかに範囲外（負の値など）のとき。
	*/
'	Static Function Convert(srcEncoding As Encoding, dstEncoding As Encoding, bytes As *Byte, size As Long) As *Byte
'	End Function
	
'	Static Function Convert(srcEncoding As Encoding, dstEncoding As Encoding, bytes As *Byte, index As Long, count As Long) As *Byte
'	End Function

	/*!
	@brief	指定したコードページ用のEncodingインスタンスの取得。
	*/
'	Static Function GetEncoding(codepage As Long) As Encoding
'	End Function
	/*!
	@brief	指定した符号化形式名用のEncodingインスタンスの取得。
	*/
'	Static Function GetEncoding(name As String) As Encoding
'	End Function
	/*!
	@brief	システムで標準のANSIコードページ用のEncodingインスタンスの取得。
	*/
	Static Function Default() As Encoding
	End Function
	/*!
	@brief	UTF-8用のEncodingインスタンスの取得。
	*/
	Static Function UTF8() As Encoding
	End Function
	/*!
	@brief	UTF-16LE用のEncodingインスタンスの取得。
	*/
	Static Function UTF16() As Encoding
	End Function
	/*!
	@brief	UTF-16BE用のEncodingインスタンスの取得。
	*/
	Static Function UTF16BE() As Encoding
	End Function
End Class

/*!
@brief	復号を行うクラス
@date	2009/01/12
@auther	Egtra
内部処理用
*/
Class Decoder
Public
	Function Decode(dst As Collections.Generic.List<WCHAR>, st As IO.Stream) As Boolean
		Decode = DecodeImpl(dst, st)
	End Function

Protected
	Abstract Function DecodeImpl(dst As Collections.Generic.List<WCHAR>, s As IO.Stream) As Boolean
End Class

/*!
@brief	符号化を行うクラス
@date	2009/01/12
@auther	Egtra
内部処理用
*/
Class Encoder
Public
	Sub Encode(src As *WCHAR, size As SIZE_T, s As IO.Stream, last As Boolean)
		EncodeImpl(src, size, s, last)
	End Sub

Protected
	Abstract Sub EncodeImpl(src As *WCHAR, size As SIZE_T, s As IO.Stream, last As Boolean)
End Class

Enum NormalizationForm
	FormC
	FormD
	FormKC
	FormKD
End Enum

Namespace Detail

/*!
@brief WideCharToMultiByte/MultiByteToWideCharで変換を行うエンコーディング。
DBCS/SBCS限定。UTF-8やUTF-7は非対応。
*/
Class WindowsCodePageEncoding
	Inherits Encoding
Public
	Sub WindowsCodePageEncoding(codepage As DWord)
		cp = codepage
	End Sub

	Override Function GetHashCode() As Long
		GetHashCode = cp As Long
	End Function

	Override Function Equals(x As Object) As Boolean
		If GetType().Equals(x.GetType()) Then
			Dim xe = x As WindowsCodePageEncoding
			Equals = cp = xe.cp
		Else
			Equals = False
		End If
	End Function

	Override Function GetDecoder() As Decoder
		GetDecoder = New WindowsCodePageDecoder(cp)
	End Function

	Override Function GetEncoder() As Encoder
		GetEncoder = New WindowsCodePageEncoder(cp)
	End Function

	/*!
	@brief	ある長さの文字列を符号化して得られるバイト列の最大の長さを返す。
	*/
	Override Function GetMaxByteCount(srcCount As Long) As Long
		GetMaxByteCount = srcCount * 2
	End Function

	/*!
	@brief	ある長さのバイト列を復号して得られる文字列の最大の長さを返す。
	*/
	Override Function GetMaxCharCount(srcCount As Long) As Long
		GetMaxCharCount = srcCount
	End Function

Protected
	Override Function GetBytesCountCore(src As *WCHAR, srcCount As Long) As Long
		GetBytesCountCore = WideCharToMultiByte(cp, 0, src, srcCount, 0, 0, 0, 0)
		If srcCount <> 0 And GetBytesCountCore = 0 Then
			ActiveBasic.Windows.ThrowWithLastError()
		End If
	End Function

	Override Function GetBytesCore(src As *WCHAR, srcCount As Long, dst As *Byte, dstCount As Long) As Long
		GetBytesCore = WideCharToMultiByte(cp, 0, src, srcCount, dst As PCSTR, dstCount, 0, 0)
		If srcCount <> 0 And GetBytesCore = 0 Then
			ActiveBasic.Windows.ThrowWithLastError()
		End If
	End Function

	Override Function GetCharsCountCore(src As *Byte, srcCount As Long) As Long
		GetCharsCountCore = MultiByteToWideChar(cp, 0, src As PCSTR, srcCount, 0, 0)
		If srcCount <> 0 And GetCharsCountCore = 0 Then
			ActiveBasic.Windows.ThrowWithLastError()
		End If
	End Function

	Override Function GetCharsCore(src As *Byte, srcCount As Long, dst As *WCHAR, dstCount As Long) As Long
		GetCharsCore = MultiByteToWideChar(cp, 0, src As PCSTR, srcCount, dst, dstCount)
		If srcCount <> 0 And GetCharsCore = 0 Then
			ActiveBasic.Windows.ThrowWithLastError()
		End If
	End Function

Private
	cp As DWord
End Class

/*!
@brief	WideCharToMultiByteで復号化を行うクラス
@date	2009/01/12
@auther	Egtra
内部処理用
*/
Class WindowsCodePageEncoder
	Inherits Encoder
Public
	Sub WindowsCodePageEncoder(codepage As DWord)
		cp = codepage
	End Sub

Protected
	Override Sub EncodeImpl(src As *WCHAR, size As SIZE_T, s As IO.Stream, last As Boolean)
		' サロゲートペアや結合文字列 (Combining Character Sequence)の途中でバッファが途切れている場合に対応する
		' ToDo: エラー処理
		Dim mbLen = WideCharToMultiByte(cp, 0, src, size As Long, 0, 0, 0, 0)
		Dim mbBuf = GC_malloc_atomic(mbLen)
		WideCharToMultiByte(cp, 0, src, size As Long, mbBuf, mbLen, 0, 0)
		s.Write(mbBuf, 0, mbLen)
	End Sub

Private
	cp As DWord
End Class

/*!
@brief	MultiByteToWideCharで復号化を行うクラス
@date	2009/01/12
@auther	Egtra
内部処理用
*/
Class WindowsCodePageDecoder
	Inherits Decoder
Public
	Sub WindowsCodePageDecoder(codepage As DWord)
		cp = codepage
	End Sub

Protected
	Override Function DecodeImpl(dst As Collections.Generic.List<WCHAR>, s As IO.Stream) As Boolean
		Dim i As Long
		For i = 0 To DefalultDecodingBufferSize - 1 'ELM
			Dim len As Long
			Dim buf[1] As CHAR
			Dim t = s.ReadByte()
			If t = -1 Then
				DecodeImpl = False
				Exit Function
			End If
			buf[0] = t As CHAR
			If IsDBCSLeadByteEx(cp, buf[0]) Then
				t = s.ReadByte()
				If t = -1 Then
					dst.Add(&hfffd As WCHAR)
					DecodeImpl = False
					Exit For
				End If
				buf[1] = t As CHAR
				len = 2
			Else
				len = 1
			End If
			Dim wc As WCHAR
			If MultiByteToWideChar(cp, 0, buf, len, VarPtr(wc), 1) = 0 Then
				dst.Add(&hFFFD As WCHAR)
			Else
				dst.Add(wc)
			End If
		Next
		DecodeImpl = True
	End Function

Private
	cp As DWord
End Class

Const DefalultDecodingBufferSize = 16384

End Namespace

End Namespace 'Text
End Namespace 'System
