// =================================================================================================
// Copyright Adobe
// Copyright 2010 Adobe
// 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. 
// =================================================================================================

#include "public/include/XMP_Environment.h"	// ! XMP_Environment.h must be the first included header.
#include "public/include/XMP_Const.h"
#include "source/XIO.hpp"

#include "XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.h"
#include "XMPFiles/source/FormatSupport/IFF/Chunk.h"

#include <algorithm>

using namespace IFF_RIFF;

//
// Static init
//
const LittleEndian& WAVEBehavior::mEndian = LittleEndian::getInstance();


//-----------------------------------------------------------------------------
// 
// WAVEBehavior::getRealSize(...)
// 
// Purpose: Validate the passed in size value, identify the valid size if the 
//			passed in isn't valid and return the valid size.
//			Throw an exception if the passed in size isn't valid and there's 
//			no way to identify a valid size.
// 
//-----------------------------------------------------------------------------

XMP_Uns64 WAVEBehavior::getRealSize( const XMP_Uns64 size, const ChunkIdentifier& id, IChunkContainer& tree, XMP_IO* stream )
{
	XMP_Uns64 realSize = size;

	if( size >= kNormalRF64ChunkSize ) // 4GB
	{
		if( this->isRF64( tree ) )
		{
			//
			// RF64 supports sizes beyond 4GB
			//
			DS64* rf64 = this->getDS64( tree, stream );

			if( rf64 != NULL )
			{
				//
				// get 64bit size from RF64 structure
				//
				switch( id.id )
				{
					case kChunk_RF64:	realSize = rf64->riffSize;	break;
					case kChunk_data:	realSize = rf64->dataSize;	break;

					default:
					{
						bool found = false;

						//
						// try to find size value for passed chunk id in the ds64 table
						//
						if( rf64->tableLength > 0 )
						{
							for( std::vector<ChunkSize64>::iterator iter=rf64->table.begin(); iter!=rf64->table.end(); iter++ )
							{
								if( iter->id == id.id )
								{
									realSize = iter->size;
									found = true;
									break;
								}
							}
						}

						if( !found )
						{
							//
							// no size for passed id available
							//
							XMP_Throw( "Unknown size value", kXMPErr_BadFileFormat );
						}
					}
				}
			}
			else
			{
				//
				// no RF64 size info available
				//
				XMP_Throw( "Unknown size value", kXMPErr_BadFileFormat );
			}
		}
		else
		{
			//
			// WAVE doesn't support that size
			//
			XMP_Throw( "Unknown size value", kXMPErr_BadFileFormat );
		}
	}

	return realSize;
}

//-----------------------------------------------------------------------------
// 
// WAVEBehavior::getMaxChunkSize(...)
// 
// Purpose: Return the maximum size of a single chunk, i.e. the maximum size 
//			of a top-level chunk.
// 
//-----------------------------------------------------------------------------

XMP_Uns64 WAVEBehavior::getMaxChunkSize() const
{
	// simple WAVE 4GByte
	XMP_Uns64 ret = 0x00000000FFFFFFFFLL;

	if( mIsRF64 )
	{
		// RF64: full possible 64bit size
		ret = 0xFFFFFFFFFFFFFFFFLL;
	}

	return ret;
}

//-----------------------------------------------------------------------------
// 
// WAVEBehavior::isValidTopLevelChunk(...)
// 
// Purpose: Return true if the passed identifier is valid for top-level chunks 
//			of a certain format.
// 
//-----------------------------------------------------------------------------

bool WAVEBehavior::isValidTopLevelChunk( const ChunkIdentifier& id, XMP_Uns32 chunkNo )
{
	return ( chunkNo == 0 )												&& 
		   ( ( ( id.id == kChunk_RIFF ) && ( id.type == kType_WAVE ) )	||  
		     ( ( id.id == kChunk_RF64 ) && ( id.type == kType_WAVE ) ) );
}

//-----------------------------------------------------------------------------
// 
// WAVEBehavior::fixHierarchy(...)
// 
// Purpose: Fix the hierarchy of chunks depending ones based on size changes of 
//			one or more chunks and second based on format specific rules.
//			Throw an exception if the hierarchy can't be fixed.
// 
//-----------------------------------------------------------------------------

