// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
// Copyright 2009 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================

// must have access to handler class fields...
#include "RIFF.hpp"
#include "RIFF_Support.hpp"
#include "RIFF_Handler.hpp"

using namespace RIFF;

namespace RIFF {

// GENERAL STATIC FUNCTIONS ////////////////////////////////////////

Chunk* getChunk ( ContainerChunk* parent, RIFF_MetaHandler* handler )
{
	LFA_FileRef file = handler->parent->fileRef;
	XMP_Uns8 level = handler->level;
	XMP_Uns32 peek = LFA_PeekUns32_LE( file );

	if ( level == 0 )
	{
		XMP_Validate( peek == kChunk_RIFF, "expected RIFF chunk not found", kXMPErr_BadFileFormat );
		XMP_Enforce( parent == NULL );
	}
	else
	{
		XMP_Validate( peek != kChunk_RIFF, "unexpected RIFF chunk below top-level", kXMPErr_BadFileFormat );
		XMP_Enforce( parent != NULL );
	}

	switch( peek )
	{
	case kChunk_RIFF:
		return new ContainerChunk( parent, handler );
	case kChunk_LIST:
		{
		if ( level != 1 ) break; // only care on this level

			// look further (beyond 4+4 = beyond id+size) to check on relevance
			LFA_Seek( file, 8, SEEK_CUR );
			XMP_Uns32 containerType = LFA_PeekUns32_LE( file );
			LFA_Seek( file, -8, SEEK_CUR );

		bool isRelevantList = ( containerType== kType_INFO || containerType == kType_Tdat );
		if ( !isRelevantList ) break;

				return new ContainerChunk( parent, handler );	
		}
	case kChunk_XMP:
		if ( level != 1 ) break; // ignore on inappropriate levels (might be compound metadata?)
		return new XMPChunk( parent, handler );		
	case kChunk_DISP:
		{
			if ( level != 1 ) break; // only care on this level
			// peek even further to see if type is 0x001 and size is reasonable
			LFA_Seek( file , 4, SEEK_CUR ); // jump DISP
			XMP_Uns32 dispSize = LFA_ReadUns32_LE( file );
			XMP_Uns32 dispType = LFA_ReadUns32_LE( file );
			LFA_Seek( file , -12, SEEK_CUR); // rewind, be in front of chunkID again

			// only take as a relevant disp if both criteria met,
			// otherwise treat as generic chunk!
			if ( (dispType == 0x0001) && ( dispSize < 256 * 1024 ) )
			{
				ValueChunk* r = new ValueChunk( parent, handler );
				handler->dispChunk = r;
				return r;
			}
			break; // treat as irrelevant (non-0x1) DISP chunks as generic chunk
		}
	case kChunk_bext:
		{
			if ( level != 1 ) break; // only care on this level
			// store for now in a value chunk
			ValueChunk* r = new ValueChunk( parent, handler );
			handler->bextChunk = r;
			return r; 
		}
	case kChunk_PrmL:
		{
			if ( level != 1 ) break; // only care on this level
			ValueChunk* r = new ValueChunk( parent, handler );
			handler->prmlChunk = r;
			return r; 		
		}
	case kChunk_Cr8r:
		{
			if ( level != 1 ) break; // only care on this level
			ValueChunk* r = new ValueChunk( parent, handler );
			handler->cr8rChunk = r;
			return r; 		
		}
	case kChunk_JUNQ:
	case kChunk_JUNK:
		{
			JunkChunk* r = new JunkChunk( parent, handler );
			return r;
		}
	}
	// this "default:" section must be ouside switch bracket, to be 
	// reachable by all those break statements above:
	

	// digest 'valuable' container chunks: LIST:INFO, LIST:Tdat
	bool insideRelevantList = ( level==2 && parent->id == kChunk_LIST 
		&& ( parent->containerType== kType_INFO || parent->containerType == kType_Tdat ));

	if ( insideRelevantList )
	{
		ValueChunk* r = new ValueChunk( parent, handler );
		return r;
	}

	// general chunk of no interest, treat as unknown blob						  
	return new Chunk( parent, handler, true, chunk_GENERAL );
}

// BASE CLASS CHUNK ///////////////////////////////////////////////
// ad hoc creation
Chunk::Chunk( ContainerChunk* parent, ChunkType c, XMP_Uns32 id )
{
	this->chunkType = c; // base class assumption
	this->parent = parent;
	this->id = id;
	this->oldSize = 0;
	this->newSize = 8;
	this->oldPos = 0; // inevitable for ad-hoc
	this->needSizeFix = false;

	// good parenting for latter destruction
	if ( this->parent != NULL )
	{
		this->parent->children.push_back( this );
		if( this->chunkType == chunk_VALUE )
			this->parent->childmap.insert( make_pair( this->id, (ValueChunk*) this ) );
	}
}

// parsing creation
Chunk::Chunk( ContainerChunk* parent, RIFF_MetaHandler* handler, bool skip, ChunkType c )
{
	chunkType = c; // base class assumption
	this->parent = parent;
	this->oldSize = 0;
	this->hasChange = false; // [2414649] valid assumption at creation time

	LFA_FileRef file = handler->parent->fileRef;

	this->oldPos = LFA_Tell( file );
	this->id = LFA_ReadUns32_LE( file );
	this->oldSize = LFA_ReadUns32_LE( file ) + 8;

	// Make sure the size is within expected bounds.
	XMP_Int64 chunkEnd = this->oldPos + this->oldSize;
	XMP_Int64 chunkLimit = handler->oldFileSize;
	if ( parent != 0 ) chunkLimit = parent->oldPos + parent->oldSize;
	if ( chunkEnd > chunkLimit ) {
		bool isUpdate = XMP_OptionIsSet ( handler->parent->openFlags, kXMPFiles_OpenForUpdate );
		bool repairFile = XMP_OptionIsSet ( handler->parent->openFlags, kXMPFiles_OpenRepairFile );
		if ( (! isUpdate) || (repairFile && (parent == 0)) ) {
			this->oldSize = chunkLimit - this->oldPos;
		} else {
			XMP_Throw ( "Bad RIFF chunk size", kXMPErr_BadFileFormat );
		}
	}

	this->newSize = this->oldSize;
	this->needSizeFix = false;

	if ( skip )
	{
		bool ok;
		LFA_Seek( file, this->oldSize - 8 , SEEK_CUR, &ok );
		XMP_Validate( ok , "skipped beyond end of file (truncated file?)", kXMPErr_BadFileFormat );			
	}

	// "good parenting", essential for latter destruction.
	if ( this->parent != NULL )
	{
		this->parent->children.push_back( this );
		if( this->chunkType == chunk_VALUE )
			this->parent->childmap.insert( make_pair( this->id, (ValueChunk*) this ) );
	}
}

void Chunk::changesAndSize( RIFF_MetaHandler* handler )
{
	// only unknown chunks should reach this method,
	// all others must reach overloads, hence little to do here:
	hasChange = false; // unknown chunk ==> no change, naturally
	this->newSize = this->oldSize;
}

std::string Chunk::toString(XMP_Uns8 level )
{
	char buffer[256];
	snprintf( buffer, 255, "%.4s -- "
							"oldSize: 0x%.8llX,  "
							"newSize: 0x%.8llX,  "
							"oldPos: 0x%.8llX\n",
		(char*)(&this->id), this->oldSize, this->newSize, this->oldPos );
	return std::string(buffer);
}

void Chunk::write( RIFF_MetaHandler* handler, LFA_FileRef file , bool isMainChunk )
{
	throw new XMP_Error(kXMPErr_InternalFailure, "Chunk::write never to be called for unknown chunks.");		
}

Chunk::~Chunk()
{
	//nothing
}

// CONTAINER CHUNK /////////////////////////////////////////////////
// a) creation
// [2376832] expectedSize - minimum padding "parking size" to use, if not available append to end
ContainerChunk::ContainerChunk( ContainerChunk* parent, XMP_Uns32 id, XMP_Uns32 containerType ) : Chunk( NULL /* !! */, chunk_CONTAINER, id )
{
	// accept no unparented ConatinerChunks
	XMP_Enforce( parent != NULL );

	this->containerType = containerType;
	this->newSize = 12;
	this->parent = parent;

	chunkVect* siblings = &parent->children;

	// add at end. ( oldSize==0 will flag optimization later in the process)
	siblings->push_back( this );
}

// b) parsing
ContainerChunk::ContainerChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_CONTAINER )
{
	bool repairMode = ( 0 != ( handler->parent->openFlags & kXMPFiles_OpenRepairFile ));

	try
	{
		LFA_FileRef file = handler->parent->fileRef;
		XMP_Uns8 level = handler->level;

		// get type of container chunk
		this->containerType = LFA_ReadUns32_LE( file );

		// ensure legality of top-level chunks
		if ( level == 0 && handler->riffChunks.size() > 0 )
		{
			XMP_Validate( handler->parent->format == kXMP_AVIFile, "only AVI may have multiple top-level chunks", kXMPErr_BadFileFormat );
			XMP_Validate( this->containerType == kType_AVIX, "all chunks beyond main chunk must be type AVIX", kXMPErr_BadFileFormat );
		}

		// has *relevant* subChunks? (there might be e.g. non-INFO LIST chunks we don't care about)
		bool hasSubChunks = ( ( this->id == kChunk_RIFF ) || 
							  ( this->id == kChunk_LIST && this->containerType == kType_INFO ) ||
							  ( this->id == kChunk_LIST && this->containerType == kType_Tdat )
						  );
		XMP_Int64 endOfChunk = this->oldPos + this->oldSize;

		// this statement catches beyond-EoF-offsets on any level
		// exception: level 0, tolerate if in repairMode
		if ( (level == 0) && repairMode && (endOfChunk > handler->oldFileSize) )
		{
			endOfChunk = handler->oldFileSize; // assign actual file size
			this->oldSize = endOfChunk - this->oldPos; //reversely calculate correct oldSize
		}

		XMP_Validate( endOfChunk <= handler->oldFileSize, "offset beyond EoF", kXMPErr_BadFileFormat );
		
		Chunk* curChild = 0;
		if ( hasSubChunks )
		{
			handler->level++;
			while ( LFA_Tell( file ) < endOfChunk )
			{
				curChild = RIFF::getChunk( this, handler );
				
				// digest pad byte - no value validation (0), since some 3rd party files have non-0-padding.
				if ( LFA_Tell(file) % 2 == 1 )
				{
					// [1521093] tolerate missing pad byte at very end of file:				
					XMP_Uns8 pad;
					LFA_Read ( file, &pad, 1 );  // Read the pad, tolerate being at EOF.

				}

				// within relevant LISTs, relentlesly delete junk chunks (create a single one
				// at end as part of updateAndChanges()
				if ( (containerType== kType_INFO || containerType == kType_Tdat)
						&& ( curChild->chunkType == chunk_JUNK ) )
				{
						this->children.pop_back();
						delete curChild;		
				} // for other chunks: join neighouring Junk chunks into one
				else if ( (curChild->chunkType == chunk_JUNK) && ( this->children.size() >= 2 ) )
				{
					// nb: if there are e.g 2 chunks, then last one is at(1), prev one at(0) ==> '-2'
					Chunk* prevChunk = this->children.at( this->children.size() - 2 );
					if ( prevChunk->chunkType == chunk_JUNK )
					{
						// stack up size to prior chunk
						prevChunk->oldSize += curChild->oldSize;
						prevChunk->newSize += curChild->newSize;
						XMP_Enforce( prevChunk->oldSize == prevChunk->newSize );
						// destroy current chunk
						this->children.pop_back();
						delete curChild;
					}
				}
			}
			handler->level--;
			XMP_Validate( LFA_Tell( file ) == endOfChunk, "subchunks exceed outer chunk size", kXMPErr_BadFileFormat );

			// pointers for later legacy processing 
			if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_INFO )
				handler->listInfoChunk = this;
			if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_Tdat )
				handler->listTdatChunk = this;
		} 
		else // skip non-interest container chunk
		{
			bool ok;
			LFA_Seek( file, this->oldSize - 8 - 4, SEEK_CUR, &ok );
			XMP_Validate( ok , "skipped beyond end of file 2 (truncated file?)", kXMPErr_BadFileFormat );
		} // if - else

	} // try
	catch (XMP_Error& e) {
		this->release(); // free resources
		if ( this->parent != 0)
			this->parent->children.pop_back(); // hereby taken care of, so removing myself...

		throw e;         // re-throw
	}
}

