/* vim: ts=4 sw=4 sts=4 ff=unix fenc=utf-8 :
 * =====================================================================
 *  sc_stream.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 <sc_os.h>
#include <sc_string.h>
#include <sc_error.h>
#include <sc_mmgr.h>
#include <sc_stream.h>

/* ---------------------------------------------------------------------
 *  プロトタイプ宣言
 * ---------------------------------------------------------------------
 */
static bool SC_Stream_fullLoad(SC_Stream* strm);


/**
 * ストリームより1文字読み出します。
 * ファイルの末尾に達した場合 0 を返します。
 * エラーが発生した場合、負の値を返します。
 *
 * @param strm ストリーム
 * @param c    読み出すバッファ
 * @return 読み出したサイズ
 */
int SC_Stream_readc_(SC_Stream* strm, char* c)
{
	int readSize;
	if (strm->_data.size <= 0)
	{	/* データがない => 読み込む	*/
		readSize =strm->_readCore(
				strm, strm->_data.buff, strm->_data.buffSize);
		if (readSize <= 0)
		{	/* 読み込み失敗 or 末尾到達	*/
			return readSize;
		}
		strm->_data.off  = 0;
		strm->_data.size = readSize;
	}

	*c = strm->_data.buff[strm->_data.off];
	strm->_data.off++;
	strm->_data.size--;
	return 1;
}


/**
 * ストリームより読み取ります。
 * ファイルの末尾に達した場合 0 を返します。
 * エラーが発生した場合、負の値を返します。
 *
 * @param strm ストリーム
 * @param buff バッファ
 * @param size サイズ
 * @return 読み取ったバイト数
 */
int SC_Stream_read_(SC_Stream* strm, void* buff, size_t size)
{
	int ret;
	int readSize = 0;
	int copySize = (int) size;

	if (strm->_data.size > 0)
	{	/* バッファがある場合、バッファをコピー	*/
		if (copySize > strm->_data.size)
		{
			copySize = strm->_data.size;
		}
		memcpy(buff, &(strm->_data.buff[strm->_data.off]), copySize);
		readSize          = copySize;
		strm->_data.off  += readSize;
		strm->_data.size -= readSize;
	}

	copySize = size - readSize;
	if (copySize > 0)
	{	/* 残りを _readCore により取得	*/
		ret = strm->_readCore(strm, &((char*)buff)[readSize], copySize);
		if (ret <= 0)
		{
			return ret;
		}
		readSize += ret;
	}
	return readSize;
}


/**
 * 指定されたバイトを読み取ります。
 * 指定されたバイト数分読み取れたか否かは、
 * 戻り値にて確認してください。
 * ファイルの末尾に達した場合 0 を返します。
 * エラーが発生した場合、負の値を返します。
 *
 * @param strm ストリーム
 * @param buff バッファ
 * @param size サイズ
 * @return 実際に読み取ったバイト数
 */
int SC_Stream_readExact_(SC_Stream* strm, void* buff, size_t size)
{
	char* readPtr  = (char*) buff;
	int   restSize = size;
	int   readSize;
	for (;;)
	{
		if (restSize == 0)
		{	/* サイズ分読み込んだ	*/
			break;
		}

		readSize = strm->read(strm, readPtr, restSize);
		if (readSize == 0)
		{	/* 終わりまで読み込んだ	*/
			break;
		}
		if (readSize < 0)
		{	/* エラー発生			*/
			return -1;
		}

		readPtr  += readSize;
		restSize -= readSize;
	}
	return (size - restSize);
}


/**
 * ストリームより、1行を読み込みます。
 * ストリームの終わりの場合、0 を返します。
 * バッファの最後には、'\0' が付与されます。
 * 読み取ったサイズに '\0' は含まれません。
 *
 * @param strm ストリーム
 * @param buff バッファ
 * @param size バッファサイズ
 * @return 読み取ったサイズ
 */
int SC_Stream_readLine_(SC_Stream* strm, char* buff, size_t size)
{
	int   i;
	int   ret;
	char  c;
	char* ptr = buff;

	for (i = 0; i < ((int) size - 1); i++)
	{
		ret = strm->readc(strm, &c);
		if (ret <= 0) { break; }

		*ptr = c;
		ptr++;
		if (c == '\n')
		{	/* LF	*/
			i++;
			break;
		}
		if (c == '\r')
		{	/* CR	*/
			i++;
			ret = strm->readc(strm, &c);
			if (ret <= 0) { break; }
			if (c == '\n')
			{	/* CRLF	*/
				*ptr = c;
				ptr++;
				i++;
			}
			else
			{	/* push back	*/
				strm->_data.off--;
				strm->_data.size++;
			}
			break;
		}

	}

	if (ret < 0)
	{
		return ret;
	}
	*ptr = '\0';
	return i;
}