void WAVEBehavior::fixHierarchy( IChunkContainer& tree )
{
	XMP_Validate( tree.numChildren() == 1, "WAVE files should only have one top level chunk (RIFF)", kXMPErr_BadFileFormat);
	
	Chunk* riffChunk = tree.getChildAt(0);

	XMP_Validate( (riffChunk->getType() == kType_WAVE || riffChunk->getType() == kChunk_RF64) , "Invalid type for WAVE/RF64 top level chunk (RIFF)", kXMPErr_BadFileFormat);

	if( riffChunk->hasChanged() )
	{
		//
		// move new added chunks to temporary container
		//
		Chunk* tmpContainer = Chunk::createChunk( mEndian );
		this->moveChunks( *riffChunk, *tmpContainer, riffChunk->numChildren() - mChunksAdded );

		//
		// try to arrange chunks at their current position
		//
		this->arrangeChunksInPlace( *riffChunk, *tmpContainer );

		//
		// for all chunks that were moved to the end try to find a FREE chunk for them
		//
		this->arrangeChunksInTree( *tmpContainer, *riffChunk );

		//
		// append all remaining new added chunks to the end of the tree
		//
		this->moveChunks( *tmpContainer, *riffChunk, 0 );
		delete tmpContainer;

		//
		// check for FREE chunks at the end
		//
		Chunk* endFREE = this->mergeFreeChunks( *riffChunk, riffChunk->numChildren() - 1 );

		if( endFREE != NULL )
		{
			riffChunk->removeChildAt( riffChunk->numChildren() - 1 );
			delete endFREE;
		}

		//
		// Fix the offset values of all chunks. Throw an exception in the case that
		// the offset of a non-modified chunk needs to be reset.
		//
		XMP_Validate( riffChunk->getOffset() == 0, "Invalid offset for RIFF top level chunk", kXMPErr_InternalFailure );

		this->validateOffsets( tree );
	
		//
		// update the RF64 chunk (if this is RF64) based on the current chunk sizes
		//
		this->updateRF64( tree );
	}
}

void WAVEBehavior::insertChunk( IChunkContainer& tree, Chunk& chunk )
{
	XMP_Validate( tree.numChildren() == 1, "WAVE files should only have one top level chunk (RIFF)", kXMPErr_BadFileFormat);
	Chunk* riffChunk = tree.getChildAt(0);

	XMP_Validate( riffChunk->getType() == kType_WAVE , "Invalid type for WAVE top level chunk (RIFF)", kXMPErr_BadFileFormat);

	//
	// add new chunk to the end of the RIFF:WAVE
	//
	riffChunk->appendChild(&chunk);

	mChunksAdded++;
}

bool WAVEBehavior::removeChunk( IChunkContainer& tree, Chunk& chunk )
{
	//
	// validate parameter
	//
	XMP_Validate( chunk.getID() != kChunk_RIFF, "Can't remove RIFF chunk!", kXMPErr_InternalFailure );
	XMP_Validate( chunk.getChunkMode() != CHUNK_UNKNOWN, "Cant' remove UNKNOWN Chunk", kXMPErr_InternalFailure );
	XMP_Validate( tree.numChildren() == 1, "WAVE files should only have one top level chunk (RIFF)", kXMPErr_BadFileFormat);	

	//
	// get top-level chunk
	//
	Chunk* riffChunk = tree.getChildAt(0);

	//
	// validate top-level chunk
	//
	XMP_Validate( (riffChunk->getType() == kType_WAVE || riffChunk->getType() == kChunk_RF64) , "Invalid type for WAVE/RF64 top level chunk (RIFF)", kXMPErr_BadFileFormat);

	//
	// calculate index of chunk to remove
	//
	XMP_Uns32 i = (XMP_Uns32)(std::find( riffChunk->firstChild(), riffChunk->lastChild(), &chunk ) - riffChunk->firstChild());

	//
	// validate index
	//
	XMP_Validate( i < riffChunk->numChildren(), "Invalid chunk in tree", kXMPErr_InternalFailure );

	//
	// adjust new chunks counter
	//
	if( i > riffChunk->numChildren() - mChunksAdded - 1 )
	{
		mChunksAdded--;
	}

	if( i < riffChunk->numChildren()-1 )
	{
		//
		// fill gap with free chunk
		//
		Chunk* free = this->createFREE( chunk.getPadSize( true ) );
		riffChunk->replaceChildAt( i, free );
		free->setAsNew();

		//
		// merge JUNK chunks
		//
		this->mergeFreeChunks( *riffChunk, i );
	}
	else
	{
		//
		// remove chunk from tree
		//
		riffChunk->removeChildAt( i );
	}

	//
	// if there is an entry in the ds64 table for the removed chunk
	// then update the ds64 table entry
	//
	if( mDS64Data != NULL && mDS64Data->tableLength > 0 )
	{
		for( std::vector<ChunkSize64>::iterator iter=mDS64Data->table.begin(); iter!=mDS64Data->table.end(); iter++ )
		{
			if( iter->id == chunk.getID() )
			{
				//
				// don't remove entry but set its size to zero
				//
				iter->size = 0LL;
				break;
			}
		}
	}
	
	return true;
}