void ContainerChunk::changesAndSize( RIFF_MetaHandler* handler )
{

	// Walk the container subtree adjusting the children that have size changes. The only containers
	// are RIFF and LIST chunks, they are treated differently.
	//
	// LISTs get recomposed as a whole. Existing JUNK children of a LIST are removed, existing real
	// children are left in order with their new size, new children have already been appended. The
	// LIST as a whole gets a new size that is the sum of the final children.
	//
	// Special rules apply to various children of a RIFF container. FIrst, adjacent JUNK children
	// are combined, this simplifies maximal reuse. The children are recursively adjusted in order
	// to get their final size.
	//
	// Try to determine the final placement of each RIFF child using general rules:
	//	- if the size is unchanged: leave at current location
	//	- if the chunk is at the end of the last RIFF chunk and grows: leave at current location
	//	- if there is enough following JUNK: add part of the JUNK, adjust remaining JUNK size
	//	- if it shrinks by 9 bytes or more: carve off trailing JUNK
	//	- try to find adequate JUNK in the current parent
	//
	// Use child-specific rules as a last resort:
	//	- if it is LIST:INFO: delete it, must be in first RIFF chunk
	//	- for others: move to end of last RIFF chunk, make old space JUNK
	
	// ! Don't create any junk chunks of exactly 8 bytes, just a header and no content. That has a
	// ! size field of zero, which hits a crashing bug in some versions of Windows Media Player.

	bool isRIFFContainer = (this->id == kChunk_RIFF);
	bool isLISTContainer = (this->id == kChunk_LIST);
	XMP_Enforce ( isRIFFContainer | isLISTContainer );
	
	XMP_Index childIndex;	// Could be local to the loops, this simplifies debuging. Need a signed type!
	Chunk * currChild;
	
	if ( this->children.empty() ) {
		if ( isRIFFContainer) {
			this->newSize = 12;	// Keep a minimal size container.
		} else {
			this->newSize = 0;	// Will get removed from parent in outer call.
		}
		this->hasChange = true;
		return;	// Nothing more to do without children.
	}
	
	// Collapse adjacent RIFF junk children, remove all LIST junk children. Work back to front to
	// simplify the effect of .erase() on the loop. Purposely ignore the first chunk.

	for ( childIndex = (XMP_Index)this->children.size() - 1; childIndex > 0; --childIndex ) {

		currChild = this->children[childIndex];
		if ( currChild->chunkType != chunk_JUNK ) continue;

		if ( isRIFFContainer ) {
			Chunk * prevChild = this->children[childIndex-1];
			if ( prevChild->chunkType != chunk_JUNK ) continue;
			prevChild->oldSize += currChild->oldSize;
			prevChild->newSize += currChild->newSize;
			prevChild->hasChange = true;
		}
		
		this->children.erase ( this->children.begin() + childIndex );
		delete currChild;
		this->hasChange = true;
		
	}
	
	// Process the children of RIFF and LIST containers to get their final size. Remove empty
	// children. Work back to front to simplify the effect of .erase() on the loop. Do not ignore
	// the first chunk.

	for ( childIndex = (XMP_Index)this->children.size() - 1; childIndex >= 0; --childIndex ) {

		currChild = this->children[childIndex];

		++handler->level;
		currChild->changesAndSize ( handler );
		--handler->level;
		
		if ( (currChild->newSize == 8) || (currChild->newSize == 0) ) {	// ! The newSIze is supposed to include the header.
			this->children.erase ( this->children.begin() + childIndex );
			delete currChild;
			this->hasChange = true;
		} else {
			this->hasChange |= currChild->hasChange;
			currChild->needSizeFix = (currChild->newSize != currChild->oldSize);
			if ( currChild->needSizeFix && (currChild->newSize > currChild->oldSize) &&
				 (this == handler->lastChunk) && (childIndex+1 == (XMP_Index)this->children.size()) ) {
				// Let an existing last-in-file chunk grow in-place. Shrinking is conceptually OK,
				// but complicates later sanity check that the main AVI chunk is not OK to append
				// other chunks later. Ignore new chunks, they might reuse junk space.
				if ( currChild->oldSize != 0 ) currChild->needSizeFix = false;
			}
		}

	}
	
	// Go through the children of a RIFF container, adjusting the placement as necessary. In brief,
	// things can only grow at the end of the last RIFF chunk, and non-junk chunks can't be shifted.

	if ( isRIFFContainer ) {
	
		for ( childIndex = 0; childIndex < (XMP_Index)this->children.size(); ++childIndex ) {

			currChild = this->children[childIndex];
			if ( ! currChild->needSizeFix ) continue;
			currChild->needSizeFix = false;
			
			XMP_Int64 sizeDiff = currChild->newSize - currChild->oldSize;	// Positive for growth.
			XMP_Uns8  padSize = (currChild->newSize & 1);	// Need a pad for odd size.
			
			// See if the following chunk is junk that can be utilized.

			Chunk * nextChild = 0;
			if ( childIndex+1 < (XMP_Index)this->children.size() ) nextChild = this->children[childIndex+1];

			if ( (nextChild != 0) && (nextChild->chunkType == chunk_JUNK) ) {
				if ( nextChild->newSize >= (9 + sizeDiff + padSize) ) {

					// Incorporate part of the trailing junk, or make the trailing junk grow.
					nextChild->newSize -= sizeDiff;
					nextChild->newSize -= padSize;
					nextChild->hasChange = true;
					continue;	

				} else if (  nextChild->newSize == (sizeDiff + padSize)  ) {

					// Incorporate all of the trailing junk.
					this->children.erase ( this->children.begin() + childIndex + 1 );
					delete nextChild;
					continue;	

				}
			}
			
			// See if the chunk shrinks enough to turn the leftover space into junk.

			if ( (sizeDiff + padSize) <= -9 ) {
				this->children.insert ( (this->children.begin() + childIndex + 1), new JunkChunk ( NULL, ((-sizeDiff) - padSize) ) );
				continue;
			}
			
			// Look through the parent for a usable span of junk.

			XMP_Index junkIndex;
			Chunk * junkChunk = 0;
			for ( junkIndex = 0; junkIndex < (XMP_Index)this->children.size(); ++junkIndex ) {
				junkChunk = this->children[junkIndex];
				if ( junkChunk->chunkType != chunk_JUNK ) continue;
				if ( (junkChunk->newSize >= (9 + currChild->newSize + padSize)) ||
					 (junkChunk->newSize == (currChild->newSize + padSize)) ) break;
			}

			if ( junkIndex < (XMP_Index)this->children.size() ) {

				// Use part or all of the junk for the relocated chunk, replace the old space with junk.

				if ( junkChunk->newSize == (currChild->newSize + padSize) ) {

					// The found junk is an exact fit.
					this->children[junkIndex] = currChild;
					delete junkChunk;

				} else {

					// The found junk has excess space. Insert the moving chunk and shrink the junk.
					XMP_Assert ( junkChunk->newSize >= (9 + currChild->newSize + padSize) );
					junkChunk->newSize -= (currChild->newSize + padSize);
					junkChunk->hasChange = true;
					this->children.insert ( (this->children.begin() + junkIndex), currChild );
					if ( junkIndex < childIndex ) ++childIndex;	// The insertion moved the current child.

				}

				if ( currChild->oldSize != 0 ) {
					this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize );	// Replace the old space with junk.
				} else {
					this->children.erase ( this->children.begin() + childIndex );	// Remove the newly created chunk's old location.
					--childIndex;	// Make the next loop iteration not skip a chunk.
				}

				continue;

			}
			
			// If this is a LIST:INFO chunk not in the last of multiple RIFF chunks, then give up
			// and replace it with oldSize junk. Preserve the first RIFF chunk's original size. 

			bool isListInfo = (currChild->id == kChunk_LIST) && (currChild->chunkType == chunk_CONTAINER) &&
							  (((ContainerChunk*)currChild)->containerType == kType_INFO);

			if ( isListInfo && (handler->riffChunks.size() > 1) &&
				 (this->id == kChunk_RIFF) && (this != handler->lastChunk) ) {

				if ( currChild->oldSize != 0 ) {
					this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize );
				} else {
					this->children.erase ( this->children.begin() + childIndex );
					--childIndex;	// Make the next loop iteration not skip a chunk.
				}

				delete currChild;
				continue;

			}
			
			// Move the chunk to the end of the last RIFF chunk and make the old space junk.
			
			if ( (this == handler->lastChunk) && (childIndex+1 == (XMP_Index)this->children.size()) ) continue;	// Already last.

			handler->lastChunk->children.push_back( currChild );
			if ( currChild->oldSize != 0 ) {
				this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize );	// Replace the old space with junk.
			} else {
				this->children.erase ( this->children.begin() + childIndex );	// Remove the newly created chunk's old location.
				--childIndex;	// Make the next loop iteration not skip a chunk.
			}

		}
	
	}
	
	// Compute the finished container's new size (for both RIFF and LIST).

	this->newSize = 12;	// Start with standard container header.
	for ( childIndex = 0; childIndex < (XMP_Index)this->children.size(); ++childIndex ) {
		currChild = this->children[childIndex];
		this->newSize += currChild->newSize;
		this->newSize += (this->newSize & 1);	// Round up if odd.
	}

	XMP_Validate ( (this->newSize <= 0xFFFFFFFFLL), "No single chunk may be above 4 GB", kXMPErr_Unimplemented );

}

