/////////////////////////////////////////////////////////////
// CINEMA 4D SDK                                           //
/////////////////////////////////////////////////////////////
// (c) MAXON Computer GmbH, all rights reserved            //
/////////////////////////////////////////////////////////////

#ifndef _GEDYNAMICARRAY_H_
#define _GEDYNAMICARRAY_H_

#ifdef __API_INTERN__
	#include "hyperfile.h"
	#include "lib_zipfile.h"
#else
	#include "ge_math.h"
	#include "c4d_file.h"
	#include "c4d_memory.h"
	#include "c4d_baselist.h"
	#include "c4d_general.h"
	#include "lib_zipfile.h"
#endif

#if defined __LINUX
#include <new>
#elif defined __MAC
#include <new>
#else
#include <new.h>
#endif

#include <string.h>

enum GeDynamicArrayType
{
	DATYPE_NONE					= 0,
	DATYPE_CHAR					= -1,
	DATYPE_UCHAR				= -2,
	DATYPE_WORD					= -3,
	DATYPE_UWORD				= -4,
	DATYPE_LONG					= -5,
	DATYPE_ULONG				= -6,
	DATYPE_REAL					= -7,
	DATYPE_LREAL				= -8,
	DATYPE_BOOL					= -9,
	DATYPE_TIME					= -10,
	DATYPE_VECTOR				= -11,
	DATYPE_LVECTOR			= -12,
	DATYPE_MATRIX				= -13,
	DATYPE_LMATRIX			= -14,
	DATYPE_STRING				= -15,
	DATYPE_FILENAME			= -16,
	DATYPE_LLONG				= -17,
	DATYPE_SREAL				=	-18,
	DATYPE_CONTAINER		=	-19,
	DATYPE_SVECTOR			=	-20,
	DATYPE_SMATRIX			=	-21
} ENUM_END_LIST(GeDynamicArrayType);

#if 0
	#define CHECKMEM(x) GeCheck(x)
#else
	#define CHECKMEM(x)
#endif

class GeDynamicArrayFileData
{
public:
	GeDynamicArrayFileData(GeDynamicArrayType type = DATYPE_NONE, LONG offset = 0):d_type(type),d_offset(offset) {}
	~GeDynamicArrayFileData() {}

	GeDynamicArrayType d_type;			//The type, e.g. DATYPE_CHAR
	LONG d_offset;		//The offset from the start of the structure
};

//GeDynamicArray file structure helper, arguments are s = Struct, m = Member, t = type
#define GeDynamicArrayFile(s, m, t) GeDynamicArrayFileData(t, offsetof(s,m))

inline Bool GeDynamicArrayWriteable(LONG type)
{
	switch (type)
	{
	case DATYPE_CHAR:			
	case DATYPE_WORD:			
	case DATYPE_LONG:			
	case DATYPE_REAL:			
	case DATYPE_LREAL:
	case DATYPE_SREAL:
	case DATYPE_LLONG:		return TRUE;
	default:	break;
	}
	return FALSE;
}

inline VLONG GeDynamicArrayStructSizeCheck(GeDynamicArrayFileData *list, VLONG count)
{
	if (!list) return 0;

	VLONG size = 0, i = 0;

	for (i = count - 1; i >= 0 ; --i)
	{
		switch (list[i].d_type)
		{
		default:
		case DATYPE_NONE: break;
		case DATYPE_CHAR: size += sizeof(CHAR); break;
		case DATYPE_UCHAR: size += sizeof(UCHAR); break;
		case DATYPE_WORD: size += sizeof(SWORD); break;
		case DATYPE_UWORD: size += sizeof(UWORD); break;
		case DATYPE_LONG: size += sizeof(LONG); break;
		case DATYPE_ULONG: size += sizeof(ULONG); break;
		case DATYPE_REAL: size += sizeof(Real); break;
		case DATYPE_LREAL: size += sizeof(LReal); break;
		case DATYPE_BOOL: size += sizeof(Bool); break;
		case DATYPE_TIME: size += sizeof(BaseTime); break;
		case DATYPE_VECTOR: size += sizeof(Vector); break;
		case DATYPE_LVECTOR: size += sizeof(LVector); break;
		case DATYPE_SVECTOR: size += sizeof(SVector); break;
		case DATYPE_MATRIX: size += sizeof(Matrix); break;
		case DATYPE_LMATRIX: size += sizeof(LMatrix); break;
		case DATYPE_SMATRIX: size += sizeof(SMatrix); break;
		case DATYPE_STRING: size += sizeof(String); break;
		case DATYPE_FILENAME: size += sizeof(Filename); break;
		case DATYPE_LLONG: size += sizeof(LLONG); break;
		case DATYPE_SREAL: size += sizeof(SReal); break;
		case DATYPE_CONTAINER: size += sizeof(BaseContainer); break;
		}
	}

	return size;
}

