#include "IDataStream.h"

/**** IDataStream *************************************************************/

IDataStream::IDataStream()
:streamLength(0), streamOffset(0), swapBytes(false)
{

}

IDataStream::~IDataStream()
{

}

/**
 *	Reads and returns an 8-bit value from the stream
 */
UInt8 IDataStream::Read8(void)
{
	UInt8	out;

	ReadBuf(&out, sizeof(UInt8));

	return out;
}

/**
 *	Reads and returns a 16-bit value from the stream
 */
UInt16 IDataStream::Read16(void)
{
	UInt16	out;

	ReadBuf(&out, sizeof(UInt16));

	if(swapBytes)
		out = Swap16(out);

	return out;
}

/**
 *	Reads and returns a 32-bit value from the stream
 */
UInt32 IDataStream::Read32(void)
{
	UInt32	out;

	ReadBuf(&out, sizeof(UInt32));

	if(swapBytes)
		out = Swap32(out);

	return out;
}

/**
 *	Reads and returns a 64-bit value from the stream
 */
UInt64 IDataStream::Read64(void)
{
	UInt64	out;

	ReadBuf(&out, sizeof(UInt64));

	if(swapBytes)
		out = Swap64(out);

	return out;
}

/**
 *	Reads and returns a 32-bit floating point value from the stream
 */
float IDataStream::ReadFloat(void)
{
	UInt32	out = Read32();

	return *((float *)&out);
}

/**
 *	Reads a null-or-return-terminated string from the stream
 *	
 *	If the buffer is too small to hold the entire string, it is truncated and
 *	properly terminated.
 *	
 *	@param buf the output buffer
 *	@param bufLength the size of the output buffer
 *	@return the number of characters written to the buffer
 */
UInt32 IDataStream::ReadString(char * buf, UInt32 bufLength, char altTerminator, char altTerminator2)
{
	char	* traverse = buf;
	bool	breakOnReturns = false;

	if((altTerminator == '\n') || (altTerminator2 == '\n'))
		breakOnReturns = true;

	ASSERT_STR(bufLength > 0, "IDataStream::ReadString: zero-sized buffer");

	if(bufLength == 1)
	{
		buf[0] = 0;
		return 0;
	}

	bufLength--;

	for(UInt32 i = 0; i < bufLength; i++)
	{
		if(HitEOF()) break;

		UInt8	data = Read8();

		if(breakOnReturns)
		{
			if(data == 0x0D)
			{
				if(Peek8() == 0x0A)
					Skip(1);

				break;
			}
		}

		if(!data || (data == altTerminator) || (data == altTerminator2))
		{
			break;
		}

		*traverse++ = data;
	}

	*traverse++ = 0;

	return traverse - buf - 1;
}

/**
 *	Reads and returns an 8-bit value from the stream without advancing the stream's position
 */
UInt8 IDataStream::Peek8(void)
{
	IDataStream_PositionSaver	saver(this);

	return Read8();
}

/**
 *	Reads and returns a 16-bit value from the stream without advancing the stream's position
 */
UInt16 IDataStream::Peek16(void)
{
	IDataStream_PositionSaver	saver(this);

	return Read16();
}

/**
 *	Reads and returns a 32-bit value from the stream without advancing the stream's position
 */
UInt32 IDataStream::Peek32(void)
{
	IDataStream_PositionSaver	saver(this);

	return Read32();
}

/**
 *	Reads and returns a 32-bit value from the stream without advancing the stream's position
 */
UInt64 IDataStream::Peek64(void)
{
	IDataStream_PositionSaver	saver(this);

	return Read64();
}

/**
 *	Reads and returns a 32-bit floating point value from the stream without advancing the stream's position
 */
float IDataStream::PeekFloat(void)
{
	IDataStream_PositionSaver	saver(this);

	return ReadFloat();
}

/**
 *	Reads raw data into a buffer without advancing the stream's position
 */
void IDataStream::PeekBuf(void * buf, UInt32 inLength)
{
	IDataStream_PositionSaver	saver(this);

	ReadBuf(buf, inLength);
}

/**
 *	Skips a specified number of bytes down the stream
 */
void IDataStream::Skip(SInt64 inBytes)
{
	SetOffset(GetOffset() + inBytes);
}

/**
 *	Writes an 8-bit value to the stream.
 */
void IDataStream::Write8(UInt8 inData)
{
	WriteBuf(&inData, sizeof(UInt8));
}

/**
 *	Writes a 16-bit value to the stream.
 */
void IDataStream::Write16(UInt16 inData)
{
	if(swapBytes)
		inData = Swap16(inData);

	WriteBuf(&inData, sizeof(UInt16));
}

/**
 *	Writes a 32-bit value to the stream.
 */
void IDataStream::Write32(UInt32 inData)
{
	if(swapBytes)
		inData = Swap32(inData);

	WriteBuf(&inData, sizeof(UInt32));
}

/**
 *	Writes a 64-bit value to the stream.
 */
void IDataStream::Write64(UInt64 inData)
{
	if(swapBytes)
		inData = Swap64(inData);

	WriteBuf(&inData, sizeof(UInt64));
}