Chunk* WAVEBehavior::createFREE( XMP_Uns64 chunkSize )
{
	XMP_Int64 alloc = chunkSize - Chunk::HEADER_SIZE;

	Chunk* chunk = NULL;

	//
	// create a 'JUNK' chunk
	//
	if( alloc > 0 )
	{
		XMP_Uns8* data = new XMP_Uns8[static_cast<size_t>( alloc )];
		memset( data, 0, static_cast<size_t>( alloc ) );

		chunk = Chunk::createUnknownChunk( mEndian, kChunk_JUNK, kType_NONE, alloc );

		chunk->setData( data, alloc );		

		delete[] data;
	}
	else	
	{
		chunk = Chunk::createHeaderChunk( mEndian, kChunk_JUNK );
	}

	// force set dirty flag
	chunk->setChanged();

	return chunk;
}

XMP_Bool WAVEBehavior::isFREEChunk( const Chunk& chunk ) const
{
	// Check for sigature JUNK and JUNQ
	return ( chunk.getID() == kChunk_JUNK || chunk.getID() == kChunk_JUNQ );
}


XMP_Uns64 WAVEBehavior::getMinFREESize() const
{
	// avoid creation of chunks with size==0
	return static_cast<XMP_Uns64>( Chunk::HEADER_SIZE ) + 2;
}

//-----------------------------------------------------------------------------
// 
// WAVEBehavior::isRF64(...)
// 
// Purpose: Is the current file a RF64 file
// 
//-----------------------------------------------------------------------------

bool WAVEBehavior::isRF64( const IChunkContainer& tree )
{
	// The file format will not change at runtime
	// So if the flag is not already set, have a look at the tree 
	if( ! mIsRF64 && tree.numChildren() != 0 )
	{
		Chunk *chunk = tree.getChildAt(0);
		// Only the TopLevel chunk is interesting
		mIsRF64 = chunk->getID() == kChunk_RF64 && 
				chunk->getType() == kType_WAVE;
	}
	
	return mIsRF64;
}

//-----------------------------------------------------------------------------
// 
// WAVEBehavior::getDS64(...)
// 
// Purpose: Return RF64 structure.
// 
//-----------------------------------------------------------------------------