//dynamic array template
template<class TYPE> class GeDynamicArray
{
protected:
	TYPE*									ptr;
	TYPE*									l_ptr;

	VLONG									l_cnt;
	VLONG									totcnt;
	VLONG									blocksize;
	VLONG									count;
public:
	GeDynamicArray();
	GeDynamicArray(VLONG size);
	GeDynamicArray(VLONG size, VLONG block);
	GeDynamicArray(const GeDynamicArray<TYPE> &g);
	~GeDynamicArray(void);

	typedef void	GeDynamicArrayCallback(VLONG index, TYPE *value, void *data);

	TYPE*					Release(void);
	void					FreeArray(void);
	GeDynamicArray<TYPE> &operator =(const GeDynamicArray<TYPE> &t);
	GeDynamicArray<TYPE> &operator =(const TYPE *t);
	
	//friend	Bool operator !(const GeDynamicArray<TYPE> &t) { return t.count == 0; }
	//operator TYPE * ()											{	return l_ptr;	}
	//TYPE * const *operator &()							{	return &l_ptr;	}

	Bool					Content()									{ return l_ptr != NULL; }
	TYPE * GetPtr()													{	return l_ptr;	}

	TYPE & operator[]	( LONG i ) const			{	GeAssert(i >= 0 && i < count); return l_ptr[i];	}
#if defined __LINUX && !defined __C4D_64BIT && 0
	TYPE & operator[] ( VLONG i ) const			{ GeAssert(i >= 0 && i < count); return l_ptr[i]; }
#endif
	TYPE & operator[]	( LLONG i ) const			{	GeAssert(i >= 0 && i < count); return l_ptr[i];	}
#if !defined(C4D_VERSION) &&  !defined __LINUX && !(defined __MAC && __LP64__)
	TYPE & operator[] ( int i )	const				{	GeAssert(i >= 0 && i < count); return operator[]((LONG) i);	}
#endif
	TYPE & operator[] ( unsigned i ) const	{	GeAssert(i >= 0 && i < count); return operator[]((LONG) i);	}
	VLONG					GetCount(void) const			{ return count; }
	void          SetCount(VLONG c);
	VLONG					GetTotCount(void)	const		{ return totcnt - l_cnt; }
	void					SetBlockSize(VLONG size);
	VLONG					GetBlockSize(void) const	{ return blocksize; }
	Bool					ReadUsingFunction(HyperFile *hf, LONG level);
	Bool					WriteUsingFunction(HyperFile *hf);
	Bool					Read(HyperFile *hf, GeDynamicArray<GeDynamicArrayFileData> &typelist);
	Bool					Write(HyperFile *hf, GeDynamicArray<GeDynamicArrayFileData> &typelist);
	void					DoToAll(GeDynamicArrayCallback *callback, void *data);
	void					DoToSome(GeDynamicArrayCallback *callback, void *data, VLONG do_start, VLONG do_end);
	void					DoToSearch(GeDynamicArrayCallback *callback, void *data, TYPE p);
	Bool					ReScopeNC(VLONG cnt, Bool make_new = TRUE);
	Bool					ReScope(VLONG cnt);
	Bool					ReSizeNC(VLONG cnt, Bool make_new = TRUE);
	Bool					ReSize(VLONG cnt);
	Bool					ReSize(VLONG c_from, VLONG c_to);
	inline void		Fill(VLONG c_from, VLONG c_to, TYPE t);
	inline void		Fill(TYPE t);
	Bool					Insert(TYPE p, VLONG index);
	Bool					Insert(const TYPE *p, VLONG index, VLONG cnt);
	Bool					MergeIn(GeDynamicArray<TYPE>&p,VLONG index);
	Bool					InsertSpace(VLONG index, VLONG amount, Bool clear = TRUE, Bool makenew = TRUE);
	inline Bool		Push(TYPE p);
	Bool					Remove(VLONG index, VLONG amount = 1);
	inline void		RemoveAll(TYPE p);
	TYPE					Pop(void);
	inline VLONG	Find(TYPE p);
	inline VLONG	Find(TYPE p, VLONG index);
	void					Shift(VLONG index, VLONG cnt);
	void					ShiftBlock(VLONG b_from, VLONG b_to, VLONG amount);
	void					Grab(TYPE *p, VLONG amount);
};

template<class TYPE> GeDynamicArray<TYPE>::GeDynamicArray(void)
{
	ptr = NULL;
	blocksize = 4;
	FreeArray();
}

template<class TYPE> GeDynamicArray<TYPE>::GeDynamicArray(VLONG size) 
{
	ptr = NULL;
	blocksize = 4;
	FreeArray();
	ReScope(size); 
}

template<class TYPE> GeDynamicArray<TYPE>::GeDynamicArray(VLONG size, VLONG block) 
{ 
	ptr = NULL;
	blocksize = block; 
	FreeArray();
	ReScope(size); 
}

template<class TYPE> GeDynamicArray<TYPE>::GeDynamicArray(const GeDynamicArray<TYPE> &g) 
{ 
	ptr = NULL;
	blocksize = g.blocksize; 
	FreeArray();
	*this = g;
}


template<class TYPE> GeDynamicArray<TYPE>::~GeDynamicArray(void)
{
	FreeArray();
}

template<class TYPE> TYPE* GeDynamicArray<TYPE>::Release(void)
{ 
CHECKMEM(ptr);
	memmove(ptr,l_ptr,sizeof(TYPE)*count);
	blocksize = 4;
	VLONG cnt = count;
	count += l_cnt;
	l_ptr = ptr;
CHECKMEM(ptr);
	l_cnt = 0;
	ReSizeNC(cnt);
	TYPE	*tmp = ptr;
	ptr = NULL; 
	l_ptr = NULL;
	totcnt = 0; 
	count = 0; 
	l_cnt = 0;
	return tmp;
}

template<class TYPE> void GeDynamicArray<TYPE>::FreeArray(void)
{ 
	if (ptr) 
	{
		VLONG i=0;
		TYPE *buf=l_ptr;

CHECKMEM(ptr);

		for (i=0;i<count;i++,buf++) buf->~TYPE();

		GeFree(ptr);
	}
	ptr = NULL;
	l_ptr = NULL;
	count = 0;
	totcnt = 0;
	l_cnt = 0;
}

template<class TYPE> GeDynamicArray<TYPE>& GeDynamicArray<TYPE>::operator =(const GeDynamicArray<TYPE> &t)
{
CHECKMEM(ptr);

	if (t.blocksize != blocksize) 
	{
		FreeArray();
		SetBlockSize(t.blocksize);
	}
	if (!ReScopeNC(t.count)) return *this;

	VLONG	i = 0;

	TYPE	*pbuf = l_ptr;
	TYPE	*tbuf = t.l_ptr;
	for (i = 0; i < count; i++, pbuf++, tbuf++) *pbuf = *tbuf;

CHECKMEM(ptr);

	return *this;
}

template<class TYPE> GeDynamicArray<TYPE>& GeDynamicArray<TYPE>::operator =(const TYPE *t)
{
CHECKMEM(ptr);

	FreeArray();
	VLONG	cnt = sizeof(*t) / sizeof(TYPE);
	if (!ReScopeNC(cnt)) return *this;

	VLONG	i = 0;

	TYPE	*pbuf = l_ptr;
	const TYPE	*tbuf = t;
	for (i = 0; i < count; i++, pbuf++, tbuf++) *pbuf = *tbuf;

CHECKMEM(ptr);

	return *this;
}

template<class TYPE> void GeDynamicArray<TYPE>::SetCount(VLONG c)					
{
	GeAssert(c >= 0 && c <= totcnt - l_cnt); 
	if (c > totcnt - l_cnt) return; 
	CHECKMEM(ptr);
	TYPE *buf = &l_ptr[c];
	for (VLONG i = c;i < count;i++, buf++) buf->~TYPE();
	count = c; 
}