std::string ContainerChunk::toString(XMP_Uns8 level )
{
	XMP_Int64 offset= 12; // compute offsets, just for informational purposes
	// (actually only correct for first chunk)

	char buffer[256];
	snprintf( buffer, 255, "%.4s:%.4s, "
			"oldSize: 0x%8llX, "
			"newSize: 0x%.8llX, "
			"oldPos: 0x%.8llX\n",
			(char*)(&this->id), (char*)(&this->containerType), this->oldSize, this->newSize, this->oldPos );
	
	std::string r(buffer);
	chunkVectIter iter;
	for( iter = this->children.begin(); iter != this->children.end(); iter++ )
	{
		char buffer[256];
		snprintf( buffer, 250, "offset 0x%.8llX", offset );
		r += std::string ( level*4, ' ' ) + std::string( buffer ) + ":" + (*iter)->toString( level + 1 );
		offset += (*iter)->newSize;
		if ( offset % 2 == 1 )
			offset++;
	}
	return std::string(r);
}

void ContainerChunk::write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk )
{
	bool ok;
	if ( isMainChunk )
		LFA_Rewind( file );

	// enforce even position
	XMP_Int64 chunkStart = LFA_Tell(file);
	XMP_Int64 chunkEnd = chunkStart + this->newSize;
	XMP_Enforce( chunkStart % 2 == 0 );
	chunkVect *rc = &this->children;

	// [2473303] have to write back-to-front to avoid stomp-on-feet
	XMP_Int64 childStart = chunkEnd;
	for ( XMP_Int32 chunkNo = (XMP_Int32)(rc->size() -1); chunkNo >= 0; chunkNo-- )
	{
		Chunk* cur = rc->at(chunkNo);

		// pad byte first
		if ( cur->newSize % 2 == 1 )
		{
			childStart--;
			LFA_Seek( file, childStart, SEEK_SET );
			LFA_WriteUns8( file, 0 );
		}

		// then contents
		childStart-= cur->newSize;
		LFA_Seek( file, childStart, SEEK_SET );
		switch ( cur->chunkType )
		{
			case chunk_GENERAL: //COULDDO enfore no change, since not write-out-able
				if ( cur->oldPos != childStart )
					LFA_Move( file, cur->oldPos, file, childStart, cur->oldSize, 0, 0 );
				break;
			default:
				cur->write( handler, file, false );
				break;
		} // switch

	} // for
	XMP_Enforce ( chunkStart + 12 == childStart);
	LFA_Seek( file, chunkStart, SEEK_SET, &ok );

	LFA_WriteUns32_LE( file, this->id );
	LFA_WriteUns32_LE( file, (XMP_Uns32) this->newSize - 8 ); // validated in changesAndSize() above
	LFA_WriteUns32_LE( file, this->containerType );

}

