// $Id: StretchyBuffer.cpp 8019 2007-07-14 22:30:11Z trick $

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Deadly Rooms of Death.
 *
 * The Initial Developer of the Original Code is
 * Caravel Software.
 * Portions created by the Initial Developer are Copyright (C) 1995, 1996, 
 * 1997, 2000, 2001, 2002 Caravel Software. All Rights Reserved.
 *
 * Contributor(s):
 *
 * ***** END LICENSE BLOCK ***** */

//StretchyBuffer.cpp.
//Implementation of CStretchyBuffer.

#include <zlib.h> //must be first
#include "StretchyBuffer.h"
#include "Assert.h"
#include "Types.h"

#include <string.h>

//
//Public methods.
//

//****************************************************************************************
CStretchyBuffer::CStretchyBuffer()
	: pBuf(NULL)
//Constructor.
{
	Clear();
}

//****************************************************************************************
CStretchyBuffer::CStretchyBuffer(
//Constructor that initializes buffer.
//
//Params:
	const CStretchyBuffer &that)  //(in) Buffer that will be copied to memory alloced by this object.
	: pBuf(NULL)
{
	Clear();
	if (that.Size())
		Set(that.pBuf, that.Size());
}

//****************************************************************************************
CStretchyBuffer::CStretchyBuffer(const string& str)
//Constructor that initializes buffer.
	: pBuf(NULL)
{
	Clear();
	Set((const BYTE*)str.c_str(), str.size());
}

//****************************************************************************************
CStretchyBuffer::CStretchyBuffer(
//Constructor that initializes buffer.
//
//Params:
	const BYTE *pSetBuf, //(in) Buffer that will be copied to memory alloced by this object.
	const DWORD dwSetBufSize)     //(in) Size of buffer in bytes.
	: pBuf(NULL)
{
	Clear();
	Set(pSetBuf, dwSetBufSize);
}

//****************************************************************************************
void CStretchyBuffer::Clear()
//Zeros member vars and frees resources associated with this object.
{
	delete[] this->pBuf;
	this->pBuf = NULL;

	this->dwBufSize = this->dwAllocSize = 0L;
}

//******************************************************************************
void CStretchyBuffer::Encode(UCHAR code)
//Encodes/decodes data in the buffer.
//
//Params:
{
	BYTE *pBuf = this->pBuf;
	for (UINT nIndex=Size(); nIndex--; )
	{
		*pBuf = *pBuf ^ code;   //flip requested bits
		++pBuf;
	}
}

//****************************************************************************************
BYTE *CStretchyBuffer::GetCopy()
//Gets a copy of this object's buffer that this object will not delete or change.
//
//Returns:
//Pointer to new buffer or NULL.
{
	if (this->dwBufSize == 0) return NULL; //Buffer is empty.

	//Alloc new buffer.
	BYTE *pCopyBuf = new BYTE[this->dwBufSize];
	if (!pCopyBuf) return NULL;

	//Copy bytes into new buffer.
	memcpy(pCopyBuf, this->pBuf, this->dwBufSize);

	return pCopyBuf;
}

//****************************************************************************************
void CStretchyBuffer::Set(
//Sets object's buffer to contain same bytes as specified buffer.
//
//Params:
	const BYTE *pSetBuf, //(in) Buffer to copy.
	DWORD dwSetBufSize)  //(in) Size of buffer.
{
	ASSERT(dwSetBufSize > 0);
	
	//Make sure object buffer is large enough.
	if (dwSetBufSize > this->dwAllocSize)
	{
		if (!Alloc(dwSetBufSize)) {ASSERTP(false, "Allocation failure."); return;}
	}

	//Copy buffer to object buffer.
	memcpy(this->pBuf, pSetBuf, dwSetBufSize);
	this->dwBufSize = dwSetBufSize;
}

//****************************************************************************************
void CStretchyBuffer::SetSize(
//back door, when pBuf is written to directly
//
//Params:
	const DWORD dwSetBufSize)  //(in)
{
	ASSERT(this->dwAllocSize >= this->dwBufSize);
	this->dwBufSize = dwSetBufSize;
}

//****************************************************************************************
void CStretchyBuffer::Append(
//Appends a buffer to the object's buffer.
//
//Params:
	const BYTE *pAddBuf, //(in) Buffer to append.
	DWORD dwAddBufSize)   //(in) Size of buffer.
{
	ASSERT(dwAddBufSize > 0);

	//Make sure object buffer is large enough.
	if (this->dwBufSize + dwAddBufSize > this->dwAllocSize)
	{
		//Grow buffer by 2.5x each expansion for best general efficiency
		if (!Realloc(static_cast<DWORD>((this->dwBufSize + dwAddBufSize) * 2.5)))
				{ASSERTP(false, "Allocation failure."); return;}
	}

	//Copy buffer to end of object buffer.
	memcpy(this->pBuf + this->dwBufSize, pAddBuf, dwAddBufSize);
	this->dwBufSize += dwAddBufSize;
}

//****************************************************************************************
bool CStretchyBuffer::Alloc(
//Allocs the buffer.  
//
//Only call Alloc() if you wish to copy data into the buffer directly (not using 
//class methods) and the buffer must be preallocated to a specific size.
//
//Params:
	DWORD dwNewAllocSize) //(in) Size to allocate buffer to.