template<class TYPE> void GeDynamicArray<TYPE>::SetBlockSize(VLONG size)
{ 
	blocksize = size;
}

template<class TYPE> Bool GeDynamicArray<TYPE>::ReadUsingFunction(HyperFile *hf, LONG level)
{
	LONG dlevel = 0;
	if (!hf->ReadLong(&dlevel)) return FALSE;

	if (dlevel >= 1)
	{
		FreeArray();

		LLONG p;
		if (!hf->ReadLLong(&p)) return FALSE;
		blocksize = VLONG(p);
		if (!hf->ReadLLong(&p)) return FALSE;
		if (!ReScope(VLONG(p))) return FALSE;

		for (p = 0; p < count; p++)
		{
			if (!l_ptr[p].Read(hf, level)) return FALSE;
		}
	}

	return TRUE;
}

template<class TYPE> Bool GeDynamicArray<TYPE>::WriteUsingFunction(HyperFile *hf)
{
	if (!hf->WriteLong(1)) return FALSE;		//Level

	if (!hf->WriteLLong(LLONG(blocksize))) return FALSE;
	if (!hf->WriteLLong(LLONG(count))) return FALSE;

	VLONG i = 0;
	for (i = 0; i < count; i++)
	{
		if (!l_ptr[i].Write(hf)) return FALSE;
	}

	return TRUE;
}

//Read array from hyperfile with list of types in order of how they appear in structures
template<class TYPE> Bool GeDynamicArray<TYPE>::Read(HyperFile *hf, GeDynamicArray<GeDynamicArrayFileData> &typelist)
{
	if (typelist.GetCount()==0 || !hf) return FALSE;

	LLONG r_count = 0, r_blocksize = 0;
	LONG r_level = 0;
	VLONG tsize = sizeof(TYPE);

	GeAssert(GeDynamicArrayStructSizeCheck(typelist.GetPtr(), typelist.GetCount()) <= tsize); 

	if (!hf->ReadLong(&r_level)) return FALSE;
	if (r_level < 0)
	{
		r_level = -r_level;
		if (!hf->ReadLLong(&r_count)) return FALSE;
		if (!hf->ReadLLong(&r_blocksize)) return FALSE;
	}
	else 
	{
		r_count = r_level;
		if (!hf->ReadLong(&r_level)) return FALSE;
		r_blocksize = r_level;
		r_level = 0;
	}

	SetBlockSize(VLONG(r_blocksize));
	if (!ReScopeNC(VLONG(r_count))) return FALSE;
	if (count == 0) return TRUE;
	if (!tsize) return TRUE;
	
CHECKMEM(ptr);
	if (r_level > 0 && typelist.GetCount() == 1 && GeDynamicArrayWriteable(typelist[0].d_type))
	{
		//Write arrays when there's only one type being written
		CHAR *ttbuf = &((CHAR*)l_ptr)[typelist[0].d_offset];

		switch (typelist[0].d_type)
		{
		case DATYPE_CHAR:			if (!hf->ReadArray((void*)ttbuf, HYPERFILEARRAY_CHAR, tsize, count)) return FALSE; break;
		case DATYPE_WORD:			if (!hf->ReadArray((void*)ttbuf, HYPERFILEARRAY_WORD, tsize, count)) return FALSE; break;
		case DATYPE_LONG:			if (!hf->ReadArray((void*)ttbuf, HYPERFILEARRAY_LONG, tsize, count)) return FALSE; break;
		case DATYPE_REAL:			if (!hf->ReadArray((void*)ttbuf, HYPERFILEARRAY_REAL, tsize, count)) return FALSE; break;
		case DATYPE_SREAL:		if (!hf->ReadArray((void*)ttbuf, HYPERFILEARRAY_SREAL, tsize, count)) return FALSE; break;
		case DATYPE_LREAL:		if (!hf->ReadArray((void*)ttbuf, HYPERFILEARRAY_LREAL, tsize, count)) return FALSE; break;
		case DATYPE_LLONG:		if (!hf->ReadArray((void*)ttbuf, HYPERFILEARRAY_LLONG, tsize, count)) return FALSE; break;
		default:	break;
		}
	}
	else
	{
		TYPE	*tbuf = l_ptr;
		VLONG	i = 0, o = 0;
		CHAR *ttbuf = NULL;
		for (i = 0; i < count; i++, tbuf++)
		{
			for (o = 0; o < typelist.GetCount(); o++)
			{
				ttbuf = &((CHAR*)tbuf)[typelist[o].d_offset];

				switch (typelist[o].d_type)
				{
				case DATYPE_CHAR:				if (!hf->ReadChar(ttbuf)) return FALSE; break;
				case DATYPE_UCHAR:			if (!hf->ReadUChar((UCHAR *) ttbuf)) return FALSE; break;
				case DATYPE_WORD:				if (!hf->ReadWord((SWORD *) ttbuf)) return FALSE; break;
				case DATYPE_UWORD:			if (!hf->ReadUWord((UWORD *) ttbuf)) return FALSE; break;
				case DATYPE_LONG:				if (!hf->ReadLong((LONG *) ttbuf)) return FALSE; break;
				case DATYPE_ULONG:			if (!hf->ReadULong((ULONG *) ttbuf)) return FALSE; break;
				case DATYPE_REAL:				if (!hf->ReadReal((Real*) ttbuf)) return FALSE; break;
				case DATYPE_LREAL:			if (!hf->ReadLReal((LReal *) ttbuf)) return FALSE; break;
				case DATYPE_SREAL:			if (!hf->ReadSReal((SReal *) ttbuf)) return FALSE; break;
				case DATYPE_BOOL:				if (!hf->ReadBool((Bool *) ttbuf)) return FALSE; break;
				case DATYPE_TIME:				if (!hf->ReadTime((BaseTime *) ttbuf)) return FALSE; break;
				case DATYPE_VECTOR:			if (!hf->ReadVector((Vector *) ttbuf)) return FALSE; break;
				case DATYPE_SVECTOR:		if (!hf->ReadSVector((SVector *) ttbuf)) return FALSE; break;
				case DATYPE_LVECTOR:		if (!hf->ReadLVector((LVector *) ttbuf)) return FALSE; break;
				case DATYPE_MATRIX:			if (!hf->ReadMatrix((Matrix *) ttbuf)) return FALSE; break;
				case DATYPE_SMATRIX:		if (!hf->ReadSMatrix((SMatrix *) ttbuf)) return FALSE; break;
				case DATYPE_LMATRIX:		if (!hf->ReadLMatrix((LMatrix *) ttbuf)) return FALSE; break;
				case DATYPE_STRING:			if (!hf->ReadString((String *) ttbuf)) return FALSE; break;
				case DATYPE_FILENAME:		if (!hf->ReadFilename((Filename *) ttbuf)) return FALSE; break;
				case DATYPE_LLONG:			if (!hf->ReadLLong((LLONG *) ttbuf)) return FALSE; break;
				case DATYPE_CONTAINER:	if (!hf->ReadContainer((BaseContainer *) ttbuf, TRUE)) return FALSE; break;
				default:	break;
				}
			}
		}
	}
CHECKMEM(ptr);

	return TRUE;
}