void ContainerChunk::release()
{
	// free subchunks
	Chunk* curChunk;
	while( ! this->children.empty() )
	{
		curChunk = this->children.back();
		delete curChunk;
		this->children.pop_back();
	}	
}

ContainerChunk::~ContainerChunk()
{
	this->release(); // free resources
}

// XMP CHUNK ///////////////////////////////////////////////
// a) create

// a) creation
XMPChunk::XMPChunk( ContainerChunk* parent ) : Chunk( parent, chunk_XMP , kChunk_XMP )
{
	// nothing
}

// b) parse
XMPChunk::XMPChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_XMP )
{
	chunkType = chunk_XMP;
	LFA_FileRef file = handler->parent->fileRef;
	XMP_Uns8 level = handler->level;

	handler->packetInfo.offset = this->oldPos + 8;
	handler->packetInfo.length = (XMP_Int32) this->oldSize - 8;

	handler->xmpPacket.reserve ( handler->packetInfo.length );
	handler->xmpPacket.assign ( handler->packetInfo.length, ' ' );
	LFA_Read ( file, (void*)handler->xmpPacket.data(), handler->packetInfo.length, kLFA_RequireAll );
			
	handler->containsXMP = true; // last, after all possible failure

	// pointer for later processing
	handler->xmpChunk = this;
}

