/* vim: ts=4 sw=4 sts=4 ff=unix fenc=utf-8 :
 * =====================================================================
 *  sc_stream_memory.c
 *  Copyright (c)  2003 - 2011  sys0tem
 *  LICENSE :
 *	LGPL (GNU Lesser General Public License - Version 3,29 June 2007)
 *	http://www.gnu.org/copyleft/lesser.html
 *	or
 *	EPL (Eclipse Public License - v1.0)
 *	http://www.eclipse.org/legal/epl-v10.html
 * =====================================================================
 */
#include <string.h>
#include <sc_os.h>
#include <sc_error.h>
#include <sc_mmgr.h>
#include <sc_stream_memory.h>


/* ---------------------------------------------------------------------
 *  プロトタイプ宣言
 * ---------------------------------------------------------------------
 */
static int  SC_Stream_stream_memoryRead(SC_Stream* strm, void* buff, size_t size);
static int SC_Stream_stream_memoryWrite(SC_Stream* strm, const void* buff, size_t size);
static bool SC_Stream_stream_memoryClose(SC_Stream* strm);

/**
 * メモリストリームを構築します。
 * 構築に失敗した場合、NULL を返します。
 *
 * @param size     ストリームの最大サイズ
 * @param buffSize 一時保存用バッファサイズ
 * @return ストリーム
 */
SC_Stream* SC_Stream_newMemoryStream(size_t size, size_t buffSize)
{
	SC_Stream* strm;
	SC_Stream_BufferData* memData;
	size_t     totalSize = sizeof(SC_Stream) + buffSize;
	totalSize += sizeof(SC_Stream_BufferData) + size;

	strm = (SC_Stream*) malloc(totalSize);
	if (strm == NULL)
	{	/* メモリ確保失敗	*/
		return NULL;
	}

	strm->_data.buff       = (char*) (strm + 1);
	strm->_data.buffSize   = buffSize;
	strm->_data.off        = 0;
	strm->_data.size       = 0;

	memData           = (SC_Stream_BufferData*) (strm->_data.buff + strm->_data.buffSize);
	memData->buff     = (char*) (memData + 1);
	memData->buffSize = size;
	memData->off      = 0;
	memData->size     = 0;

	strm->_strmInfo        = memData;
	strm->readc            = SC_Stream_readc_;
	strm->read             = SC_Stream_read_;
	strm->readExact        = SC_Stream_readExact_;
	strm->readLine         = SC_Stream_readLine_;
	strm->limitedRead      = SC_Stream_limitedRead_;
	strm->writec           = SC_Stream_writec_;
	strm->write            = SC_Stream_write_;
	strm->writeExact       = SC_Stream_writeExact_;
	strm->_readCore        = SC_Stream_stream_memoryRead;
	strm->_writeCore       = SC_Stream_stream_memoryWrite;
	strm->close            = SC_Stream_stream_memoryClose;
	return strm;
}


/**
 * メモリから読み込みます。
 * 末尾に達した場合 0 を返します。
 *
 * @param strm ストリーム
 * @param buff バッファ
 * @param size サイズ
 * @return 読み取ったバッファサイズ
 */
static
int SC_Stream_stream_memoryRead(SC_Stream* strm, void* buff, size_t size)
{
	size_t readSize;
	size_t restReadSize;
	char*  outPtr = (char*) buff;
	SC_Stream_BufferData* data = (SC_Stream_BufferData*) strm->_strmInfo;
	if (data->size == 0)
	{	/* データなし	*/
		return 0;
	}

	/* 読み取るサイズを設定	*/
	restReadSize = size;
	if (data->size < size)
	{
		restReadSize = data->size;
	}

	/*  0  1  2  3  4  5  6  (data->buffSize = 7)
	 * [x][x][ ][ ][x][x][x]
	 *              |     |
	 *              OFF   END
	 * OFF～ENDまでの範囲の読み込みで事足りるかチェック
	 */
	readSize = data->buffSize - data->off;
	if (readSize <= restReadSize)
	{	/* とりあえず足りないので、ENDまで読み込む	*/
		memcpy(outPtr, &data->buff[data->off], readSize);
		/* 読み込んだ分処理する						*/
		data->off     = 0;
		data->size   -= readSize;
		restReadSize -= readSize;
		outPtr       += readSize;
	}

	if (restReadSize > 0)
	{	/* まだ読み込む必要があるならすべて読み込む	*/
		memcpy(outPtr, &data->buff[data->off], restReadSize);
		data->off  += restReadSize;
		data->size -= restReadSize;
		outPtr     += restReadSize;
	}

	/* サイズを返す	*/
	return (outPtr - (char*) buff);
}


/**
 * メモリに書き込みます。
 *
 * @param strm ストリーム
 * @param buff バッファ
 * @param size サイズ
 * @return 書き出したバッファサイズ
 */
static
int SC_Stream_stream_memoryWrite(SC_Stream* strm, const void* buff, size_t size)
{
	int        tmp;
	size_t     writeSize;
	size_t     restWriteSize;
	const char*           inPtr = (const char*) buff;
	SC_Stream_BufferData* data  = (SC_Stream_BufferData*) strm->_strmInfo;

	/* 書き込むサイズを設定	*/
	restWriteSize = size;
	tmp           = data->buffSize - data->size;	/* 空き容量		*/
	if (restWriteSize >= tmp)
	{	/* 書き込もうとするサイズの方がデカイ=>可能な範囲書き込む	*/
		restWriteSize = tmp;
	}

	/*  0  1  2  3  4  5  6  (data->buffSize = 7)
	 * [ ][ ][x][x][x][ ][ ]
	 *        |     |     |
	 *        OFF   E     END
	 * ENDの位置までの書き込みで事たりるかチェック
	 * Eの次の位置 = data->off + data->size
	 */
	writeSize = tmp - data->off;	/* E以降の空 = 全空容量 - 先頭からの空き */
	if ((writeSize > 0) && (writeSize <= restWriteSize))
	{	/* とりあえずたりないので、ENDの位置まで書き込む	*/
		memcpy(&data->buff[data->off + data->size], inPtr, writeSize);
		/* 書き込んだ分処理する								*/
		data->size    += writeSize;
		restWriteSize -= writeSize;
		inPtr         += writeSize;
	}

	/* ここにくる時点で(E < OFF) のような形になっている
	 *
	 *  0  1  2  3  4  5  6  (data->buffSize = 7)
	 * [x][ ][ ][x][x][x][x]
	 *  |        |        |
	 *  E        OFF      END
	 *
	 * Eの次の位置 = data->off + data->size - data->buffSize
	 *                       3 +          5 - 7
	 */
	if (restWriteSize > 0)
	{	/* まだ書き込む必要なら E の次の位置から書き込む	*/
		tmp = data->off + data->size;
		if (tmp >= data->buffSize)
		{
			tmp -= data->buffSize;
		}
		memcpy(&data->buff[tmp], inPtr, restWriteSize);
		data->size += restWriteSize;
		inPtr      += restWriteSize;
	}

	/* 書き込んだサイズを返す	*/
	return (inPtr - (char*) buff);
}


/**
 * ストリームを close します。
 * メモリストリームの場合、バッファをクリアするのみです。
 *
 * @param strm ストリーム
 * @return true/false (成功/失敗)
 */
static
bool SC_Stream_stream_memoryClose(SC_Stream* strm)
{
	SC_Stream_BufferData* data  = (SC_Stream_BufferData*) strm->_strmInfo;
	data->off  = 0;
	data->size = 0;
	return true;
}