/**
 *	Writes a 32-bit floating point value to the stream.
 */
void IDataStream::WriteFloat(float inData)
{
	if(swapBytes)
	{
		UInt32	temp = *((UInt32 *)&inData);

		temp = Swap32(temp);

		WriteBuf(&temp, sizeof(UInt32));
	}
	else
	{
		WriteBuf(&inData, sizeof(float));
	}
}

/**
 *	Writes a null-terminated string to the stream.
 */
void IDataStream::WriteString(const char * buf)
{
	WriteBuf(buf, std::strlen(buf) + 1);
}

/**
 *	Returns the length of the stream
 */
SInt64 IDataStream::GetLength(void)
{
	return streamLength;
}

/**
 *	Returns the number of bytes remaining in the stream
 */
SInt64 IDataStream::GetRemain(void)
{
	return streamLength - streamOffset;
}

/**
 *	Returns the current offset into the stream
 */
SInt64 IDataStream::GetOffset(void)
{
	return streamOffset;
}

/**
 *	Returns whether we have reached the end of the stream or not
 */
bool IDataStream::HitEOF(void)
{
	return streamOffset >= streamLength;
}

/**
 *	Moves the current offset into the stream
 */
void IDataStream::SetOffset(SInt64 inOffset)
{
	streamOffset = inOffset;
}

/**
 *	Enables or disables byte swapping for basic data transfers
 */
void IDataStream::SwapBytes(bool inSwapBytes)
{
	swapBytes = inSwapBytes;
}

IDataStream * IDataStream::GetRootParent(void)
{
	IDataStream	* parent = GetParent();

	if(parent)
		return parent->GetRootParent();
	else
		return this;
}

void IDataStream::CopyStreams(IDataStream * out, IDataStream * in, UInt64 bufferSize, UInt8 * buf)
{
	in->Rewind();

	bool	ourBuffer = false;

	if(!buf)
	{
		buf = new UInt8[bufferSize];
		ourBuffer = true;
	}

	UInt64	remain = in->GetLength();

	while(remain > 0)
	{
		UInt64	transferSize = remain;

		if(transferSize > bufferSize)
			transferSize = bufferSize;

		in->ReadBuf(buf, transferSize);
		out->WriteBuf(buf, transferSize);

		remain -= transferSize;
	}

	if(ourBuffer)
		delete [] buf;
}

void IDataStream::CopySubStreams(IDataStream * out, IDataStream * in, UInt64 remain, UInt64 bufferSize, UInt8 * buf)
{
	bool	ourBuffer = false;

	if(!buf)
	{
		buf = new UInt8[bufferSize];
		ourBuffer = true;
	}

	while(remain > 0)
	{
		UInt64	transferSize = remain;

		if(transferSize > bufferSize)
			transferSize = bufferSize;

		in->ReadBuf(buf, transferSize);
		out->WriteBuf(buf, transferSize);

		remain -= transferSize;
	}

	if(ourBuffer)
		delete [] buf;
}

/**** IDataStream_PositionSaver ***********************************************/

/**
 *	The constructor; save the stream's position
 */
IDataStream_PositionSaver::IDataStream_PositionSaver(IDataStream * tgt)
{
	stream = tgt;
	offset = tgt->GetOffset();
}

/**
 *	The destructor; restore the stream's saved position
 */
IDataStream_PositionSaver::~IDataStream_PositionSaver()
{
	stream->SetOffset(offset);
}

/**** IDataSubStream **********************************************************/

IDataSubStream::IDataSubStream()
:stream(NULL), subBase(0)
{
	//
}

IDataSubStream::IDataSubStream(IDataStream * inStream, SInt64 inOffset, SInt64 inLength)
{
	stream = inStream;
	subBase = inOffset;
	streamLength = inLength;

	stream->SetOffset(inOffset);
}

IDataSubStream::~IDataSubStream()
{

}

void IDataSubStream::Attach(IDataStream * inStream, SInt64 inOffset, SInt64 inLength)
{
	stream = inStream;
	subBase = inOffset;
	streamLength = inLength;

	stream->SetOffset(inOffset);
}

void IDataSubStream::ReadBuf(void * buf, UInt32 inLength)
{
	ASSERT_STR(inLength <= GetRemain(), "IDataSubStream::ReadBuf: hit eof");

	if(stream->GetOffset() != subBase + streamOffset)
		stream->SetOffset(subBase + streamOffset);

	stream->ReadBuf(buf, inLength);

	streamOffset += inLength;
}

void IDataSubStream::WriteBuf(const void * buf, UInt32 inLength)
{
	if(stream->GetOffset() != subBase + streamOffset)
		stream->SetOffset(subBase + streamOffset);

	stream->WriteBuf(buf, inLength);

	streamOffset += inLength;

	if(streamLength < streamOffset)
		streamLength = streamOffset;
}

void IDataSubStream::SetOffset(SInt64 inOffset)
{
	stream->SetOffset(subBase + inOffset);
	streamOffset = inOffset;
}