void XMPChunk::changesAndSize( RIFF_MetaHandler* handler )
{
	XMP_Enforce( &handler->xmpPacket != 0 );
	XMP_Enforce( handler->xmpPacket.size() > 0 );
	this->newSize = 8 + handler->xmpPacket.size();

	XMP_Validate( this->newSize <= 0xFFFFFFFFLL, "no single chunk may be above 4 GB", kXMPErr_InternalFailure );

	// a complete no-change would have been caught in XMPFiles common code anyway
	this->hasChange = true;
}
	
void XMPChunk::write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk )
{
	LFA_WriteUns32_LE( file, kChunk_XMP );
	LFA_WriteUns32_LE( file, (XMP_Uns32) this->newSize - 8 ); // validated in changesAndSize() above
	LFA_Write( file, handler->xmpPacket.data(), (XMP_Int32)handler->xmpPacket.size() );
}

// Value CHUNK ///////////////////////////////////////////////
// a) creation
ValueChunk::ValueChunk( ContainerChunk* parent, std::string value, XMP_Uns32 id ) : Chunk( parent, chunk_VALUE, id )
{
	this->oldValue = std::string();
	this->SetValue( value );
}

// b) parsing
ValueChunk::ValueChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_VALUE )
{
	// set value: -----------------
	LFA_FileRef file = handler->parent->fileRef;
	XMP_Uns8 level = handler->level;

	// unless changed through reconciliation, assume for now.
	// IMPORTANT to stay true to the original (no \0 cleanup or similar)
	// since unknown value chunks might not be fully understood,
	// hence must be precisely preserved !!!

	XMP_Int32 length = (XMP_Int32) this->oldSize - 8;
	this->oldValue.reserve( length );
	this->oldValue.assign( length + 1, '\0' );
	LFA_Read ( file, (void*)this->oldValue.data(), length, kLFA_RequireAll );

	this->newValue = this->oldValue;
	this->newSize = this->oldSize;
}

