/* vim: ts=4 sw=4 sts=4 fenc=utf-8 ff=unix :
 * =============================================================================
 *  serializable
 *
 *  Copyright (C) 2008 by kei-n
 *  URL : http://sys0tem.homelinux.com/
 *
 * =============================================================================
 */
module sal.io.serializable;

// for Endian
public import std.system;


/**
 * クラスがシリアライズ可能であることを示すインタフェース.
 */
public
interface Serializable
{
	// NOP
}


/**
 * オブジェクトを外部に出力、または外部から入力可能とするインタフェース.
 */
public
interface Externalizable : Serializable
{

	/**
	 * 入力ストリームより、オブジェクトを復元します.
	 * 本インタフェースを使用するクラスは、
	 * 引数なしのコンストラクタを呼び出した後、
	 * 本メソッドを呼び出し、オブジェクトの復元を試みます.
	 *
	 * Params:
	 *	input = 入力ストリーム
	 */
	void readExternal(ObjectInput input);


	/**
	 * オブジェクトを出力ストリームへ書き出します.
	 * 本メソッドの呼び出しにより、クラス内部の状態が変更されてはなりません.
	 *
	 * Params:
	 *	output = 出力ストリーム
	 */
	const void writeExternal(ObjectOutput output);


}


/**
 * オブジェクト入力ストリーム.
 */
public
class ObjectInput
{

	/**
	 * 指定されたエンディアンの入力ストリームを構築します.
	 *
	 * Params:
	 *	buff   = バッファ
	 *	offset = オフセット
	 *	e      = エンディアン
	 */
	public
	this(in ubyte[] buff, ref int* offset, in Endian e = endian)
	{
		// 各値を設定
		this.objBuff    = buff;
		this.offsetPtr  = offset;
		this.endianMode = e;
	}


	/**
	 * 指定された型で値を読み取ります.				$(BR)$(BR)
	 * 型として、次の値を指定することができます.	$(BR)$(BR)
	 * $(UL
	 *   $(LI プリミティブな型)
	 *   $(LI struct 型)
	 *   $(LI Externalizable を継承したクラス)
	 * )$(BR)$(BR)
	 * 型の中に動的配列が含まれる場合、事前にサイズを決める必要があります.
	 * $(D_CODE
	 * struct Sample {
	 *     int     magic;
	 *     ubyte[] data;
	 * }
	 * Sample s;
	 * s.data = new ubyte[40];
	 * s      = input.read(s);	// input is ObjectInput instance
	 * )
	 *
	 * Params:
	 *	t = 読み出す型
	 *
	 * Return:
	 *	値
	 */
	public
	T read(T)(ref T t)
	{
		return toValue(t, this.objBuff, this.offsetPtr, endianMode);
	}


	/**
	 * 次のバッファのオフセット位置を取得します.
	 *
	 * Return:
	 *	次のバッファのオフセット位置
	 */
	public
	int getNextOffset()
	{
		return *this.offsetPtr;
	}


	private
	{
		const ubyte[]    objBuff;		/// オブジェクトバッファ
		int*             offsetPtr;		/// オフセット
		invariant Endian endianMode;	/// エンディアン
	}
	
}


/**
 * オブジェクト出力ストリーム.
 */
public
class ObjectOutput
{

	/**
	 * 指定されたエンディアンの入力ストリームを構築します.
	 * バッファが足りない場合、自動的にバッファサイズが拡張されます.
	 *
	 * Params:
	 *	buff   = バッファ
	 *	offset = オフセット
	 *	e      = エンディアン
	 */
	public
	this(ref ubyte[] buff, ref int* offset, in Endian e = endian)
	{
		// 各値を設定
		this.objBuff    = buff;
		this.offsetPtr  = offset;
		this.endianMode = e;
	}