WAVEBehavior::DS64* WAVEBehavior::getDS64( IChunkContainer& tree, XMP_IO* stream )
{
	DS64* ret = mDS64Data;

	if( ret == NULL )
	{
		//
		// try to find 'ds64' chunk in the tree
		//
		Chunk* ds64 = NULL;
		Chunk* rf64 = NULL;

		if( tree.numChildren() > 0 )
		{
			rf64 = tree.getChildAt(0);

			if( rf64 != NULL && rf64->getID() == kChunk_RF64 && rf64->numChildren() > 0 )
			{
				//
				// 'ds64' chunk needs to be the very first child of the 'RF64' chunk
				//
				ds64 = rf64->getChildAt(0);
			}

			//
			// Try to create 'ds64' chunk by parsing the stream
			//
			if( ds64 == NULL && stream != NULL )
			{
				//
				// remember file position before start reading from the stream
				//
				XMP_Uns64 filePos = stream->Offset();

				try
				{
					ds64 = Chunk::createChunk( mEndian );
					ds64->readChunk( stream );
				}
				catch( ... )
				{
					delete ds64;
					ds64 = NULL;
				}

				if( rf64 != NULL && ds64 != NULL && ds64->getID() == kChunk_ds64 )
				{
					//
					// Successfully read 'ds64' chunk.
					// Now read its data area as well and
					// add chunk to the 'RF64' chunk
					//
					ds64->cacheChunkData( stream );
					rf64->appendChild( ds64, false );
				}
				else
				{
					//
					// Either the reading failed or the 'ds64' chunk
					// doesn't exists at the expected position.
					// Now clean up and reject the stream position.
					//
					delete ds64;
					ds64 = NULL;

					stream->Seek( filePos, kXMP_SeekFromStart );
				}
			}
			else if( ds64 != NULL && ds64->getID() != kChunk_ds64 )
			{
				//
				// first child of 'RF64' chunk is NOT 'ds64'!
				//
				ds64 = NULL;
			}
		}

		//
		// parse 'ds64' chunk, store the RF64 struct and return it
		//
		if( ds64 != NULL ) 
		{
			DS64* ds64data = new DS64();

			if( this->parseDS64Chunk( *ds64, *ds64data ) )
			{
				mDS64Data	= ds64data;
				ret			= mDS64Data;
			}
			else
			{
				delete ds64data;
			}
		}
	}

	return ret;
}

//-----------------------------------------------------------------------------
// 
// WAVEBehavior::updateRF64(...)
// 
// Purpose: update the RF64 chunk (if this is RF64) based on the current chunk sizes
// 
//-----------------------------------------------------------------------------

void WAVEBehavior::updateRF64( IChunkContainer& tree )
{
	if( this->isRF64( tree ) )
	{
		XMP_Validate( mDS64Data != NULL, "Missing DS64 structure", kXMPErr_InternalFailure );
		XMP_Validate( tree.numChildren() == 1, "Invalid RF64 tree", kXMPErr_InternalFailure );

		//
		// Check all chunks that sizes have changed and update their related value in the DS64 chunk
		//
		Chunk* rf64 = tree.getChildAt(0);
		XMP_Validate( rf64 != NULL && rf64->getID() == kChunk_RF64 && rf64->numChildren() > 0, "Invalid RF64 chunk", kXMPErr_InternalFailure );

		this->doUpdateRF64( *rf64 );

		//
		// try to find 'ds64' chunk in the tree
		// (needs to be the very first child of the 'RF64' chunk)
		//
		Chunk* ds64 = rf64->getChildAt(0);
		XMP_Validate( ds64 != NULL && ds64->getID() == kChunk_ds64, "Missing 'ds64' chunk", kXMPErr_InternalFailure );

		//
		// serialize DS64 structure and write into ds64 chunk
		//
		this->serializeDS64Chunk( *mDS64Data, *ds64 );		
	}
}

void WAVEBehavior::doUpdateRF64( Chunk& chunk )
{
	//
	// update ds64 entry for chunk if its size has changed
	//
	if( chunk.hasChanged() && chunk.getOriginalSize() > kNormalRF64ChunkSize )
	{
		switch( chunk.getID() )
		{
			case kChunk_RF64:	mDS64Data->riffSize = chunk.getSize();		break;
			case kChunk_data:
				if( chunk.getSize() != chunk.getOriginalSize() )
				{
					XMP_Throw( "Data chunk must not change", kXMPErr_InternalFailure );
				}
				break;
			default:
			{
				bool requireEntry = ( chunk.getSize() > kNormalRF64ChunkSize );
				bool found = false;

				//
				// try to find entry for passed chunk id in the ds64 table
				//
				if( mDS64Data->tableLength > 0 )
				{
					for( std::vector<ChunkSize64>::iterator iter=mDS64Data->table.begin(); iter!=mDS64Data->table.end(); iter++ )
					{
						if( iter->id == chunk.getID() )
						{
							// always set new size even if it's less than 4GB
							iter->size = chunk.getSize();
							found = true;
							break;
						}
					}
				}

				//
				// We can't add new entries to the table. So if we found no entry within 'ds64'
				// for the passed chunk ID and the size of the chunk is larger than 4GB then
				// we have to throw an exception
				//
				XMP_Validate( found || ( ! found && ! requireEntry ), "Can't update 'ds64' chunk", kXMPErr_Unimplemented );
			}
		}
	}

	//
	// go through all children to update ds64 data
	//
	for( XMP_Uns32 i=0; i<chunk.numChildren(); i++ )
	{
		Chunk* child = chunk.getChildAt(i);
		
		this->doUpdateRF64( *child );
	}
}