void ValueChunk::SetValue( std::string value, bool optionalNUL /* = false */ )
{
	this->newValue.assign( value );
	if ( (! optionalNUL) || ((value.size() & 1) == 1) ) {
		// ! The NUL should be optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte.
		this->newValue.append( 1, '\0' ); // append zero termination as explicit part of string
	}
	this->newSize = this->newValue.size() + 8;
}

void ValueChunk::changesAndSize( RIFF_MetaHandler* handler )
{
	// Don't simply assign to this->hasChange, it might already be true.
	if ( this->newValue.size() != this->oldValue.size() ) {
		this->hasChange = true;
	} else if ( strncmp ( this->oldValue.c_str(), this->newValue.c_str(), this->newValue.size() ) != 0 ) {
		this->hasChange = true;
	}
}
	
void ValueChunk::write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk )
{
	LFA_WriteUns32_LE( file, this->id );
	LFA_WriteUns32_LE( file, (XMP_Uns32)this->newSize - 8 );
	LFA_Write( file, this->newValue.data() , (XMP_Int32)this->newSize - 8 );
}

/* remove value chunk if existing.
   return true if it was existing. */
bool ContainerChunk::removeValue( XMP_Uns32	id )
{
	valueMap* cm = &this->childmap;			
	valueMapIter iter = cm->find( id );

	if( iter == cm->end() ) 
		return false;  //not found
	
	ValueChunk* propChunk = iter->second;

	// remove from vector (difficult)
	chunkVect* cv = &this->children;
	chunkVectIter cvIter;
	for (cvIter = cv->begin(); cvIter != cv->end(); ++cvIter )
	{
		if ( (*cvIter)->id == id )
			break; // found!
	}
	XMP_Validate( cvIter != cv->end(), "property not found in children vector", kXMPErr_InternalFailure );
	cv->erase( cvIter );

	// remove from map (easy)
	cm->erase( iter );

	delete propChunk;
	return true; // found and removed
}