	/**
	 * 指定された値をストリームに書き出します.		$(BR)$(BR)
	 * 型として、次の値を指定することができます.	$(BR)$(BR)
	 * $(UL
	 *   $(LI プリミティブな型)
	 *   $(LI struct 型)
	 *   $(LI Externalizable を継承したクラス)
	 * )
	 *
	 * Params:
	 *	t = 書き出す値
	 */
	public
	void write(T)(in T t)
	{
		toBytes(this.objBuff, this.offsetPtr, t, endianMode);
	}


	/**
	 * 次のバッファのオフセット位置を取得します.
	 *
	 * Return:
	 *	次のバッファのオフセット位置
	 */
	public
	int getNextOffset()
	{
		return *this.offsetPtr;
	}


	/**
	 * ストリームバイトバッファを取得します.
	 *
	 * Return:
	 *	バイトバッファ
	 */
	public
	ubyte[] getBytes()
	{
		return this.objBuff;
	}

	private
	{
		ubyte[]			 objBuff;		/// オブジェクトバッファ
		int*             offsetPtr;		/// オフセット
		invariant Endian endianMode;	/// エンディアン
	}
	
}


/**
 * 指定された値を ubyte[] に変換します.
 * 値は指定されたエンディアンに従い、シリアライズ化されます.
 *
 * Params:
 *	t = 値
 *	e = エンディアン (Endian.BigEndian/Endian.LittleEndian)
 *
 * Return:
 *	変換後の値
 */
public
ubyte[] toBytes(T)(in T t, in Endian e = endian)
{
	int  offset    = 0;
	int* offsetPtr = &offset;
	ubyte[] buff   = new ubyte[0];
	return toBytes(buff, offsetPtr, t, e);
}

/**
 * 指定されたバイトバッファを指定された型の値に変換します.
 *
 * Params:
 *	t		= 変換するスカラ型
 *	buff	= バイトバッファ
 *	offset	= オフセット
 *	e		= エンディアン
 *
 * Return:
 *	変換後の値
 */
public
ubyte[] toBytes(T)
	(ref ubyte[] buff, ref int* offset, in T t, in Endian e)
{
	static if (__traits(isScalar, T))
	{	// スカラ型
		toScalarBytes(buff, offset, t, e);
	}
	else static if (is(T == struct))
	{	// 構造体
		toStructBytes(buff, offset, t, e);
	}
	else static if (is(T U:U[]))
	{	// 動的配列型
		toArrayBytes(buff, offset, t, e);
	}
	else static if (is(T : Externalizable))
	{	// 外部出力可能クラス
		toExternalizableBytes(buff, offset, t, e);
	}
	else static if (is(T == union))
	{	// union
		toUnionBytes(buff, offset, t, e);
	}
	else
	{	// 上記以外はシリアライズ化不可
		static assert(false);
	}
	return buff;
}
unittest
{
	unittestD d;
	d.c = new unittestC();
	d.bVal                = 0x12;
	d.c.iVal              = 0x12345678;
	d.c.b.head[0].bVal[0] = 0xAA;
	d.c.b.head[0].iVal    = 0xABCDEF01;
	d.c.b.head[0].dVal    = 0.25;
	d.c.b.data[0]         = 0x12;
	d.c.b.data[1]         = 0x34;
	d.iVal                = 0x98765432;

	int     offset    = 0;
	int*    offsetPtr = &offset;
	ubyte[] buff      = new ubyte[1024];
	toBytes(buff, offsetPtr, d, Endian.BigEndian);

	assert(buff.length == 1024);
	ubyte[] data = [
		0x12,					// unittestD.bVal
		0x12, 0x34, 0x56, 0x78,	// unittestD.c.iVal
		0xAA,					// unittestD.c.b.head.bVal
		0xAB, 0xCD, 0xEF, 0x01,	// unittestD.c.b.head.iVal

		// unittestD.c.b.head.dVal (0.25 [BigEndian])
		0x3F, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x12, 0x34,				// unittestD.c.b.data[0], [1]
		0x98, 0x76, 0x54, 0x32	// unittestD.iVal
	];

	for (int i = 0; i < data.length; i++)
	{
		assert(data[i] == buff[i]);
	}

}