//Write array to hyperfile
template<class TYPE> Bool	GeDynamicArray<TYPE>::Write(HyperFile *hf, GeDynamicArray<GeDynamicArrayFileData> &typelist)
{
	if (typelist.GetCount()==0 || !hf) return FALSE;

	LLONG r_count = LLONG(count), r_blocksize = LLONG(blocksize);
	VLONG tsize = sizeof(TYPE);

	GeAssert(GeDynamicArrayStructSizeCheck(typelist.GetPtr(), typelist.GetCount()) <= tsize); 

	if (!hf->WriteLong(-1)) return FALSE; //Level
	if (!hf->WriteLLong(r_count)) return FALSE;
	if (!hf->WriteLLong(r_blocksize)) return FALSE;
	if (!ptr || count == 0) return TRUE;

	if (!tsize) return TRUE;

CHECKMEM(ptr);

	if (typelist.GetCount() == 1 && GeDynamicArrayWriteable(typelist[0].d_type))
	{
		//Write arrays when there's only one type being written
		CHAR *ttbuf = &((CHAR*)l_ptr)[typelist[0].d_offset];

		switch (typelist[0].d_type)
		{
		case DATYPE_CHAR:			if (!hf->WriteArray((void*)ttbuf, HYPERFILEARRAY_CHAR, tsize, count)) return FALSE; break;
		case DATYPE_WORD:			if (!hf->WriteArray((void*)ttbuf, HYPERFILEARRAY_WORD, tsize, count)) return FALSE; break;
		case DATYPE_LONG:			if (!hf->WriteArray((void*)ttbuf, HYPERFILEARRAY_LONG, tsize, count)) return FALSE; break;
		case DATYPE_REAL:			if (!hf->WriteArray((void*)ttbuf, HYPERFILEARRAY_REAL, tsize, count)) return FALSE; break;
		case DATYPE_LREAL:		if (!hf->WriteArray((void*)ttbuf, HYPERFILEARRAY_LREAL, tsize, count)) return FALSE; break;
		case DATYPE_SREAL:		if (!hf->WriteArray((void*)ttbuf, HYPERFILEARRAY_SREAL, tsize, count)) return FALSE; break;
		case DATYPE_LLONG:		if (!hf->WriteArray((void*)ttbuf, HYPERFILEARRAY_LLONG, tsize, count)) return FALSE; break;
		default:	break;
		}
	}
	else
	{
		VLONG	i = 0, o = 0;
		TYPE	*tbuf = l_ptr;
		CHAR *ttbuf = NULL;
		for (i = 0; i < count; i++, tbuf++)
		{
			for (o = 0; o < typelist.GetCount(); o++)
			{
				ttbuf = &((CHAR*)tbuf)[typelist[o].d_offset];

				switch (typelist[o].d_type)
				{
				case DATYPE_CHAR:				if (!hf->WriteChar(ttbuf[0])) return FALSE; break;
				case DATYPE_UCHAR:			if (!hf->WriteUChar(((UCHAR *) ttbuf)[0])) return FALSE; break;
				case DATYPE_WORD:				if (!hf->WriteWord(((SWORD *) ttbuf)[0])) return FALSE; break;
				case DATYPE_UWORD:			if (!hf->WriteUWord(((UWORD *) ttbuf)[0])) return FALSE; break;
				case DATYPE_LONG:				if (!hf->WriteLong(((LONG *) ttbuf)[0])) return FALSE; break;
				case DATYPE_ULONG:			if (!hf->WriteULong(((ULONG *) ttbuf)[0])) return FALSE; break;
				case DATYPE_REAL:				if (!hf->WriteReal(((Real *) ttbuf)[0])) return FALSE; break;
				case DATYPE_SREAL:			if (!hf->WriteSReal(((SReal *) ttbuf)[0])) return FALSE; break;
				case DATYPE_LREAL:			if (!hf->WriteLReal(((LReal *) ttbuf)[0])) return FALSE; break;
				case DATYPE_BOOL:				if (!hf->WriteBool(((Bool *) ttbuf)[0])) return FALSE; break;
				case DATYPE_TIME:				if (!hf->WriteTime(((BaseTime *) ttbuf)[0])) return FALSE; break;
				case DATYPE_VECTOR:			if (!hf->WriteVector(((Vector *) ttbuf)[0])) return FALSE; break;
				case DATYPE_SVECTOR:		if (!hf->WriteSVector(((SVector *) ttbuf)[0])) return FALSE; break;
				case DATYPE_LVECTOR:		if (!hf->WriteLVector(((LVector *) ttbuf)[0])) return FALSE; break;
				case DATYPE_MATRIX:			if (!hf->WriteMatrix(((Matrix *) ttbuf)[0])) return FALSE; break;
				case DATYPE_SMATRIX:		if (!hf->WriteSMatrix(((SMatrix *) ttbuf)[0])) return FALSE; break;
				case DATYPE_LMATRIX:		if (!hf->WriteLMatrix(((LMatrix *) ttbuf)[0])) return FALSE; break;
				case DATYPE_STRING:			if (!hf->WriteString(((String *) ttbuf)[0])) return FALSE; break;
				case DATYPE_FILENAME:		if (!hf->WriteFilename(((Filename *) ttbuf)[0])) return FALSE; break;
				case DATYPE_LLONG:			if (!hf->WriteLLong(((LLONG *) ttbuf)[0])) return FALSE; break;
				case DATYPE_CONTAINER:	if (!hf->WriteContainer(((BaseContainer *) ttbuf)[0])) return FALSE; break;
				default:	break;
				}
			}
		}
	}

CHECKMEM(ptr);

	return TRUE;
}