/* returns iterator to (first) occurence of this chunk.
   iterator to the end of the map if chunk pointer is not found  */
chunkVectIter ContainerChunk::getChild( Chunk* needle )
{
	chunkVectIter iter;
	for( iter = this->children.begin(); iter != this->children.end(); iter++ )
	{
		Chunk* temp1 = *iter;
		Chunk* temp2 = needle;
		if ( (*iter) == needle ) return iter;
	}
	return this->children.end();
}

/* replaces a chunk by a JUNK chunk.
   Also frees memory of prior chunk. */
void ContainerChunk::replaceChildWithJunk( Chunk* child, bool deleteChild )
{
	chunkVectIter iter = getChild( child );
	if ( iter == this->children.end() ) {
		throw new XMP_Error(kXMPErr_InternalFailure, "replaceChildWithJunk: childChunk not found.");
	}

	*iter = new JunkChunk ( NULL, child->oldSize );
	if ( deleteChild ) delete child;

	this->hasChange = true;
}

// JunkChunk ///////////////////////////////////////////////////
// a) creation
JunkChunk::JunkChunk( ContainerChunk* parent, XMP_Int64 size ) : Chunk( parent, chunk_JUNK, kChunk_JUNK )
{
	XMP_Assert( size >= 8 );
	this->oldSize = size;
	this->newSize = size;
	this->hasChange = true;
}

