/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: CHXFlatArray.cpp,v 1.4.2.3 2004/07/09 01:49:47 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include "CHXFlatArray.h"
#include "CHXClientDebug.h"
#include "hlxclib/stdlib.h"
#include "hlxclib/memory.h"

CHXFlatArray::CHXFlatArray(UINT32 recordSize)
	: mRecordSize(recordSize)
	, mCount(0)
	, mpData(NULL)
	, mDataSize(0)
{
	CHXASSERT(mRecordSize < 0x10000L);	// we're probably being instantiated badly if this happens
	CHXASSERT(mRecordSize > 0);
}

CHXFlatArray::CHXFlatArray(UINT32 recordSize, const void* firstItemPtr)
	: mRecordSize(recordSize)
	, mCount(0)
	, mpData(NULL)
	, mDataSize(0)
{
	SetToDataAt(firstItemPtr, recordSize);
}

CHXFlatArray::CHXFlatArray(const CHXFlatArray& sourceArray)
{
	mpData = NULL;
	mDataSize = 0;
	*this = sourceArray;
}

CHXFlatArray& CHXFlatArray::operator=(const CHXFlatArray& sourceArray)
{
	// do nothing if we're copying ourselves
	if (this == &sourceArray) return *this;
	
	mRecordSize = sourceArray.mRecordSize;
	if (InternalReplaceAllData(sourceArray.mpData, sourceArray.mDataSize))
	{
		mCount = sourceArray.mCount;
	}
	else
	{
		mCount = 0;
	}
	return *this;
}

CHXFlatArray& CHXFlatArray::operator+=(const CHXFlatArray& array2)
{
	CHXASSERT(mRecordSize == array2.mRecordSize || mRecordSize == 0);
	
	if (mRecordSize == array2.mRecordSize || mRecordSize == 0)
	{
		if (mCount == 0 || mpData == NULL)
		{
			*this = array2;
		}
		else 
		{
			if (InternalAppendData(array2.mpData, array2.mDataSize))
			{
				mCount += array2.mCount;
			}
		}
	}
	return *this;
}

CHXFlatArray::~CHXFlatArray()
{
	if (mpData)
	{
		free(mpData);
		mDataSize = 0;
	}
}

UINT32 CHXFlatArray::CopyDataTo(void *pOutBuff, UINT32 buffSize) const
{
	UINT32 bytesToCopy;
	
	bytesToCopy = mDataSize;
	if (bytesToCopy > buffSize)
	{
		bytesToCopy = buffSize;
	}
	
	if (mpData && bytesToCopy)
	{
		memcpy(pOutBuff, mpData, bytesToCopy);
	}
	return bytesToCopy;
}

bool CHXFlatArray::SetToDataAt(const void *pBuff, UINT32 buffSize)
{
	if(mRecordSize <= 0) return false;
	
	if(buffSize % mRecordSize != 0) return false;

	if (InternalReplaceAllData(pBuff, buffSize))
	{
		mCount = buffSize / mRecordSize;
		return true;
	}
	else
	{
		mCount = 0;
		return false;
	}
}

bool CHXFlatArray::SetAt(UINT32 recordNum, const void *itemPtr)
{
	if(mRecordSize <= 0) return false;	// forgot to set record size for array

	UINT32 newRecordCount = recordNum + 1;
	
	if (newRecordCount > mCount)
	{
		// allocate more slots for records
		UINT32 oldSize, newSize;
		
		newSize = mRecordSize * newRecordCount;
		oldSize = mRecordSize * mCount;
		
		CHXASSERT(oldSize == mDataSize);
		
		if (!InternalAppendData(NULL, newSize - oldSize)) return false;

		// zero out the new slots
		memset(oldSize + mpData, 0, newSize - oldSize);
		mCount = newRecordCount;
	}
	
	if (itemPtr != NULL)
	{
		memcpy((recordNum * mRecordSize) + mpData, itemPtr, mRecordSize);
	}
	return true;
}