//routine to execute code on all entries in ptr array
template<class TYPE> void GeDynamicArray<TYPE>::DoToAll(GeDynamicArrayCallback *callback, void *data) 
{ 
	if (!ptr) return;

CHECKMEM(ptr);

	VLONG	i = 0; 
	TYPE *buf = l_ptr;
	for (i = 0; i < count; i++, buf++)
	{
		callback(i, buf, data); 
	}
CHECKMEM(ptr);

}

//routine to execute code on some entries in ptr array
template<class TYPE> void GeDynamicArray<TYPE>::DoToSome(GeDynamicArrayCallback *callback, void *data, VLONG do_start, VLONG do_end)
{
	if (!ptr) return;

CHECKMEM(ptr);

	VLONG	dend = do_end;
	VLONG	dsta = do_start;
	if (dend < dsta) dend = do_start, dsta = do_end;
	if (dend > count) dend = count;
	if (dsta < 0) dsta = 0;

	TYPE *buf = &l_ptr[dsta];
	VLONG	i = 0;
	for (i = dsta; i < dend; i++, buf++)
	{
		callback(i, buf, data);
	}
CHECKMEM(ptr);
}

//routine to execute code on entries that match TYPE p in ptr array
template<class TYPE> void GeDynamicArray<TYPE>::DoToSearch(GeDynamicArrayCallback *callback, void *data, TYPE p)
{
	if (!ptr) return;

CHECKMEM(ptr);

	TYPE *buf = l_ptr;
	VLONG	i = 0;
	for (i = 0; i < count; i++, buf++)
	{
		if (*buf == p) callback(i, buf, data);
	}
CHECKMEM(ptr);
}

//Resizes the array without retatining the data and without clearing the data
template<class TYPE> Bool GeDynamicArray<TYPE>::ReScopeNC(VLONG cnt, Bool make_new)
{
	if (cnt <= 0)
	{
		FreeArray();
		return TRUE;
	}

	if (!blocksize)
	{
		GeBoom();
		return FALSE;
	}
	LLONG	nsize = (((cnt + l_cnt - 1) / blocksize) + 1) * blocksize, i = 0;
	TYPE *buf = NULL;

CHECKMEM(ptr);

	if (count)
	{
		for (i = 0, buf = l_ptr;i < count;i++, buf++) buf->~TYPE();
	}

	if (nsize != totcnt && !(cnt > count && nsize < totcnt))
	{
		//Check against massive numbers
		LLONG tsize = nsize * LLONG(sizeof(TYPE));
		if (tsize != VLONG(tsize) || tsize < nsize || tsize < cnt)
		{
			GeAssert(FALSE);		//To avoid people allocating more memory than is possible on the system
			return FALSE;				//
		}

		if (!ptr) ptr = GeAllocTypeNC(TYPE,(VLONG)nsize);
		else ptr = (TYPE*)GeReallocNC(ptr, sizeof(TYPE) * (VLONG)nsize);

		if (!ptr) return FALSE;

		l_ptr = &ptr[l_cnt];
		totcnt = (VLONG)nsize;
	}

	if (make_new)
	{
		for (i = 0, buf = l_ptr;i < cnt;i++, buf++) new(buf)TYPE;
	}

	count = cnt;

CHECKMEM(ptr);

	return TRUE;
}

//Resizes the array without retaining the data
template<class TYPE> Bool	GeDynamicArray<TYPE>::ReScope(VLONG cnt)
{
	if (cnt <= 0)
	{
		FreeArray();
		return TRUE;
	}

	if (!blocksize)
	{
		GeBoom();
		return FALSE;
	}
	LLONG	nsize = (((cnt + l_cnt - 1) / blocksize) + 1) * blocksize, i = 0;
	TYPE *buf = NULL;

CHECKMEM(ptr);

	if (count)
	{
		for (i = 0,buf = l_ptr;i < count;i++, buf++) buf->~TYPE();
	}

	if (nsize != totcnt && !(cnt > count && nsize < totcnt))
	{
		//Check against massive numbers
		LLONG tsize = nsize * LLONG(sizeof(TYPE));
		if (tsize != VLONG(tsize) || tsize < nsize || tsize < cnt)
		{
			GeAssert(FALSE);		//To avoid people allocating more memory than is possible on the system
			return FALSE;				//
		}

		if (!ptr) ptr = GeAllocTypeNC(TYPE, (VLONG)nsize);
		else ptr = (TYPE*)GeReallocNC(ptr, sizeof(TYPE) * (VLONG)nsize);

		if (!ptr) return FALSE;

		l_ptr = &ptr[l_cnt];

		totcnt = (VLONG)nsize;
	}

	ClearMemType(l_ptr, cnt );
	for (i = 0, buf = l_ptr;i < cnt;i++, buf++) new(buf)TYPE;

	count = cnt;
CHECKMEM(ptr);
	return TRUE;
}

//Resizes the array without clearing the memory used
template<class TYPE> Bool	GeDynamicArray<TYPE>::ReSizeNC(VLONG cnt, Bool make_new)
{
	if (cnt == count) return TRUE;

	if (cnt < count) return Remove(cnt, count - cnt);
	return InsertSpace(count,cnt - count,FALSE, make_new);
}

template<class TYPE> Bool	GeDynamicArray<TYPE>::ReSize(VLONG cnt)
{
	if (cnt == count) return TRUE;

	if (cnt < count) return Remove(cnt, count - cnt);
	return InsertSpace(count,cnt - count,TRUE);
}

//Reize the array down
template<class TYPE> Bool	GeDynamicArray<TYPE>::ReSize(VLONG c_from, VLONG c_to)
{
	VLONG	t_from = VCut(c_from, 0, count), t_to = VCut(c_to + 1, 0, count);
	if (t_from == t_to) return ReSize(0);
	if (t_from > t_to)
	{
		VLONG	temp = t_from;
		t_from = t_to;
		t_to = temp;
	}

	Shift(t_from, -t_from);
	return ReSize(t_to - t_from);
}

//Fill the array with TYPE t between c_from and c_to
template<class TYPE> inline void GeDynamicArray<TYPE>::Fill(VLONG c_from, VLONG c_to, TYPE t)
{
	VLONG	t_from = VCut(c_from, 0, count), t_to = VCut(c_to + 1, 0, count);
	if (t_from == t_to) return;
	if (t_from > t_to)
	{
		VLONG	temp = t_from;
		t_from = t_to;
		t_to = temp;
	}

	TYPE	*buf = NULL;
	VLONG	i = 0;
CHECKMEM(ptr);
	for (i = t_from, buf = &l_ptr[t_from]; i < t_to; i++, buf++) *buf = t;
CHECKMEM(ptr);
}