/**
 * 指定された buff を指定された型に変換します.
 *
 * Params:
 *	t    = 変換する型
 *	buff = バッファ
 *	e    = エンディアン
 *
 * Return:
 *	変換後の値
 */
public
T toValue(T)(ref T t, in ubyte[] buff, in Endian e = endian)
{
	int  offset    = 0;
	int* offsetPtr = &offset;
	return toValue(t, buff, offsetPtr, e);
}


/**
 * 指定されたバイトバッファを指定された型の値に変換します.
 *
 * Params:
 *	t		= 変換するスカラ型
 *	buff	= バイトバッファ
 *	offset	= オフセット
 *	e		= エンディアン
 *
 * Return:
 *	変換後の値
 */
private
T toValue(T)
	(ref T t, in ubyte[] buff, ref int* offset, in Endian e)
{
	static if (__traits(isScalar, T))
	{	// スカラ型
		toScalarValue(t, buff, offset, e);
	}
	else static if (is(T == struct))
	{	// 構造体
		toStructValue(t, buff, offset, e);
	}
	else static if (is(T U:U[]))
	{	// 動的配列型
		toArrayValue(t, buff, offset, e);
	}
	else static if (is(T : Externalizable))
	{	// 外部出力可能クラス
		toExternalizableValue(t, buff, offset, e);
	}
	else static if (is(T == union))
	{	// union
		toUnionValue(t, buff, offset, e);
	}
	else
	{	// 上記以外はシリアライズ化不可
		static assert(false);
	}
	return t;
}
unittest
{
	ubyte[] data = [
		0x12,					// unittestD.bVal
		0x34, 0x56, 0x78, 0x9A,	// unittestD.c.iVal
		0xBC,					// unittestD.c.b.head.bVal
		0xDE, 0xFE, 0xDC, 0xBA,	// unittestD.c.b.head.iVal

		// unittestD.c.b.head.dVal (0.25 [BigEndian])
		0x3F, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x01, 0x23,				// unittestD.c.b.data[0], [1]
		0x98, 0x76, 0x54, 0x32	// unittestD.iVal
	];

	ubyte[] tmp = cast(ubyte[]) data;

	int  offset    = 0;
	int* offsetPtr = &offset;
	unittestD d;
	unittestD retD = toValue(d, tmp, offsetPtr, Endian.BigEndian);
	assert(d.bVal                == 0x12);
	assert(d.c.iVal              == 0x3456789A);
	assert(d.c.b.head[0].bVal[0] == 0xBC);
	assert(d.c.b.head[0].iVal    == 0xDEFEDCBA);
	assert(d.c.b.head[0].dVal    == 0.25);
	assert(d.c.b.data[0]         == 0x01);
	assert(d.c.b.data[1]         == 0x23);
	assert(d.iVal                == 0x98765432);
}


/**
 * 指定された型のシリアライズ化された際のサイズを取得します.
 *
 * Params:
 *    t = 型オブジェクト
 * Return:
 *    サイズ
 */
public
size_t getSize(T)(in T t)
{
	size_t size = 0;
	static if (__traits(isScalar, T))
	{	// スカラ型
		size =  t.sizeof;
	}
	else static if (is(T == struct))
	{	// 構造体
		foreach (i, param; t.tupleof)
		{
			static if(__traits(isStaticArray, typeof(param)))
			{	// 静的配列の場合は、一つずつ処理
				for (int j = 0; j < param.length; j++)
				{
					size += getSize(param[j]);
				}
			}
			else
			{	// 他は通常どおり処理
				size += getSize(param);
			}
		}
	}
	else static if (is(T U:U[]))
	{	// 動的配列型
		for (int i = 0; i < t.length; i++)
		{
			size += getSize(t[i]);
		}
	}
	else static if (is(T : Externalizable))
	{	// 外部出力可能クラス
		if (t is null)
		{
			t = new T();
		}
		int          offset    = 0;
		int*         offsetPtr = &offset;
		ubyte[]      buff      = new ubyte[0];
		ObjectOutput output    = new ObjectOutput(buff, offsetPtr);
		t.writeExternal(output);
		size = cast(size_t) output.getNextOffset();
	}
	else static if (is(T == union))
	{	// union
		size_t tmpSize;
		foreach (param; t.tupleof)
		{
			tmpSize = getSize(param);
			if (tmpSize > size)
			{	// もっともサイズの大きい物を union のサイズとする.
				size = tmpSize;
			}
		}
	}
	else
	{	// 上記以外はシリアライズ化不可
		static assert(false);
	}
	return size;
}