//
//Returns:
//True if successful, false if not.
{
	ASSERT(dwNewAllocSize > 0);

	if (dwNewAllocSize == this->dwAllocSize) return true; //Nothing to do.

	//Delete existing buffer.
	delete[] this->pBuf;
	this->dwAllocSize = this->dwBufSize = 0;

	//Alloc new buffer and set related member vars.
	this->pBuf = new BYTE[dwNewAllocSize];
	if (!this->pBuf) return false;
	this->dwAllocSize = dwNewAllocSize;

	return true;
}

//****************************************************************************************
bool CStretchyBuffer::Realloc(
//Reallocates the buffer to a new size and containing whatever is in the existing buffer.
//
//Only call Alloc() if you wish to copy data into the buffer directly (not using 
//class methods) and the buffer must be preallocated to a specific size.
//
//Params:
	DWORD dwNewAllocSize) //(in) Size to reallocate buffer to.
//
//Returns:
//True if successful, false if not.
{
	BYTE *pNewBuf = NULL;
	DWORD dwCopySize;

	ASSERT(dwNewAllocSize > 0);

	if (dwNewAllocSize == this->dwAllocSize) return true; //Nothing to do.

	//Create new buffer.
	pNewBuf = new BYTE[dwNewAllocSize];
	if (!pNewBuf) 
	{
		Clear(); 
		return false;
	}

	//Copy bytes from old buffer into new buffer.  Data will be truncated if
	//it all can't fit.
	dwCopySize = this->dwBufSize;
	if (dwCopySize > 0)
	{
		if (dwCopySize > dwNewAllocSize) dwCopySize = dwNewAllocSize;
		ASSERT(this->pBuf);
		memcpy(pNewBuf, this->pBuf, dwCopySize);
	}
	
	//Delete old buffer.
	delete[] this->pBuf;

	//Set member vars for new buffer.
	this->pBuf = pNewBuf;
	this->dwAllocSize = dwNewAllocSize;
	this->dwBufSize = dwCopySize;

	return true;
}

//****************************************************************************************
bool CStretchyBuffer::Compress(
//Compress the data.
//
//Returns: whether compression succeeded
//
//Params:
	UCHAR* &encodedBuf, ULONG &encodedSize)   //(out)
{
	const ULONG srcLen = Size();
	uLongf zencodedSize = (ULONG) (1.01 * srcLen) + 13;
	encodedBuf = new UCHAR[zencodedSize];
	const int res = compress(encodedBuf, &zencodedSize, (const UCHAR*)(*this), srcLen);
	encodedSize = zencodedSize;
	return res == Z_OK;
}

//****************************************************************************************
bool CStretchyBuffer::Uncompress(
//Uncompress the compressed buffer.
//
//Returns: whether Uncompression succeeded
//
//Params:
	UCHAR* &decodedBuf, ULONG &decodedSize)   //(out)
{
	const DWORD fileSize = Size();
	uLongf zdecodedSize = fileSize * (fileSize > 250000 ? 4 : 15);   //about as big as the uncompressed data will ever be
	decodedBuf = new UCHAR[zdecodedSize];
	if (!decodedBuf) return false;
	int res;
	do {
		res = uncompress(decodedBuf, &zdecodedSize, (BYTE*)(*this), fileSize);
		switch (res)
		{
			case Z_BUF_ERROR:
				//This wasn't enough memory to decode the data to,
				//so double the buffer size.
				zdecodedSize *= 2;
				delete[] decodedBuf;
				decodedBuf = new UCHAR[zdecodedSize+1]; //allow adding null-terminating char to decodedBuf, if desired
				if (!decodedBuf) return false;
				break;

			case Z_DATA_ERROR:
				ASSERT(false);
				return false;

			case Z_MEM_ERROR:
				ASSERT(false);
				return false;
		}
	} while (res != Z_OK);  //success

	//Upon successful completion, decodedSize is the actual size of the uncompressed buffer.
	decodedSize = zdecodedSize;
	return true;
}


//****************************************************************************************
bool CStretchyBuffer::RemoveBytes(
//Remove the selected bytes from the buffer.
//
//Returns: false if the range is invalid, true otherwise
//
//Params:
ULONG from, ULONG to)   //(in)
{
	ASSERT(to > from);
	const DWORD dwSize = Size();
	if ((long)from < 0)
		from += dwSize;
	if ((long)to < 0)
		to += dwSize;

	if (((long)from < 0) || ((long)to < 0) || (from > dwSize-1) || (to > dwSize)) return false;

	memmove(this->pBuf + from, this->pBuf + to, dwSize - from);
	this->dwBufSize -= to-from;
	return true;
}


//****************************************************************************************
CStretchyBuffer &CStretchyBuffer::operator += (
//Concatenate a DWORD to this CStretchyBuffer
//
//Returns: *this
//
//Params:
DWORD dwAdd)  //(in)
{
	BYTE b;
	b = (dwAdd & 0xff); Append(&b, 1);
	b = (dwAdd >> 8 & 0xff); Append(&b, 1);
	b = (dwAdd >> 16 & 0xff); Append(&b, 1);
	b = (dwAdd >> 24 & 0xff); Append(&b, 1);
	return *this;
}