//-----------------------------------------------------------------------------
// 
// WAVEBehavior::parseRF64Chunk(...)
// 
// Purpose: Parses the data block of the given RF64 chunk into the internal data structures
// 
//-----------------------------------------------------------------------------

bool WAVEBehavior::parseDS64Chunk( const Chunk& ds64Chunk, WAVEBehavior::DS64& ds64 )
{
	bool ret = false;

	// It is a valid ds64 chunk
	if( ds64Chunk.getID() == kChunk_ds64 && ds64Chunk.getSize() >= kMinimumDS64ChunkSize )
	{
		const XMP_Uns8* data;
		XMP_Uns64 size = ds64Chunk.getData(&data);

		// (Exempi) Unsafe for memset
		ds64 = WAVEBehavior::DS64();
		// memset( &ds64, 0, kMinimumDS64ChunkSize);

		//
		// copy fix input data into RF64 block (except chunk size table)
		// Safe as fixed size matches size of struct that is #pragma packed(1)
		//
		ds64.From(data, kMinimumDS64ChunkSize);
		// memcpy( &ds64, data, kMinimumDS64ChunkSize );

		// If there is more data but the table length is <= 0 then this is not a valid ds64 chunk
		if (size > kMinimumDS64ChunkSize && ds64.tableLength > 0 && ((size - kMinimumDS64ChunkSize) >= (ds64.tableLength * sizeof(ChunkSize64))))
		{
			// copy chunk sizes table
			//

			XMP_Uns32 offset = kMinimumDS64ChunkSize;
			ChunkSize64 chunkSize;

			for( XMP_Uns32 i = 0 ; i < ds64.tableLength ; i++, offset += sizeof(ChunkSize64) )
			{
				chunkSize.id = mEndian.getUns32( data + offset );
				chunkSize.size = mEndian.getUns64( data + offset + 4 );

				ds64.table.push_back( chunkSize );
			}
		}

		// remember any existing table buffer
		ds64.trailingBytes = static_cast<XMP_Uns32>(size - kMinimumDS64ChunkSize - ds64.tableLength * sizeof(ChunkSize64));
		
		// Either a table has been correctly parsed or there was no table
		ret = (size - kMinimumDS64ChunkSize) >= (ds64.tableLength * sizeof(ChunkSize64));
	}
	
	return ret;
}

//-----------------------------------------------------------------------------
// 
// WAVEBehavior::serializeRF64Chunk(...)
// 
// Purpose: Serializes the internal RF64 data structures into the data part of the given chunk
// 
//-----------------------------------------------------------------------------
bool WAVEBehavior::serializeDS64Chunk( const WAVEBehavior::DS64& ds64, Chunk& ds64Chunk )
{
	if( ds64Chunk.getID() != kChunk_ds64 )
	{
		return false; // not a valid ds64 chunk
	}

	// Calculate needed size
	XMP_Uns32 size = kMinimumDS64ChunkSize + ds64.tableLength * sizeof(ChunkSize64) + ds64.trailingBytes;
	// Create tmp buffer
	XMP_Uns8* data = new XMP_Uns8[size];
	memset( data, 0, size );

	// copy fix input data into buffer (except chunk sizes table)
	// Safe as fixed size matches size of struct that is #pragma packed(1)
	memcpy( data, &ds64, kMinimumDS64ChunkSize );

	// copy chunk sizes table
	if( ds64.tableLength > 0 )
	{
		XMP_Uns32 offset = kMinimumDS64ChunkSize;
		
		for( XMP_Uns32 i = 0 ; i < ds64.tableLength ; i++, offset += sizeof(ChunkSize64) )
		{
			mEndian.putUns32( ds64.table.at(i).id, data + offset );
			mEndian.putUns64( ds64.table.at(i).size, data + offset + 4 );
		}
	}

	ds64Chunk.setData( data, size );

	// free tmp buffer
	delete []data;

	return true;
}