//Fill the array with TYPE t
template<class TYPE> inline void GeDynamicArray<TYPE>::Fill(TYPE t)	
{ 
	TYPE	*buf = l_ptr; 
	VLONG i = 0; 
CHECKMEM(ptr);
	for (i = 0; i < count; i++, buf++) *buf = t; 
CHECKMEM(ptr);
}

//Add value to the array at index
//and shifts array up
template<class TYPE> Bool GeDynamicArray<TYPE>::Insert(TYPE p, VLONG index)
{
	if (index < 0) return FALSE;
CHECKMEM(ptr);
	if (!InsertSpace(index,1,TRUE)) return FALSE;
	l_ptr[index] = p;
CHECKMEM(ptr);
	return TRUE;
}

//Inserts (copies) an existing array into this one
template<class TYPE> Bool	GeDynamicArray<TYPE>::Insert(const TYPE *p, VLONG index, VLONG cnt)
{
	if (cnt <= 0) return TRUE;
	if (index < 0 || index > count || !p) return FALSE;
CHECKMEM(ptr);
	if (!InsertSpace(index, cnt, TRUE)) return FALSE;
	VLONG i=0;
	TYPE *buf = &l_ptr[index];
	const TYPE *pbuf = p;
	for (i = 0;i < cnt;i++, pbuf++, buf++) (*buf) = (*pbuf);
CHECKMEM(ptr);
	return TRUE;
}

//Merges an existing dynamic array into this one, this array becomes the owner
template<class TYPE> Bool	GeDynamicArray<TYPE>::MergeIn(GeDynamicArray<TYPE>&p,VLONG index)
{
	if (p.GetCount() <= 0) return TRUE;
	if (index < 0 || index > count) return FALSE;
CHECKMEM(ptr);
	if (!InsertSpace(index, p.GetCount(), FALSE, FALSE)) return FALSE;
	VLONG cnt = p.GetCount();
	TYPE *data = p.Release();
	if (!data) return FALSE;
	CopyMemType(data, &l_ptr[index], cnt );
	GeFree(data);
CHECKMEM(ptr);
	return TRUE;
}

//Inserts a gap in the array
template<class TYPE> Bool GeDynamicArray<TYPE>::InsertSpace(VLONG index,VLONG amount,Bool clear,Bool makenew)
{
	if (index < 0) return FALSE;
	if (amount <= 0) return TRUE;

CHECKMEM(ptr);

	VLONG	cnt = ((count > index)?count:index) + amount;

	if (!blocksize)
	{
		GeBoom();
		return FALSE;
	}

	if (index < (count >> 1)) //Insert space low
	{
		l_cnt -= amount;
		if (l_cnt < 0) //resize it's larger
		{
			LLONG nadd = (((-l_cnt - 1)/blocksize) + 1) * blocksize;

			//Check against massive numbers
			LLONG tsize = (LLONG(totcnt) + nadd) * LLONG(sizeof(TYPE));
			if (tsize != VLONG(tsize) || tsize < (nadd + totcnt) || tsize < amount)
			{
				GeAssert(FALSE);		//To avoid people allocating more memory than is possible on the system
				return FALSE;				//
			}

			totcnt += (VLONG)nadd;
			l_cnt += (VLONG)nadd;

			if (!ptr) ptr = GeAllocTypeNC(TYPE,(VLONG)totcnt);
			else ptr = (TYPE*)GeReallocNC(ptr, sizeof(TYPE) * totcnt);

			if (!ptr) return FALSE;

			l_ptr = &ptr[l_cnt];

			if (index > 0)
			{
				memmove(&l_ptr[index + amount], &l_ptr[(amount - nadd) + index], sizeof(TYPE)* (count - index));
				memmove(l_ptr, &l_ptr[amount - nadd], sizeof(TYPE) * index);
			}
			else
			{
				memmove(&l_ptr[amount], &l_ptr[amount - nadd], sizeof(TYPE) * count);
			}
		}
		else //just shift space in
		{
			if (index > 0) memmove(&ptr[l_cnt], l_ptr, sizeof(TYPE) * index);
			l_ptr = &ptr[l_cnt];
		}
	}
	else //Insert space high
	{
		LLONG	nsize = (((cnt + l_cnt - 1) / blocksize) + 1) * blocksize;

		if (nsize > totcnt) //Resize it's larger
		{
			//Check against massive numbers
			LLONG tsize = nsize * LLONG(sizeof(TYPE));
			if (tsize != VLONG(tsize) || tsize < nsize || tsize < amount)
			{
				GeAssert(FALSE);		//To avoid people allocating more memory than is possible on the system
				return FALSE;
			}

			if (!ptr) ptr = GeAllocTypeNC(TYPE,(VLONG)nsize);
			else ptr = (TYPE*)GeReallocNC(ptr, sizeof(TYPE) * (VLONG)nsize);

			if (!ptr) return FALSE;

			l_ptr = &ptr[l_cnt];

			totcnt = (VLONG)nsize;
		} 

		//Shift space in
		if (index < count)
		{
			memmove(&(l_ptr[index + amount]), &(l_ptr[index]),  sizeof(TYPE) * (count - index));
		}
	}

	VLONG i = 0;
	TYPE *buf = NULL;

	if (clear) 
	{
		if (count < index) //Clear space up till index
			ClearMemType(&l_ptr[count], (index - count) );

		//Clear space from index over amount
		ClearMemType(&l_ptr[index], amount );
	}

	if (makenew)
	{
		if (count < index) //Placement new from end of array to index
			for (i = count, buf = &l_ptr[count]; i<index; i++, buf++) new(buf)TYPE;

		// Placement new over amount
		for (i = 0, buf = &l_ptr[index]; i < amount; i++, buf++) new(buf)TYPE;
	}

	count = cnt;
CHECKMEM(ptr);
	return TRUE;
}

//Add value to the end of the array
template<class TYPE> Bool GeDynamicArray<TYPE>::Push(TYPE p)	
{ 
	return Insert(p, count);
}

