/*	XGStringList.cpp
 *
 *		This manages a handle full of strings
 */

/**********************************************************************

  YAAF is Copyright 1997-1999 by William Woody.

  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions 
  are met:

  Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer.

  Neither the name of the copyright holder or other contributors may
  be used to promote products derived from this software without
  specific written prior permission.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  SOFTWARE.
  
  Contact:   woody@alumni.caltech.edu
  Web page:  http://www.pandawave.com/yaaf/

**********************************************************************/

#include <XConfig.h>			// must be first to optimize compile on MSVC++
#include <XHandle.h>
#include <XStringUtil.h>
#include <XDataUtil.h>
#include <string.h>
#include <XError.h>

/************************************************************************/
/*																		*/
/*	Construction/Destruction											*/
/*																		*/
/************************************************************************/

/*$	XGStringList::XGStringList
 *
 *		This initializes an emtpy string list. The single parameter
 *	#extraData# specifies the size of the extra data associated with
 *	each string, in bytes.
 */

XGStringList::XGStringList(short extraData)
{
	fNumber = 0;
	fExtra = extraData;
}

/*$	XGStringList::~XGStringList
 *
 *		Delete the string list and release resources. Currently this
 *	does little but defer to the XGHandle class.
 */

XGStringList::~XGStringList()
{
}

/*$	XGStringList::operator =
 *
 *		Copies all the strings and extra data from one string list
 *	to another.
 */

XGStringList &XGStringList::operator = (const XGStringList &str)
{
	XGHandle::operator = (str);
	
	fNumber = str.fNumber;
	fExtra = str.fExtra;
	
	return *this;
}

/************************************************************************/
/*																		*/
/*	String Interface													*/
/*																		*/
/************************************************************************/

/*$	XGStringList::GetString
 *
 *		Get the specified string. The string index is specified as a
 *	zero-based index.
 */

void XGStringList::GetString(long x, char *str) const
{
	unsigned char *ptr = (unsigned char *)**this;
	
	if ((x < 0) || (x >= fNumber)) {
		throw XGPostError(KXGStringListError);
	}
	while (x-- > 0) ptr += fExtra + 1 + *ptr;
	cvtp2c(str,ptr);
}

/*$	XGStringList::SetString
 *
 *		Set the string at this index to the specified value. The
 *	maximum length of a stored string is 255 bytes in length, not
 *	including the NULL terminator.
 */

void XGStringList::SetString(long x, const char *str)
{
	long delta;
	long index;
	unsigned char *ptr = (unsigned char *)**this;
	
	if ((x < 0) || (x >= fNumber)) {
		throw XGPostError(KXGStringListError);
	}
	index = 0;
	while (x-- > 0) index += fExtra + 1 + ptr[index];
	delta = strlen(str) - ptr[index];
	if (delta > 0) {
		XGHandle::Insert(index,delta);
	} else if (delta < 0) {
		XGHandle::Delete(index,-delta);
	}
	ptr = (unsigned char *)**this;
	cvtc2p(ptr+index,str);
}

/*$	XGStringList::Insert
 *
 *		Insert a string into the offset specified. If index is set
 *	to zero, this puts the string at the start. If the index is
 *	set to the length of this thing, the string is appended to the end.
 *
 *		If there is extra data associated with this string, space
 *	is allocated for the extra data and initialized to zero.
 */

void XGStringList::Insert(long x, const char *str)
{
	long delta;
	long index;
	unsigned char *ptr = (unsigned char *)**this;
	
	if (str == NULL) str = "";						// insert blank string
	if ((x < 0) || (x > fNumber)) {
		throw XGPostError(KXGStringListError);
	}
	index = 0;
	while (x-- > 0) index += fExtra + 1 + ptr[index];
	delta = strlen(str) + 1 + fExtra;
	XGHandle::Insert(index,delta);
	ptr = (unsigned char *)**this;
	cvtc2p(ptr+index,str);							// copy string in
	if (fExtra) memset(ptr+index+ptr[index]+1,0,fExtra); // zero extra memory
	fNumber++;
}

