#include <crtdbg.h>
#include "memstrm.h"

//#############################################################################
//##########################    MemoryStream    ###############################
//#############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MemoryStream::MemoryStream(void *stream_start, size_t stream_size, size_t initial_offset)
{
	streamStart = (char*)stream_start;
	streamSize = stream_size;
	
	Verify(initial_offset <= stream_size);
	currentPosition = streamStart + initial_offset;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MemoryStream& MemoryStream::ReadBytes(void *ptr, size_t number_of_bytes)
{
	Verify(GetBytesRemaining() >= number_of_bytes);

	memcpy(ptr, GetPointer(), number_of_bytes);
	AdvancePointer(number_of_bytes);
	return *this;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MemoryStream& MemoryStream::WriteBytes(const void *ptr, size_t number_of_bytes)
{
	Verify(number_of_bytes > 0);
	Verify(number_of_bytes <= GetBytesRemaining());
	
	memcpy(GetPointer(), ptr, number_of_bytes);
	AdvancePointer(number_of_bytes);
	return *this;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MemoryStream& MemoryStream_Write(MemoryStream *stream, std::istream *input)
{
	Verify(!input->bad());
	input->clear();

	//
	//--------------------------------------------------------
	// If we are reading a binary stream, read it in one chunk
	//--------------------------------------------------------
	//
	if (input->flags() & std::ios::binary)
	{
		input->seekg(0, std::ios::end);
		int len = input->tellg();
		input->clear();
		input->seekg(0);

		if (!stream->AllocateBytes(len))
			return *stream;
		
		input->read((char*)stream->GetPointer(), len);
		stream->AdvancePointer(len);
	}

	//
	//----------------------------------------------------------------
	// Read a text file character by character if we are a DOS machine
	//----------------------------------------------------------------
	//
	else
	{
		input->clear();
		input->seekg(0);
		unsigned char byte;

		while (input->get((char*)&byte, 1))
			*stream << byte;
	}

	return *stream;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MemoryStream& MemoryStream_Write(MemoryStream *stream, const MemoryStream *input_stream)
{
	return stream->WriteBytes(input_stream->GetPointer(), input_stream->GetBytesRemaining());
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool MemoryStream::TestInstance() const
{
	return size_t(currentPosition - streamStart) <= streamSize;
}

//#############################################################################
//######################    DynamicMemoryStream    ############################
//#############################################################################

//
// calculate the allocation size for stream
//
inline size_t Calculate_Buffer_Size(size_t needed)
{
	return (needed + 0x80) & ~0x7F;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DynamicMemoryStream::DynamicMemoryStream(size_t stream_size)
	: MemoryStream(NULL, stream_size)
{
	streamSize = Calculate_Buffer_Size(stream_size);
	streamStart = new char[streamSize];
	
	currentPosition = streamStart;
	streamLength = 0L;
	ownsStream = true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DynamicMemoryStream::DynamicMemoryStream(void *stream_start, size_t stream_size, size_t initial_offset)
	: MemoryStream(stream_start, stream_size, initial_offset)
{
	ownsStream = false;
	streamLength = stream_size;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DynamicMemoryStream::~DynamicMemoryStream()
{
	if (ownsStream)
		delete[] streamStart;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool DynamicMemoryStream::AllocateBytes(size_t count)
{
	if (ownsStream && MemoryStream::GetBytesRemaining() < count)
	{
		size_t current_offset = currentPosition - streamStart;
		size_t new_stream_size = streamSize + count - MemoryStream::GetBytesRemaining() + 1;
		new_stream_size = Calculate_Buffer_Size(new_stream_size);
		char *new_stream_start = new char[new_stream_size];

		memcpy(new_stream_start, streamStart, streamSize);
		delete[] streamStart;
		streamStart = new_stream_start;
		streamSize = new_stream_size;
		currentPosition = streamStart + current_offset;
	}
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MemoryStream& DynamicMemoryStream::WriteBytes(const void *ptr, size_t number_of_bytes)
{
	Verify(number_of_bytes > 0);
	
	//
	//--------------------------------------------------------------------
	// If we own the stream, and we don't have enough memory to accept the
	// write, copy the buffer and make a new stream that is bigger
	//--------------------------------------------------------------------
	//
	AllocateBytes(number_of_bytes);
	memcpy(GetPointer(), ptr, number_of_bytes);
	AdvancePointer(number_of_bytes);

	streamLength = __max(streamLength, GetBytesUsed());
	return *this;
}