//Remove value from the array at index
//and shifts array down
template<class TYPE> Bool GeDynamicArray<TYPE>::Remove(VLONG index, VLONG amount) 
{ 
	if (amount <= 0) return TRUE;
	if (index < 0 || index >= count || !ptr) return FALSE;
	if (!blocksize)
	{
		GeBoom();
		return FALSE;
	}

	if (index <= 0 && amount >= count)
	{
		FreeArray();
		return TRUE;
	}

	VLONG top = VMin(index + amount, count), amt = top - index;
	VLONG i = 0;
	TYPE *buf = NULL;

CHECKMEM(ptr);

	for (i = 0, buf = &l_ptr[index];i < amt;i++, buf++) buf->~TYPE();

	if (index < (count >> 1))
	{
		l_cnt += amt;

		if (index > 0) memmove(&l_ptr[amt], l_ptr, sizeof(TYPE) * index); //shift l_ptr up

		l_ptr = &ptr[l_cnt];

		if (l_cnt >= blocksize)
		{
			VLONG nsub = (l_cnt/blocksize) * blocksize;

			totcnt -= nsub;
			l_cnt -= nsub;

			memmove(&l_ptr[-nsub], l_ptr, sizeof(TYPE) * (count - amt)); //Shift down

			ptr = (TYPE*)GeReallocNC(ptr, sizeof(TYPE) * totcnt);
			if (!ptr) return FALSE;

			l_ptr = &ptr[l_cnt];
		}
	}
	else
	{
		VLONG	nsize = ((((count + l_cnt - 1) - amt) / blocksize) + 1) * blocksize;

		if (index < count - 1) memmove(&(l_ptr[index]), &(l_ptr[index + amt]),sizeof(TYPE) * (count - (index + amt) ) );

		if (nsize < totcnt)
		{
			ptr = (TYPE*)GeReallocNC(ptr, sizeof(TYPE) * nsize);
			if (!ptr) return FALSE;

			l_ptr = &ptr[l_cnt];

			totcnt = nsize;
		} 
	}
	count-=amt;
CHECKMEM(ptr);
	return TRUE;
}

//Removes all that match p from the array
template<class TYPE> inline void GeDynamicArray<TYPE>::RemoveAll(TYPE p) 
{ 
	VLONG	index = 0; 
	while (TRUE) 
	{ 
		index = Find(p, index); 
		if (index == NOTOK) break; 
		Remove(index); 
	} 
}

//Remove value from the end of the array
template<class TYPE> TYPE GeDynamicArray<TYPE>::Pop(void) 
{ 
	if (count <= 0) return TYPE();
	TYPE result = l_ptr[count - 1];
	Remove(count - 1);
	return result;
}

//Find the index of type that matches type p
template<class TYPE> inline VLONG GeDynamicArray<TYPE>::Find(TYPE p)	
{ 
	return Find(p,0); 
}

//Try to find an object from index onwards that matches type p
template<class TYPE> inline VLONG GeDynamicArray<TYPE>::Find(TYPE p, VLONG index)
{
	if (index >= count || !ptr) return NOTOK;

CHECKMEM(ptr);
	TYPE	*buf = &l_ptr[index];
	VLONG	i = 0;
	for (i = index; i < count; i++, buf++)
	{
		if (*buf == p) return i;
	}
CHECKMEM(ptr);
	return NOTOK;
}

//Shift array down or up from index value
// negative to move down, positive to move up
template<class TYPE> void	GeDynamicArray<TYPE>::Shift(VLONG index, VLONG cnt)
{
	ShiftBlock(index,count,cnt);
}

//Shift a block of the array up or down
// negative to move down, positive to move up
template<class TYPE> void	GeDynamicArray<TYPE>::ShiftBlock(VLONG b_from, VLONG b_to, VLONG amount)
{
	VLONG block_start = VMin(b_from, b_to);
	VLONG block_end = VMax(b_from, b_to);
	if (amount == 0 || block_start == block_end || block_start < 0 || block_end > count) return;

CHECKMEM(ptr);
	TYPE	*buf = NULL;
	VLONG	i = 0;
	if (amount > 0)
	{
		VLONG	istart = block_start;
		VLONG	iend = block_end - amount;

		if (iend < istart) iend = istart;

		VLONG m_min = VMin(count, VMax(block_end, istart + amount));
		VLONG m_max = VMin(count, block_end + amount);

		for (i = m_min, buf = &l_ptr[m_min]; i < m_max; i++, buf++) buf->~TYPE();

		m_min = VMin(count, istart + amount);

		if (m_max - m_min > 0)
			memmove(&(l_ptr[m_min]),&(l_ptr[istart]),sizeof(TYPE)*(m_max - m_min));

		iend = VMin(istart + amount, block_end);

		ClearMemType(&l_ptr[istart], (iend - istart) );
		for (i = istart, buf = &l_ptr[istart]; i < iend; i++, buf++) new(buf)TYPE;
	}
	else
	{
		VLONG	istart = block_start;
		VLONG	iend = block_end;

		VLONG	m_shift = 0;
		VLONG	m_end = VMax(0, iend + amount), m_max = VMin(m_end, istart);
		VLONG	m_to = istart + amount;
		if ( m_to < 0 )
		{
			m_to = 0;
			m_shift = - (istart + amount);
		}

		for (i = m_to, buf = &l_ptr[m_to]; i < m_max; i++, buf++) buf->~TYPE();

		if (m_end - m_to > 0)
			memmove(&(l_ptr[m_to]),&(l_ptr[istart + m_shift]),sizeof(TYPE)*(m_end - m_to));

		istart = VMax(block_start, m_end);

		ClearMemType(&l_ptr[istart], (iend - istart) );
		for (i = istart, buf = &l_ptr[istart]; i < iend; i++, buf++) new(buf)TYPE;
	}
CHECKMEM(ptr);
}

//Dynamic Array takes control of a datablock given to it
template<class TYPE> void	GeDynamicArray<TYPE>::Grab(TYPE *p, VLONG amount)
{
CHECKMEM(ptr);
	FreeArray();
	ptr = l_ptr = p;
	l_cnt = 0;
	if (ptr)
		totcnt = count = amount;
	else
		totcnt = count = 0;
CHECKMEM(ptr);
}