// =============================================================================
//  以下、モジュール内部関数
// =============================================================================

// -----------------------------------------------------------------------------
//  for UnittestClass
// -----------------------------------------------------------------------------
private
{
struct unittestA { ubyte bVal[1]; uint iVal; double dVal; }
struct unittestB { unittestA     head[1]; ubyte[] data; }
class  unittestC : Externalizable {
	/// コンストラクタ
	public this() {
		b.data = new ubyte[2];
	}
	/// 書き込み
	public
	const void writeExternal(ObjectOutput output) {
		output.write(this.iVal);
		output.write(this.b);
	}
	/// 読み取り
	public
	void readExternal(ObjectInput input) {
		input.read(this.iVal);
		input.read(this.b);
	}
	int iVal;
	unittestB b;
}
struct unittestD {
	byte bVal;
	unittestC c;
	int iVal;
}

}
// -----------------------------------------------------------------------------



// -----------------------------------------------------------------------------
//  toBytes
// -----------------------------------------------------------------------------


/**
 * 指定されたスカラ型の値を ubyte[] に変換します.
 * 指定されたバッファに収まらない場合、バッファが拡張されます.
 *
 * Params:
 *	buff	= バイトバッファ
 *	offset	= オフセット
 *	t		= 変換する型
 *	e		= エンディアン
 *
 * Return:
 *	変換後の値
 */
private
ubyte[] toScalarBytes(T)
	(ref ubyte[] buff, ref int* offset, in T t, in Endian e)
in
{
	static if (!__traits(isScalar, T)) { static assert(false); }
}
body
{
	// バッファ不足の場合追加する.
	int addBufLen = t.sizeof - (buff.length - *offset);
	if (addBufLen > 0)
	{
		buff ~= new ubyte[addBufLen];
	}
	ubyte* ptr = cast(ubyte*) &t;
	if (e == endian)
	{	// システムのエンディアンと同じなら変換不要
		buff[*offset .. (*offset + t.sizeof)] = ptr[0 .. t.sizeof];
	}
	else
	{	// システムのエンディアンと異なるなら変換要
		for (int i = (t.sizeof - 1); i >= 0; i--)
		{
			buff[*offset + i] = *ptr;
			ptr++;
		}
	}
	*offset += t.sizeof;	// サイズをセット
	return buff;
}
unittest
{
	int val = 0x12345678;

	int     offset    = 0;
	int*    offsetPtr = &offset;

	ubyte[] buff = new ubyte[4];

	// convert BigEndian (buff[0 - 3])
	toScalarBytes(buff, offsetPtr, val, Endian.BigEndian);

	// convert LittleEndian (buff[4 - 7])
	toScalarBytes(buff, offsetPtr, val, Endian.LittleEndian);

	// check
	ubyte[] checkBuffer = [0x12, 0x34, 0x56, 0x78, 0x78, 0x56, 0x34, 0x12];
	assert(*offsetPtr == 8);
	for (int i = 0; i < checkBuffer.length; i++)
	{
		assert(buff[i] == checkBuffer[i]);
	}
}


/**
 * 指定されたstruct型の値を ubyte[] に変換します.
 * 指定されたバッファに収まらない場合、バッファが拡張されます.
 *
 * Params:
 *	buff	= バイトバッファ
 *	offset	= オフセット
 *	t		= 変換する型
 *	e		= エンディアン
 *
 * Return:
 *	変換後の値
 */
private
ubyte[] toStructBytes(T)
	(ref ubyte[] buff, ref int* offset, in T t, in Endian e)