/*$	XGStringList::Delete
 *
 *		Delete the specified string and extra bytes.
 */

void XGStringList::Delete(long x)
{
	long index;
	unsigned char *ptr = (unsigned char *)**this;
	
	if ((x < 0) || (x >= fNumber)) {
		throw XGPostError(KXGStringListError);
	}
	index = 0;
	while (x-- > 0) index += fExtra + 1 + ptr[index];
	XGHandle::Delete(index,fExtra+1+ptr[index]);
	fNumber--;
}

/*$	XGStringList::DeleteAll
 *
 *		Delete all the strings assicated with this string list.
 */

void XGStringList::DeleteAll()
{
	SetSize(0);
	fNumber = 0;
}

/************************************************************************/
/*																		*/
/*	String Interface													*/
/*																		*/
/************************************************************************/

/*$	XGStringList::GetExtra
 *
 *		Get the extra data. This copies the extra data to the buffer
 *	provided. The buffer should be initialized to be large enough to 
 *	store the extra data.
 */

void XGStringList::GetExtra(long x, void *data) const
{
	unsigned char *ptr = (unsigned char *)**this;
	
	if (fExtra == 0) {
		throw XGPostError(KXGStringListExtra);
	}
	if ((x < 0) || (x >= fNumber)) {
		throw XGPostError(KXGStringListError);
	}
	while (x-- > 0) ptr += fExtra + 1 + *ptr;
	::memmove((char *)data,ptr + *ptr + 1,fExtra);
}

/*$	XGStringList::SetExtra
 *
 *		Sets the extra data associated with this string. This copies
 *	the data from the pointer provided, up to the number of extra
 *	bytes associated with this string list.
 */

void XGStringList::SetExtra(long x, const void *data)
{
	unsigned char *ptr = (unsigned char *)**this;
	
	if (fExtra == 0) {
		throw XGPostError(KXGStringListExtra);
	}
	if ((x < 0) || (x >= fNumber)) {
		throw XGPostError(KXGStringListError);
	}
	while (x-- > 0) ptr += fExtra + 1 + *ptr;
	::memmove(ptr + *ptr + 1,(const char *)data,fExtra);
}

/*$	XGStringList::SetExtraSize
 *
 *		This changes the size of the extra bytes associated with each
 *	string. The extra data is either appended or deleted from the end
 *	of each extra record.
 *
 *		Normally you only set the size of the data buffer associated
 *	with string on initialization of the string list.
 */

void XGStringList::SetExtraSize(short size)
{
	long oldsize;
	long newsize;
	long oldoff,newoff;
	short index;
	short csize;
	short smsize;
	unsigned char *aptr,*bptr;
	
	/*
	 *	Easy stuff
	 */
	
	if (size == fExtra) return;						// no size change
	if (fNumber == 0) {
		fExtra = size;
		return;										// no strings
	}
	
	/*
	 *	Hard stuff--create a temporary handle and copy new structures
	 *	into it.
	 */
	
	XGHandle copy;
	
	oldsize = GetSize();
	newsize = oldsize + (size - fExtra) * fNumber;	// change in size
	copy.SetSize(newsize);
	aptr = (unsigned char *)**this;
	bptr = (unsigned char *)*copy;
	
	smsize = size;
	if (smsize < fExtra) smsize = fExtra;
	oldoff = 0;
	newoff = 0;
	for (index = 0; index < fNumber; index++) {
		csize = smsize + 1 + aptr[oldoff];
		
		::memmove(bptr + newoff, aptr + oldoff, csize);
		
		newoff += size + 1 + aptr[oldoff];
		oldoff += fExtra + 1 + aptr[oldoff];
	}
	
	/*
	 *	Now copy the data back
	 */
	
	SetSize(newsize);
	::memmove(aptr,bptr,newsize);
}