//-----------------------------------------
//Safe Variant, this can be accessed via [] safely and will never get out of bounds
template<class TYPE> class GeSafeDynamicArray : public GeDynamicArray < TYPE >
{
	INSTANCEOF(GeSafeDynamicArray<TYPE>, GeDynamicArray<TYPE>)
public:
	GeSafeDynamicArray(void)	
	{ 
		this->ptr = NULL;
		this->blocksize = 4;
		this->FreeArray();
	}

	GeSafeDynamicArray(VLONG size)
	{ 
		this->ptr = NULL;
		this->blocksize = 4;
		this->FreeArray();
		this->ReScope(size); 
	}

	GeSafeDynamicArray(VLONG size, VLONG block) 
	{
		this->ptr = NULL;
		this->blocksize = block;
		this->FreeArray();
		this->ReScope(size); 
	}

	~GeSafeDynamicArray(void)
	{
		this->FreeArray();
	}

	GeSafeDynamicArray<TYPE> &operator =(const GeSafeDynamicArray<TYPE> &t)
	{
		this->FreeArray();

		this->SetBlockSize(t.GetBlockSize());
		this->ReScopeNC(t.count);

CHECKMEM(ptr);

		VLONG	i = 0;
		TYPE *buf = this->l_ptr, *buf2 = t.l_ptr;
		for (i = 0; i < this->count; i++, buf++, buf2++) (*buf) = (*buf2);

CHECKMEM(ptr);
		return *this;
	}

	TYPE & operator[](LONG i) const
	{
CHECKMEM(ptr);
		if (this->count == 0)
		{
			((GeDynamicArray<TYPE>*)this)->ReScope(1);
			return this->l_ptr[0];
		}
		if (i < 0) return this->l_ptr[0];
		if (i >= this->count) return this->l_ptr[this->count - 1];
		return this->l_ptr[i];
	}
#if defined __LINUX && !defined __C4D_64BIT && 0
	TYPE & operator[](VLONG i) const
	{
CHECKMEM(ptr);
		if (this->count == 0)
		{
			((GeDynamicArray<TYPE>*)this)->ReScope(1);
			return this->l_ptr[0];
		}
		if (i < 0) return this->l_ptr[0];
		if (i >= this->count) return this->l_ptr[this->count - 1];
		return this->l_ptr[i];
	}
#endif
	TYPE & operator[](LLONG i) const
	{
CHECKMEM(ptr);
		if (this->count == 0)
		{
			((GeDynamicArray<TYPE>*)this)->ReScope(1);
			return this->l_ptr[0];
		}
		if (i < 0) return this->l_ptr[0];
		if (i >= this->count) return this->l_ptr[this->count - 1];
		return this->l_ptr[i];
	}
#if !defined(C4D_VERSION) &&  !defined( __LINUX ) && !( defined( __MAC ) && __LP64__ )
	TYPE & operator[] ( int i ) const
	{
		return operator[]((VLONG) i);
	}
#endif
	TYPE & operator[] ( unsigned i ) const
	{
		return operator[]((VLONG) i);
	}
};

//--------------------------------------------------------
//Automatic variant, this will resize based on the index you specify in array[index]

#define DEFAULT_ADR_BLOCK_SIZE 16

template<class TYPE> class GeAutoDynamicArray : public GeDynamicArray < TYPE >
{
	INSTANCEOF(GeAutoDynamicArray<TYPE>, GeDynamicArray<TYPE>)
public:
	GeAutoDynamicArray(void)
	{ 
		this->ptr = NULL;
		this->blocksize = DEFAULT_ADR_BLOCK_SIZE;
		this->FreeArray();
	}

	GeAutoDynamicArray(VLONG size) 
	{ 
		this->ptr = NULL;
		this->blocksize = DEFAULT_ADR_BLOCK_SIZE;
		this->FreeArray();
		this->ReScope(size);
	}

	GeAutoDynamicArray(VLONG size, VLONG block) 
	{ 
		this->ptr = NULL;
		this->blocksize = block;
		this->FreeArray();
		this->ReScope(size);
	}
	~GeAutoDynamicArray(void)
	{
		this->FreeArray();
	}

	GeAutoDynamicArray<TYPE> &operator =(const GeAutoDynamicArray<TYPE> &t)
	{
		this->FreeArray();
		this->SetBlockSize(t.GetBlockSize());
		this->ReScopeNC(t.count);

CHECKMEM(ptr);
		VLONG	i = 0;
		TYPE *buf = this->l_ptr, *buf2 = t.l_ptr;
		for (i = 0; i < this->count; i++, buf++, buf2++) (*buf) = (*buf2);
CHECKMEM(ptr);
		return *this;
	}

	GeAutoDynamicArray<TYPE> &operator =(TYPE *t) 
	{ 
		this->FreeArray(); 
		this->ptr = t; 
		this->count = sizeof(this->ptr) / sizeof(TYPE); 
		this->totcnt = this->count; 
		this->l_ptr = &(this->ptr[this->l_cnt]);
CHECKMEM(ptr);
		return *this; 	
	}

	TYPE & operator[](LONG i) const
	{
CHECKMEM(ptr);
		if (this->count == 0)
		{
			((GeDynamicArray<TYPE>*)this)->ReScope(1);
		}
		if (i < 0) return this->l_ptr[0];
		if (i >= this->count)
		{
			((GeDynamicArray<TYPE>*)this)->ReSize(i + 1);
		}
		return this->l_ptr[i];
	}
#if defined __LINUX && !defined __C4D_64BIT && 0
	TYPE & operator[](VLONG i) const
	{
CHECKMEM(ptr);
		if (this->count == 0)
		{
			((GeDynamicArray<TYPE>*)this)->ReScope(1);
		}
		if (i < 0) return this->l_ptr[0];
		if (i >= this->count)
		{
			((GeDynamicArray<TYPE>*)this)->ReSize(i + 1);
		}
		return this->l_ptr[i];
	}
#endif
	TYPE & operator[](LLONG i) const
	{
CHECKMEM(ptr);
		if (this->count == 0)
		{
			((GeDynamicArray<TYPE>*)this)->ReScope(1);
		}
		if (i < 0) return this->l_ptr[0];
		if (i >= this->count)
		{
			((GeDynamicArray<TYPE>*)this)->ReSize(i + 1);
		}
		return this->l_ptr[i];
	}
#if !defined(C4D_VERSION) &&  !defined( __LINUX ) &&  !( defined( __MAC ) && __LP64__)
	TYPE & operator[] ( int i ) const
	{
		return operator[]((VLONG) i);
	}
#endif
	TYPE & operator[] ( unsigned i ) const
	{
		return operator[]((VLONG) i);
	}
};
#endif