in
{
	static if (!is(T == struct)) { static assert(false); }
}
body
{
	foreach (param; t.tupleof)
	{
		toBytes(buff, offset, param, e);
	}
	return buff;
}


/**
 * 指定された配列型の値を ubyte[] に変換します.
 * 指定されたバッファに収まらない場合、バッファが拡張されます.
 *
 * Params:
 *	buff	= バイトバッファ
 *	offset	= オフセット
 *	t		= 変換する型
 *	e		= エンディアン
 *
 * Return:
 *	変換後の値
 */
private
ubyte[] toArrayBytes(T)
	(ref ubyte[] buff, ref int* offset, in T t, in Endian e)
in
{
	static if (!is(T U:U[])) { static assert(false); }
}
body
{
	for (int i = 0; i < t.length; i++)
	{
		toBytes(buff, offset, t[i], e);
	}
	return buff;
}

/**
 * 指定された Externalizableを実装したクラスを ubyte[] に変換します.
 * 指定されたバッファに収まらない場合、バッファが拡張されます.
 *
 * Params:
 *	buff	= バイトバッファ
 *	offset	= オフセット
 *	t		= 変換する型
 *	e		= エンディアン
 *
 * Return:
 *	変換後の値
 */
private
ubyte[] toExternalizableBytes(T)
	(ref ubyte[] buff, ref int* offset, in T t, in Endian e)
in
{
	static if (!is(T : Externalizable)) { static assert(false); }
}
body
{
	ObjectOutput output = new ObjectOutput(buff, offset, e);
	t.writeExternal(output);
	return buff;
}


/**
 * 指定されたunion型の値を ubyte[] に変換します.
 * もっともサイズの大きい型にて変換されます.
 * 指定されたバッファに収まらない場合、バッファが拡張されます.
 *
 * Params:
 *	buff	= バイトバッファ
 *	offset	= オフセット
 *	t		= 変換する型
 *	e		= エンディアン
 *
 * Return:
 *	変換後の値
 */
private
ubyte[] toUnionBytes(T)
	(ref ubyte[] buff, ref int* offset, in T t, in Endian e)
in
{
	static if (!is(T == union)) { static assert(false); }
}
body
{
	// 最大となるサイズを取得 [index 取得が好ましいがとりあえず]
	size_t size = getSize(t);
	size_t tmpSize;
	foreach (param; t.tupleof)
	{
		// 最大となるサイズと一致の場合, 対象型にて変換
		tmpSize = getSize(param);
		if (size == tmpSize)
		{
			return toBytes(buff, offset, param, e);
		}
	}
	// 以下実行されることはない「はず」
	assert(false);
}



// -----------------------------------------------------------------------------
//  toValue
// -----------------------------------------------------------------------------


/**
 * 指定されたバイトバッファを指定されたスカラ型の値に変換します.
 *
 * Params:
 *	t		= 変換する型
 *	buff	= バイトバッファ
 *	offset	= オフセット
 *	e		= エンディアン
 *
 * Return:
 *	変換後の値
 */
private
T toScalarValue(T)
	(ref T t, in ubyte[] buff, ref int* offset, in Endian e)
in
{
	static if (!__traits(isScalar, T)) { static assert(false); }
}
body
{
	T resultVal;
	ubyte* ptr = cast(ubyte*) &resultVal;
	if (e == endian)
	{	// システムのエンディアンと同じなら変換不要
		ptr[0 .. t.sizeof] = buff[(*offset) .. ((*offset) + t.sizeof)];
	}
	else
	{	// システムのエンディアンと異なるなら変換不要
		for (int i = (t.sizeof - 1); i >= 0; i--)
		{
			*ptr = buff[*offset + i];
			ptr++;
		}
	}
	*offset += t.sizeof;	// サイズをセット
	t        = resultVal;	// T に結果を格納
	return resultVal;		// T と同じ値を返す
}
unittest
{
	ubyte[] buff      = [ 0x00, 0x12, 0x34, 0x56, 0x78];
	int     val;
	int     offset    = 1;
	int*    offsetPtr = &offset;

	// BigEndian check
	toScalarValue(val, buff, offsetPtr, Endian.BigEndian);
	assert(val        == 0x12345678);
	assert(*offsetPtr == 5);

	// LittleEndian check
	*offsetPtr = 1;
	toScalarValue(val, buff, offsetPtr, Endian.LittleEndian);
	assert(val == 0x78563412);
}