bool CHXFlatArray::GetAt(UINT32 recordNum, void *outItemPtr) const
{
	if(mRecordSize <= 0) return false;	// forgot to set record size for array

	if(recordNum >= mCount) return false;
	
	UINT8* recordPtr = mpData + (mRecordSize * recordNum);
	
	if (outItemPtr)
	{
		memcpy(outItemPtr, recordPtr, mRecordSize);
	}
	return true;
}

bool CHXFlatArray::InsertBefore(UINT32 recordNum, const void *itemPtr)
{
	// parameter should be between 0 and mCount (since mCount is 1 greater than the max valid index)
	if(recordNum > mCount) return false;
	
	UINT32 recordOffset = mRecordSize * recordNum;
	
	if (InternalInsertData(recordOffset, itemPtr, mRecordSize))
	{
		mCount++;
		return true;
	}
	return false;
}

bool CHXFlatArray::Remove(UINT32 recordNum)
{
	// parameter should be between 0 and mCount (since mCount is 1 greater than the max valid index)
	if(recordNum >= mCount) return false;
	
	// move all records down
	
	UINT32 recordDataOffset = mRecordSize * recordNum;
	
	if (InternalDeleteData(recordDataOffset, mRecordSize))
	{
		mCount--;
		return true;
	}
	return false;
}

void CHXFlatArray::RemoveAll()
{
	InternalDeleteAllData();
	mCount = 0;
}

bool CHXFlatArray::SetCount(UINT32 numRecords)
{
	if(mRecordSize <= 0) return false;	// forgot to set record size for array

	if (numRecords == mCount) return true;
		
	// if we need to lengthen the array, use SetAt
	if (numRecords > mCount) return SetAt(numRecords - 1, NULL);
	
	// shorten the array
	if (InternalTruncateData((mCount - numRecords) * mRecordSize))
	{
		mCount = numRecords;
		return true;
	}
	return false;
}

bool CHXFlatArray::FindRecord(const void *matchPtr, AreElementsEqualProcPtr AreElementsEqual,
							  UINT32 startAtRecordNum, UINT32 *outFoundRecordNum, void *outFoundRecordPtr) const
{
	UINT32 idx;
	for (idx = startAtRecordNum; idx < mCount; idx++)
	{
		UINT8* recordPtr = mpData + (mRecordSize * idx);

		if (AreElementsEqual(recordPtr, matchPtr))
		{
			if (outFoundRecordNum)
			{
				*outFoundRecordNum = idx;
			}
			if (outFoundRecordPtr)
			{
				memcpy(outFoundRecordPtr, recordPtr, mRecordSize);
			}
			return true;
		}
	}
	return false;
}

bool CHXFlatArray::DiffRecords(AreElementsEqualProcPtr AreElementsEqual, const CHXFlatArray& recordDataToSubtract)
{
	if(mRecordSize <= 0 || mRecordSize != recordDataToSubtract.mRecordSize) return false;

	for (int idx = mCount - 1; idx >= 0; idx--)
	{
		UINT8* recordPtr = mpData + (mRecordSize * idx);
		if (recordDataToSubtract.HasRecord(recordPtr, AreElementsEqual))
		{
			Remove(idx);
		}
	}
	return true;
}

bool CHXFlatArray::SectRecords(AreElementsEqualProcPtr AreElementsEqual, const CHXFlatArray& recordDataToIntersect)
{
	if(mRecordSize <= 0 || mRecordSize != recordDataToIntersect.mRecordSize) return false;
	
	for (int idx = mCount - 1; idx >= 0; idx--)
	{
		UINT8* recordPtr = mpData + (mRecordSize * idx);
		if (!recordDataToIntersect.HasRecord(recordPtr, AreElementsEqual))
		{
			Remove(idx);
		}
	}
	return true;
}

// the internal routines that follow do not adjust record counts at all; routines that
// call these should manage records