// b) parsing
JunkChunk::JunkChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, true, chunk_JUNK )
{
	chunkType = chunk_JUNK;
}

void JunkChunk::changesAndSize( RIFF_MetaHandler* handler )
{
	this->newSize = this->oldSize; // optimization at a later stage
	XMP_Validate( this->newSize <= 0xFFFFFFFFLL, "no single chunk may be above 4 GB", kXMPErr_InternalFailure );
	if ( this->id == kChunk_JUNQ ) this->hasChange = true;	// Force ID change to JUNK.
}

// zeroBuffer, etc to write out empty native padding
const static XMP_Uns32 kZeroBufferSize64K = 64 * 1024;
static XMP_Uns8 kZeroes64K [ kZeroBufferSize64K ]; // C semantics guarantee zero initialization.
	
void JunkChunk::write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk )
{
	LFA_WriteUns32_LE( file, kChunk_JUNK );		// write JUNK, never JUNQ
	XMP_Enforce( this->newSize < 0xFFFFFFFF );
	XMP_Enforce( this->newSize >= 8 );			// minimum size of any chunk
	XMP_Uns32 innerSize = (XMP_Uns32)this->newSize - 8;
	LFA_WriteUns32_LE( file, innerSize );

	// write out in 64K chunks
	while ( innerSize > kZeroBufferSize64K )
	{
		LFA_Write( file, kZeroes64K , kZeroBufferSize64K );
		innerSize -= kZeroBufferSize64K;
	}
	LFA_Write( file, kZeroes64K , innerSize );
}

} // namespace RIFF