/**
 * 指定されたバイトバッファを指定されたstruct型の値に変換します.
 *
 * Params:
 *	t		= 変換する型
 *	buff	= バイトバッファ
 *	offset	= オフセット
 *	e		= エンディアン
 *
 * Return:
 *	変換後の値
 */
private
T toStructValue(T)
	(ref T t, in ubyte[] buff, ref int* offset, in Endian e)
in
{
	static if (!is(T == struct)) { static assert(false); }
}
body
{
	foreach (i, param; t.tupleof)
	{
		static if(__traits(isStaticArray, typeof(param)))
		{	// 静的配列の場合は、一つずつ処理
			for (int j = 0; j < param.length; j++)
			{
				t.tupleof[i][j] = toValue(param[j], buff, offset, e);
			}
		}
		else
		{	// 静的配列以外は、通常どおり再起処理
			t.tupleof[i] = toValue(param, buff, offset, e);
		}
	}
	return t;
}


/**
 * 指定されたバイトバッファを指定されたstruct型の値に変換します.
 *
 * Params:
 *	t		= 変換する型
 *	buff	= バイトバッファ
 *	offset	= オフセット
 *	e		= エンディアン
 *
 * Return:
 *	変換後の値
 */

private
T toArrayValue(T)
	(ref T t, in ubyte[] buff, ref int* offset, in Endian e)
in
{
	static if (!is(T U:U[])) { static assert(false); }
}
body
{
	for (int i = 0; i < t.length; i++)
	{
		toValue(t[i], buff, offset, e);
	}
	return t;
}


/**
 * 指定されたバイトバッファを指定された Externalizable を
 * 実装したクラスに変換します.
 *
 * Params:
 *	t		= 変換する型
 *	buff	= バイトバッファ
 *	offset	= オフセット
 *	e		= エンディアン
 *
 * Return:
 *	変換後の値
 */
private
T toExternalizableValue(T)
	(ref T t, in ubyte[] buff, ref int* offset, in Endian e)
in
{
	static if (!is(T : Externalizable)) { static assert(false); }
}
body
{
	if (t is null)
	{	// null の場合は、クラスを構築する
		t = new T();
	}
	ObjectInput input = new ObjectInput(buff, offset, e);
	t.readExternal(input);
	return t;
}


/**
 * 指定されたバイトバッファを指定されたunion型の値に変換します.
 * もっとも, サイズの大きい型に調整されます.
 *
 * Params:
 *	t		= 変換する型
 *	buff	= バイトバッファ
 *	offset	= オフセット
 *	e		= エンディアン
 *
 * Return:
 *	変換後の値
 */
private
T toUnionValue(T)
	(ref T t, in ubyte[] buff, ref int* offset, in Endian e)
in
{
	static if (!is(T == union)) { static assert(false); }
}
body
{
	size_t size    = getSize(t);
	size_t tmpSize;
	foreach (i, param; t.tupleof)
	{	// 最大となるサイズと一致なら本型にて処理する.
		tmpSize = getSize(param);
		if (size == tmpSize)
		{
			static if(__traits(isStaticArray, typeof(param)))
			{	// 静的配列の場合は、一つずつ処理
				for (int j = 0; j < param.length; j++)
				{
					t.tupleof[i][j] = toValue(param[j], buff, offset, e);
				}
			}
			else
			{	// 静的配列以外は、通常どおり再起処理
				t.tupleof[i] = toValue(param, buff, offset, e);
			}
			return t;
		}
	}
	// ここに来ることはない「はず」
	assert(false);
}