bool CHXFlatArray::InternalReplaceAllData(const void *pReplacementData, UINT32 replacementDataSize)
{
	// get rid of old data
	if (mpData)
	{
		free(mpData);
		mpData = NULL;
		mDataSize = 0;
	}
	if (!replacementDataSize) return true;

	mpData = (UINT8*)malloc(replacementDataSize);
	if (!mpData) return false;

	// copy the data into a new allocation
	mDataSize = replacementDataSize;
	memcpy(mpData, pReplacementData, mDataSize);

	return true;
}

bool CHXFlatArray::InternalInsertData(UINT32 insertBeforeOffset, const void *pInsertData, UINT32 insertDataSize)
{
	// insertBeforeOffset is 0-based; max legal value is mDataSize, which inserts at end of all
	// data (though it's easier to use InternalAppendData for that)
	//
	// It is valid for pInsertData to be NULL, meaning don't initialize the inserted data space
	
	if(insertBeforeOffset > mDataSize) return false;
	
	if (insertDataSize == 0) return true;

	UINT8* pNewData = (UINT8*)malloc(mDataSize + insertDataSize);
	CHXASSERT(pNewData != NULL);
	if (!pNewData ) return false;

	if (mpData != NULL && insertBeforeOffset > 0)
	{
		memcpy(pNewData, mpData, insertBeforeOffset);
	}
	
	if (pInsertData)
	{
		memcpy(pNewData + insertBeforeOffset, pInsertData, insertDataSize);
	}
	
	if (mpData != NULL && insertBeforeOffset < mDataSize)
	{
		memcpy(pNewData + insertBeforeOffset + insertDataSize,
			mpData + insertBeforeOffset, mDataSize - insertBeforeOffset);
	}
	
	if (mpData) free(mpData);
	
	mpData = pNewData;
	mDataSize += insertDataSize;
		
	return true;
}

bool CHXFlatArray::InternalDeleteData(UINT32 deleteAtOffset, UINT32 deleteDataSize)
{
	// deleteAtOffset is 0-based; max legal value is mDataSize - 1
	
	if (mDataSize == 0 && deleteDataSize == 0) return true;
	
	if(deleteAtOffset > (mDataSize - 1)) return false;
	if((deleteAtOffset + deleteDataSize) > mDataSize) return false;
	
	if (deleteDataSize == mDataSize)
	{
		CHXASSERT(deleteAtOffset == 0);
		
		free(mpData);
		mpData = NULL;
		mDataSize = 0;
		
		return true;
	}
	else if (deleteDataSize > 0)
	{
		UINT8* pNewData = (UINT8*)malloc(mDataSize - deleteDataSize);
		CHXASSERT(pNewData != NULL);
		
		if (pNewData && mpData != NULL)
		{
			if (deleteAtOffset > 0)
			{
				memcpy(pNewData, mpData, deleteAtOffset);
			}
			
			if (deleteAtOffset < mDataSize - 1)
			{
				memcpy(pNewData + deleteAtOffset,
					mpData + deleteAtOffset + deleteDataSize, 
					mDataSize - (deleteAtOffset + deleteDataSize));
			}
			
			free(mpData);
			
			mpData = pNewData;
			mDataSize -= deleteDataSize;
			
			return true;
		}
	}
	return false;
}

//
// The remaining routines are independent of data storage method
//

bool CHXFlatArray::SetRecordSize(UINT32 recordSize)
{
	CHXASSERT(mRecordSize == 0 || mRecordSize == recordSize);
	
	mRecordSize = recordSize;
	RemoveAll();
	
	return true;
}

bool CHXFlatArray::Push(const void *itemPtr)
{
	return SetAt(mCount, itemPtr);
}

bool CHXFlatArray::Pop(void *itemPtr)
{
	if (GetAt(mCount - 1, itemPtr))
	{
		(void) Remove(mCount - 1);
		return true;
	}
	return false;
}

bool CHXFlatArray::Enqueue(const void *itemPtr)
{
	return SetAt(mCount, itemPtr);
}

bool CHXFlatArray::Dequeue(void *itemPtr)
{
	if (GetAt(0, itemPtr))
	{
		(void) Remove(0);
		return true;
	}
	return false;
}