/**
 * ストリームより、指定されたデータの直前までのデータを読み込みます。
 * ストリームの終わり、指定されたデータの直前に達した場合、0 を返します。
 * 指定されたデータのサイズが、バッファサイズより大きい場合失敗します。
 *
 * @param strm ストリーム
 * @param buff バッファ
 * @param size サイズ
 * @param limitData ここで指定されたデータまで読み込む
 * @param limitSize limitData のサイズ
 * @return 実際に読み取ったバイト数
 */
int SC_Stream_limitedRead_(SC_Stream* strm,
	char* buff     , size_t size,
	char* limitData, size_t limitSize
	) 
{
	bool        ret;
	const char* limitDataPtr;
	int         readSize = size;
	int         tmpSize;

	if (((int) limitSize * 2) > strm->_data.buffSize)
	{
		return -1;
	}

	/* とりあえず、バッファいっぱいにロードする。	*/
	ret = SC_Stream_fullLoad(strm);
	if (!ret) {	return -1;	}

	/* limitData を検索								*/
	limitDataPtr = SC_memindex(
			strm->_data.buff, strm->_data.size,
			limitData, limitSize);

	if (limitDataPtr == NULL)
	{	/* 見つからない => 読み込みサイズを制限する
		   中途半端な位置で切れてる可能性があるため
		   読み込み終了位置を制限する */
		tmpSize = strm->_data.size - limitSize;
		if (readSize > tmpSize)
		{
			readSize = tmpSize;
		}
	}
	else
	{	/* 見つかった => 最大でも見つかった位置の直前までを返す */
		tmpSize = limitDataPtr - strm->_data.buff;
		if (readSize > tmpSize)
		{
			readSize = tmpSize;
		}
	}

	if (readSize > 0)
	{
		readSize = strm->read(strm, buff, readSize);
	}
	return readSize;
}



/**
 * ストリームに1文字書き出します。
 * ファイルの末尾に達した場合 0 を返します。
 * エラーが発生した場合、負の値を返します。
 *
 * @param strm ストリーム
 * @param c    書き出す値
 * @return true/false (成功/失敗)
 */
int SC_Stream_writec_(SC_Stream* strm, char c)
{
	int ret;
	ret = strm->_writeCore(strm, &c, 1); 
	return ret;
}


/**
 * ストリームに書き出します。
 * ファイルの末尾に達した場合 0 を返します。
 * エラーが発生した場合、負の値を返します。
 *
 * @param strm ストリーム
 * @param buff バッファ
 * @param size サイズ
 * @return 書き出したバイト数
 */
int SC_Stream_write_(SC_Stream* strm, const void* buff, size_t size)
{
	int ret;
	ret = strm->_writeCore(strm, buff, size); 
	return ret;
}


/**
 * 指定されたバイトを書き込みます。
 * ファイルの末尾に達した場合 0 を返します。
 * エラーが発生した場合、負の値を返します。
 *
 * @param stram ストリーム
 * @param buff  バッファ
 * @param size  サイズ
 * @return 書き込んだバイト数
 */
int SC_Stream_writeExact_(SC_Stream* strm, const void* buff, size_t size)
{
	const char*  writePtr = (const char*) buff;
	int restSize = (int) size;
	int writeSize;
	for (;;)
	{
		if (restSize == 0)
		{	/* サイズ分書き込んだ	*/
			break;
		}

		writeSize = strm->write(strm, writePtr, restSize);
		if (writeSize == 0)
		{	/* 0 byte 書き込んだ	*/
			break;
		}
		if (writeSize < 0)
		{	/* エラーが発生した		*/
			return -1;
		}

		writePtr += writeSize;
		restSize -= writeSize;
	}
	return (size - restSize);
}


/**
 * 指定されたストリームを削除します。
 *
 * @param stram ストリーム
 */
void SC_Stream_delete(SC_Stream* strm)
{
	strm->close(strm);
	free(strm);
}


/**
 * 指定されたストリームのバッファにロードします。
 */
static
bool SC_Stream_fullLoad(SC_Stream* strm)
{
	int size;

	/* データを前詰めする	*/
	if (strm->_data.size > 0)
	{
		memmove(strm->_data.buff,
			&(strm->_data.buff[strm->_data.off]),
			strm->_data.size);
		strm->_data.off = 0;
	}

	/* バッファにロードする	*/
	size = strm->_readCore(strm,
			&(strm->_data.buff[strm->_data.size]),
			strm->_data.buffSize - strm->_data.size);
	if (size < 0)
	{
		return false;
	}
	strm->_data.size += size;
	return true;
}

