// =================================================================================================
// Copyright Adobe
// Copyright 2008 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 "public/include/XMP_IO.hpp"

#include "XMPFiles/source/XMPFiles_Impl.hpp"
#include "source/XMPFiles_IO.hpp"
#include "source/XIO.hpp"
#include "source/IOUtils.hpp"

#include "XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp"
#include "XMPFiles/source/FormatSupport/XDCAM_Support.hpp"
#include "third-party/zuid/interfaces/MD5.h"
#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp"

using namespace std;

// =================================================================================================
/// \file XDCAMEX_Handler.cpp
/// \brief Folder format handler for XDCAMEX.
///
/// This handler is for the XDCAMEX video format.
///
/// .../MyMovie/
///		BPAV/
///			MEDIAPRO.XML
///			MEDIAPRO.BUP
///			CLPR/
///				709_001_01/
///					709_001_01.SMI
///					709_001_01.MP4
///					709_001_01M01.XML
///					709_001_01R01.BIM
///					709_001_01I01.PPN
///				709_001_02/
///				709_002_01/
///				709_003_01/
///			TAKR/
///				709_001/
///					709_001.SMI
///					709_001M01.XML
///
/// The Backup files (.BUP) are optional. No files or directories other than those listed are
/// allowed in the BPAV directory. The CLPR (clip root) directory may contain only clip directories,
/// which may only contain the clip files listed. The TAKR (take root) direcory may contail only
/// take directories, which may only contain take files. The take root directory can be empty.
/// MEDIPRO.XML contains information on clip and take management.
///
/// Each clip directory contains a media file (.MP4), a clip info file (.SMI), a real time metadata
/// file (.BIM), a non real time metadata file (.XML), and a picture pointer file (.PPN). A take
/// directory conatins a take info and non real time take metadata files.
// =================================================================================================

// =================================================================================================
// XDCAMEX_CheckFormat
// ===================
//
// This version checks for the presence of a top level BPAV directory, and the required files and
// directories immediately within it. The CLPR and TAKR subfolders are required, as is MEDIAPRO.XML.
//
// The state of the string parameters depends on the form of the path passed by the client. If the
// client passed a logical clip path, like ".../MyMovie/012_3456_01", the parameters are:
//   rootPath   - ".../MyMovie"
//   gpName     - empty
//   parentName - empty
//   leafName   - "012_3456_01"
// If the client passed a full file path, like ".../MyMovie/BPAV/CLPR/012_3456_01/012_3456_01M01.XML", they are:
//   rootPath   - ".../MyMovie/BPAV"
//   gpName     - "CLPR"
//   parentName - "012_3456_01"
//   leafName   - "012_3456_01M01"

// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has
// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the
// ! file exists for a full file path.

// ! Using explicit '/' as a separator when creating paths, it works on Windows.

bool XDCAMEX_CheckFormat ( XMP_FileFormat /*format*/,
						   const std::string & _rootPath,
						   const std::string & gpName,
						   const std::string & parentName,
						   const std::string & _leafName,
						   XMPFiles * parent )
{
	std::string rootPath = _rootPath;
	std::string clipName = _leafName;
	std::string grandGPName;

	std::string bpavPath ( rootPath );

	// Do some initial checks on the gpName and parentName.

	if ( gpName.empty() != parentName.empty() ) return false;	// Must be both empty or both non-empty.

	if ( gpName.empty() ) {

		// This is the logical clip path case. Make sure .../MyMovie/BPAV/CLPR is a folder.
		bpavPath += kDirChar;	// The rootPath was just ".../MyMovie".
		bpavPath += "BPAV";
		if ( Host_IO::GetChildMode ( bpavPath.c_str(), "CLPR" ) != Host_IO::kFMode_IsFolder ) return false;

	} else {

		// This is the explicit file case. Make sure the ancestry is OK, compare using the parent's
		// length since the file can have a suffix like "M01". Use the leafName as the clipName to
		// preserve lower case, but truncate to the parent's length to remove any suffix.

		if ( gpName != "CLPR" ) return false;
		XIO::SplitLeafName ( &rootPath, &grandGPName );
		MakeUpperCase ( &grandGPName );
		if ( grandGPName != "BPAV" ) return false;

		if ( ! XMP_LitNMatch ( parentName.c_str(), clipName.c_str(), parentName.size() ) ) {
			std::string tempName = clipName;
			MakeUpperCase ( &tempName );
			if ( ! XMP_LitNMatch ( parentName.c_str(), tempName.c_str(), parentName.size() ) ) return false;
		}
		
		clipName.erase ( parentName.size() );

	}

	// Check the rest of the required general structure.
	if ( Host_IO::GetChildMode ( bpavPath.c_str(), "TAKR" ) != Host_IO::kFMode_IsFolder ) return false;
	if ( Host_IO::GetChildMode ( bpavPath.c_str(), "MEDIAPRO.XML" ) != Host_IO::kFMode_IsFile ) return false;

	// Make sure the clip's .MP4 and .SMI files exist.
	std::string tempPath ( bpavPath );
	tempPath += kDirChar;
	tempPath += "CLPR";
	tempPath += kDirChar;
	tempPath += clipName;
	tempPath += kDirChar;
	tempPath += clipName;
	tempPath += ".MP4";
	if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) return false;
	tempPath.erase ( tempPath.size()-3 );
	tempPath += "SMI";
	if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) return false;

	// And now save the psuedo path for the handler object.
	tempPath = rootPath;
	tempPath += kDirChar;
	tempPath += clipName;
	size_t pathLen = tempPath.size() + 1;	// Include a terminating nul.
	parent->tempPtr = malloc ( pathLen );
	if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for XDCAMEX clip info", kXMPErr_NoMemory );
	memcpy ( parent->tempPtr, tempPath.c_str(), pathLen );

	return true;

}	// XDCAMEX_CheckFormat

// =================================================================================================

static void* CreatePseudoClipPath ( const std::string & clientPath ) 
{

	// Used to create the clip pseudo path when the CheckFormat function is skipped.
	
	std::string pseudoPath = clientPath;

	size_t pathLen;
	void* tempPtr = 0;
	
	if ( Host_IO::Exists ( pseudoPath.c_str() ) ) {
	
		// The client passed a physical path. The logical clip name is the last folder name, the
		// parent of the file. This is best since some files have suffixes.
		
		std::string clipName, ignored;
		
		XIO::SplitLeafName ( &pseudoPath, &ignored );	// Split the file name.
		XIO::SplitLeafName ( &pseudoPath, &clipName );	// Use the parent folder name.

		XIO::SplitLeafName ( &pseudoPath, &ignored );	// Remove the 2 intermediate folder levels.
		XIO::SplitLeafName ( &pseudoPath, &ignored );
		
		pseudoPath += kDirChar;
		pseudoPath += clipName;
	
	}

	pathLen = pseudoPath.size() + 1;	// Include a terminating nul.
	tempPtr = malloc ( pathLen );
	if ( tempPtr == 0 ) XMP_Throw ( "No memory for XDCAMEX clip info", kXMPErr_NoMemory );
	memcpy ( tempPtr, pseudoPath.c_str(), pathLen );
	
	return tempPtr;

}	// CreatePseudoClipPath

// =================================================================================================
// XDCAMEX_MetaHandlerCTor
// =======================

XMPFileHandler * XDCAMEX_MetaHandlerCTor ( XMPFiles * parent )
{
	return new XDCAMEX_MetaHandler ( parent );

}	// XDCAMEX_MetaHandlerCTor

// =================================================================================================
// XDCAMEX_MetaHandler::XDCAMEX_MetaHandler
// ========================================

XDCAMEX_MetaHandler::XDCAMEX_MetaHandler ( XMPFiles * _parent ) : expat(0),clipMetadata(0)
{
	this->parent = _parent;	// Inherited, can't set in the prefix.
	this->handlerFlags = kXDCAMEX_HandlerFlags;
	this->stdCharForm  = kXMP_Char8Bit;

	// Extract the root path and clip name from tempPtr.

	if ( this->parent->tempPtr == 0 ) {
		// The CheckFormat call might have been skipped.
		this->parent->tempPtr = CreatePseudoClipPath ( this->parent->GetFilePath() );
	}

	this->rootPath.assign ( (char*) this->parent->tempPtr );
	free ( this->parent->tempPtr );
	this->parent->tempPtr = 0;

	XIO::SplitLeafName ( &this->rootPath, &this->clipName );

}	// XDCAMEX_MetaHandler::XDCAMEX_MetaHandler

// =================================================================================================
// XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler
// =========================================

XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler()
{

	this->CleanupLegacyXML();
	if ( this->parent->tempPtr != 0 ) {
		free ( this->parent->tempPtr );
		this->parent->tempPtr = 0;
	}

}	// XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler

// =================================================================================================
// XDCAMEX_MetaHandler::MakeClipFilePath
// =====================================

bool XDCAMEX_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ )
{

	*path = this->rootPath;
	*path += kDirChar;
	*path += "BPAV";
	*path += kDirChar;
	*path += "CLPR";
	*path += kDirChar;
	*path += this->clipName;
	*path += kDirChar;
	*path += this->clipName;
	*path += suffix;
	
	if ( ! checkFile ) return true;
	return Host_IO::Exists ( path->c_str() );

}	// XDCAMEX_MetaHandler::MakeClipFilePath

// =================================================================================================
// XDCAMEX_MetaHandler::MakeMediaproPath
// =====================================

bool XDCAMEX_MetaHandler::MakeMediaproPath ( std::string * path, bool checkFile /* = false */ )
{

	*path = this->rootPath;
	*path += kDirChar;
	*path += "BPAV";
	*path += kDirChar;
	*path += "MEDIAPRO.XML";
	
	if ( ! checkFile ) return true;
	return Host_IO::Exists ( path->c_str() );

}	// XDCAMEX_MetaHandler::MakeMediaproPath

// =================================================================================================
// XDCAMEX_MetaHandler::MakeLegacyDigest
// =====================================

// *** Early hack version.

#define kHexDigits "0123456789ABCDEF"

void XDCAMEX_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
{
	digestStr->erase();
	if ( this->clipMetadata == 0 ) return;	// Bail if we don't have any legacy XML.
	XMP_Assert ( this->expat != 0 );

	XMP_StringPtr xdcNSptr = this->xdcNS.c_str();
	XML_NodePtr legacyContext, legacyProp;

	legacyContext = this->clipMetadata->GetNamedElement ( xdcNSptr, "Access" );
	if ( legacyContext == 0 ) return;

	MD5_CTX    context;
	unsigned char digestBin [16];
	MD5Init ( &context );

	legacyProp = legacyContext->GetNamedElement ( xdcNSptr, "Creator" );
	if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
		const XML_Node * xmlValue = legacyProp->content[0];
		MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
	}

	legacyProp = legacyContext->GetNamedElement ( xdcNSptr, "CreationDate" );
	if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
		const XML_Node * xmlValue = legacyProp->content[0];
		MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
	}

	legacyProp = legacyContext->GetNamedElement ( xdcNSptr, "LastUpdateDate" );
	if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
		const XML_Node * xmlValue = legacyProp->content[0];
		MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
	}

	MD5Final ( digestBin, &context );

	char buffer [40];
	for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
		XMP_Uns8 byte = digestBin[in];
		buffer[out]   = kHexDigits [ byte >> 4 ];
		buffer[out+1] = kHexDigits [ byte & 0xF ];
	}
	buffer[32] = 0;
	digestStr->append ( buffer );

}	// XDCAMEX_MetaHandler::MakeLegacyDigest

// =================================================================================================
// XDCAMEX_MetaHandler::CleanupLegacyXML
// =====================================

void XDCAMEX_MetaHandler::CleanupLegacyXML()
{

	delete this->expat;
	this->expat = 0;

	clipMetadata = 0;	// ! Was a pointer into the expat tree.

}	// XDCAMEX_MetaHandler::CleanupLegacyXML

// =================================================================================================
// XDCAMEX_MetaHandler::GetFileModDate
// ===================================

static inline bool operator< ( const XMP_DateTime & left, const XMP_DateTime & right ) {
	int compare = SXMPUtils::CompareDateTime ( left, right );
	return (compare < 0);
}

bool XDCAMEX_MetaHandler::GetFileModDate ( XMP_DateTime * modDate )
{

	// The XDCAM EX locations of metadata:
	//	BPAV/
	//		MEDIAPRO.XML	// Has non-XMP metadata.
	//		CLPR/
	//			709_3001_01:
	//				709_3001_01M01.XML	// Has non-XMP metadata.
	//				709_3001_01M01.XMP

	bool ok, haveDate = false;
	std::string fullPath;
	XMP_DateTime oneDate, junkDate;
	if ( modDate == 0 ) modDate = &junkDate;

	ok = this->MakeMediaproPath ( &fullPath, true /* checkFile */ );
	if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
	if ( ok ) {
		if ( *modDate < oneDate ) *modDate = oneDate;
		haveDate = true;
	}

	ok = this->MakeClipFilePath ( &fullPath, "M01.XML", true /* checkFile */ );
	if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
	if ( ok ) {
		if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
		haveDate = true;
	}

	ok = this->MakeClipFilePath ( &fullPath, "M01.XMP", true /* checkFile */ );
	if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
	if ( ok ) {
		if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
		haveDate = true;
	}

	return haveDate;

}	// XDCAMEX_MetaHandler::GetFileModDate

// Adds all the associated resources for the specified clip only (not related spanned ones) 
static void FillClipAssociatedResources( std::vector<std::string> * resourceList, std::string &clipPath, std::string &clipName )
{
	std::string filePath;
	std::string spannedClipFolderPath = clipPath + clipName + kDirChar;
	
	std::string clipPathNoExt = spannedClipFolderPath + clipName;
	// Get the files present inside clip folder.
	std::vector<std::string> regExpStringVec;
	std::string regExpString;
	regExpString = "^" + clipName + ".MP4$";
	regExpStringVec.push_back(regExpString);
	regExpString = "^" + clipName + "M\\d\\d.XMP$";
	regExpStringVec.push_back(regExpString);
	regExpString = "^" + clipName + "M\\d\\d.XML$";
	regExpStringVec.push_back(regExpString);
	regExpString = "^" + clipName + "I\\d\\d.PPN$";
	regExpStringVec.push_back(regExpString);
	regExpString = "^" + clipName + "R\\d\\d.BIM$";
	regExpStringVec.push_back(regExpString);
	regExpString = "^" + clipName + ".SMI$";
	regExpStringVec.push_back(regExpString);

	IOUtils::GetMatchingChildren (*resourceList, spannedClipFolderPath, regExpStringVec, false, true, true );

}


// =================================================================================================
// XDCAMEX_MetaHandler::FillAssociatedResources
// ======================================
void XDCAMEX_MetaHandler::FillAssociatedResources ( std::vector<std::string> * resourceList )
{
	// The possible associated resources:
	//	BPAV/
	//		MEDIAPRO.XML	
	//		CUEUP.XML
	//		CLPR/
	//			MIXXXX_YY:			MI is MachineID, XXXX is TakeSerial, 
	//								YY is ClipSuffix(as single take can be divided across multiple clips.)
	//								In case of spanning, all the clip folders starting from "MIXXXX_" are looked for.
	//				MIXXXX_YY.MP4
	//				MIXXXX_YYMNN.XML	NN is a counter which will start from from 01 and can go upto 99 based 
	//									on number of files present in this folder with same extension.
	//				MIXXXX_YYMNN.XMP
	//				MIXXXX_YYINN.PPN
	//				MIXXXX_YYRNN.BIM
	//				MXXXX_YY.SMI
	//		TAKR/
	//			MIXXXX:
	//				MIXXXXMNN.XML		NN is a counter which will start from from 01 and can go upto 99 based 
	//									on number of files present in this folder with same extension.
	//				MIXXXX.SMI
	//				MIXXXXUNN.SMI		NN is a counter which goes from 01 to N-1 where N is number of media, this
	//									take is divided into. For Nth, MIXXXX.SMI shall be picked up.
	XMP_VarString bpavPath = this->rootPath + kDirChar + "BPAV" + kDirChar;
	XMP_VarString filePath;
	//Add RootPath
	filePath = this->rootPath + kDirChar;
	PackageFormat_Support::AddResourceIfExists ( resourceList, filePath );

	// Get the files present directly inside BPAV folder.
	filePath = bpavPath + "MEDIAPRO.XML";
	PackageFormat_Support::AddResourceIfExists ( resourceList, filePath );
	filePath = bpavPath + "MEDIAPRO.BUP";
	PackageFormat_Support::AddResourceIfExists ( resourceList, filePath );
	filePath = bpavPath + "CUEUP.XML";
	PackageFormat_Support::AddResourceIfExists ( resourceList, filePath );
	filePath = bpavPath + "CUEUP.BUP";
	PackageFormat_Support::AddResourceIfExists ( resourceList, filePath );

	XMP_VarString clipPath = bpavPath + "CLPR" + kDirChar;
	size_t clipSuffixIndex = this->clipName.find_last_of('_');
	XMP_VarString takeName = this->clipName.substr(0, clipSuffixIndex);

	// Add spanned clip files. 
	// Here, we iterate over all the folders present inside "/BPAV/CLPR/" and whose name starts from
	// "MIXXXX_". All valid files present inside such folders are added to the list.
	XMP_VarString regExpString;
	regExpString = "^" + takeName + "_\\d\\d$";
	XMP_StringVector list;

	IOUtils::GetMatchingChildren ( list, clipPath, regExpString, true, false, false );
	size_t spaningClipsCount = list.size();
	for ( size_t index = 0; index < spaningClipsCount; index++ ) {
		FillClipAssociatedResources ( resourceList, clipPath, list[index] );
	}
	list.clear();

	size_t sizeWithoutTakeFiles = resourceList->size();
	XMP_VarString takeFolderPath = bpavPath + "TAKR" + kDirChar + takeName + kDirChar;
	XMP_StringVector regExpStringVec;

	// Get the files present inside take folder.
	regExpString = "^" + takeName + "M\\d\\d.XML$";
	regExpStringVec.push_back ( regExpString );
	regExpString = "^" + takeName + "U\\d\\d.SMI$";
	regExpStringVec.push_back ( regExpString );
	regExpString = "^" + takeName + ".SMI$";
	regExpStringVec.push_back ( regExpString );
	IOUtils::GetMatchingChildren ( *resourceList, takeFolderPath, regExpStringVec, false, true, true );

	if ( sizeWithoutTakeFiles == resourceList->size() )
	{
		// no Take files added to resource list. But "TAKR" folder is necessary to recognize this format
		// so let's add it to the list.
		filePath = bpavPath + "TAKR" + kDirChar;
		PackageFormat_Support::AddResourceIfExists(resourceList, filePath);
	}
}	// XDCAMEX_MetaHandler::FillAssociatedResources

// =================================================================================================
// XDCAMEX_MetaHandler::FillMetadataFiles
// ======================================
void XDCAMEX_MetaHandler::FillMetadataFiles ( std::vector<std::string> * metadataFiles )
{
	std::string noExtPath, filePath;

	noExtPath = rootPath + kDirChar + "BPAV" + kDirChar + "CLPR" +
						   kDirChar + clipName + kDirChar + clipName;

	filePath = noExtPath + "M01.XMP";
	metadataFiles->push_back ( filePath );
	filePath = noExtPath + "M01.XML";
	metadataFiles->push_back ( filePath );
	filePath = rootPath + kDirChar + "BPAV" + kDirChar + "MEDIAPRO.XML";
	metadataFiles->push_back ( filePath );

}	// 	FillMetadataFiles_XDCAM_EX

// =================================================================================================
// XDCAMEX_MetaHandler::IsMetadataWritable
// =======================================

bool XDCAMEX_MetaHandler::IsMetadataWritable ( )
{
	std::vector<std::string> metadataFiles;
	FillMetadataFiles(&metadataFiles);
	std::vector<std::string>::iterator itr = metadataFiles.begin();
	// Check whether sidecar is writable, if not then check if it can be created.
	XMP_Bool xmpWritable = Host_IO::Writable( itr->c_str(), true );
	// Check for legacy metadata file.
	XMP_Bool xmlWritable = Host_IO::Writable( (++itr)->c_str(), false );
	return ( xmlWritable && xmpWritable );
}// XDCAMEX_MetaHandler::IsMetadataWritable

// =================================================================================================
// XDCAMEX_MetaHandler::CacheFileData
// ==================================

void XDCAMEX_MetaHandler::CacheFileData()
{
	XMP_Assert ( ! this->containsXMP );

	if ( this->parent->UsesClientIO() ) {
		XMP_Throw ( "XDCAMEX cannot be used with client-managed I/O", kXMPErr_InternalFailure );
	}

	// See if the clip's .XMP file exists.

	std::string xmpPath;
	this->MakeClipFilePath ( &xmpPath, "M01.XMP" );
	if ( ! Host_IO::Exists ( xmpPath.c_str() ) ) return;	// No XMP.

	// Read the entire .XMP file. We know the XMP exists, New_XMPFiles_IO is supposed to return 0
	// only if the file does not exist.

	bool readOnly = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenForUpdate );

	XMP_Assert ( this->parent->ioRef == 0 );
	XMPFiles_IO* xmpFile =  XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), readOnly );
	if ( xmpFile == 0 ) XMP_Throw ( "XDCAMEX XMP file open failure", kXMPErr_InternalFailure );
	this->parent->ioRef = xmpFile;

	XMP_Int64 xmpLen = xmpFile->Length();
	if ( xmpLen > 100*1024*1024 ) {
		XMP_Throw ( "XDCAMEX XMP is outrageously large", kXMPErr_InternalFailure );	// Sanity check.
	}

	this->xmpPacket.erase();
	this->xmpPacket.append ( (size_t)xmpLen, ' ' );

	/*XMP_Int32 ioCount =*/ xmpFile->ReadAll ( (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen );

	this->packetInfo.offset = 0;
	this->packetInfo.length = (XMP_Int32)xmpLen;
	FillPacketInfo ( this->xmpPacket, &this->packetInfo );

	this->containsXMP = true;

}	// XDCAMEX_MetaHandler::CacheFileData

// =================================================================================================
// XDCAMEX_MetaHandler::GetTakeDuration
// ====================================

void XDCAMEX_MetaHandler::GetTakeDuration ( const std::string & takeURI, std::string & duration )
{

	// Some versions of gcc can't tolerate goto's across declarations.
	// *** Better yet, avoid this cruft with self-cleaning objects.
	#define CleanupAndExit	\
		{									\
			delete expatMediaPro;	        \
			takeXMLFile.Close();			\
			return;							\
		}

	duration.clear();

	// Build a directory string to the take .xml file.

	std::string takeDir ( takeURI );
	takeDir.erase ( 0, 1 );	// Change the leading "//" to "/", then all '/' to kDirChar.
#if XMP_MacBuild
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
#endif
	if ( kDirChar != '/' ) {
		for ( size_t i = 0, limit = takeDir.size(); i < limit; ++i ) {
			if ( takeDir[i] == '/' ) takeDir[i] = kDirChar;
		}
	}
#if XMP_MacBuild
#pragma clang diagnostic pop
#endif 
	std::string takePath ( this->rootPath );
	takePath += kDirChar;
	takePath += "BPAV";
	takePath += takeDir;

	// Replace .SMI with M01.XML.
	if ( takePath.size() > 4 ) {
		takePath.erase ( takePath.size() - 4, 4 );
		takePath += "M01.XML";
	}

	// Parse MEDIAPRO.XML

	XML_NodePtr takeRootElem = 0;
	XML_NodePtr context = 0;

	Host_IO::FileRef hostRef = Host_IO::Open ( takePath.c_str(), Host_IO::openReadOnly );
	if ( hostRef == Host_IO::noFileRef ) return;	// The open failed.
	XMPFiles_IO takeXMLFile ( hostRef, takePath.c_str(), Host_IO::openReadOnly );

	ExpatAdapter * expatMediaPro = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces );
	if ( expatMediaPro == 0 ) return;

	XMP_Uns8 buffer [64*1024];
	while ( true ) {
		XMP_Int32 ioCount = takeXMLFile.Read ( buffer, sizeof(buffer) );
		if ( ioCount == 0 ) break;
		expatMediaPro->ParseBuffer ( buffer, ioCount, false /* not the end */ );
	}

	expatMediaPro->ParseBuffer ( 0, 0, true );	// End the parse.
	takeXMLFile.Close();

	// Get the root node of the XML tree.

	XML_Node & mediaproXMLTree = expatMediaPro->tree;
	for ( size_t i = 0, limit = mediaproXMLTree.content.size(); i < limit; ++i ) {
		if ( mediaproXMLTree.content[i]->kind == kElemNode ) {
			takeRootElem = mediaproXMLTree.content[i];
		}
	}
	if ( takeRootElem == 0 ) CleanupAndExit

	XMP_StringPtr rlName = takeRootElem->name.c_str() + takeRootElem->nsPrefixLen;
	if ( ! XMP_LitMatch ( rlName, "NonRealTimeMeta" ) ) CleanupAndExit

	// MediaProfile, Contents
	XMP_StringPtr ns = takeRootElem->ns.c_str();
	context = takeRootElem->GetNamedElement ( ns, "Duration" );
	if ( context != 0 ) {
		XMP_StringPtr durationValue = context->GetAttrValue ( "value" );
		if ( durationValue != 0 ) duration = durationValue;
	}

	CleanupAndExit
	#undef CleanupAndExit

}	// XDCAMEX_MetaHandler::GetTakeDuration

// =================================================================================================
// XDCAMEX_MetaHandler::GetMediaProMetadata
// ========================================

bool XDCAMEX_MetaHandler::GetMediaProMetadata ( SXMPMeta * xmpObjPtr,
											  	const std::string& _clipUMID,
											  	bool digestFound )
{
	// Build a directory string to the MEDIAPRO file.

	std::string mediaproPath;
	this->MakeMediaproPath ( &mediaproPath );
	return XDCAM_Support::GetMediaProLegacyMetadata ( xmpObjPtr, _clipUMID, mediaproPath, digestFound );

}	// XDCAMEX_MetaHandler::GetMediaProMetadata

// =================================================================================================
// XDCAMEX_MetaHandler::GetTakeUMID
// ================================

void XDCAMEX_MetaHandler::GetTakeUMID ( const std::string& _clipUMID,
										std::string&	   takeUMID,
										std::string&	   takeXMLURI )
{

	// Some versions of gcc can't tolerate goto's across declarations.
	// *** Better yet, avoid this cruft with self-cleaning objects.
	#define CleanupAndExit	\
		{									\
			delete expatMediaPro;	        \
			mediaproXMLFile.Close();		\
			return;							\
		}

	takeUMID.clear();
	takeXMLURI.clear();

	// Build a directory string to the MEDIAPRO file.

	std::string mediapropath ( this->rootPath );
	mediapropath += kDirChar;
	mediapropath += "BPAV";
	mediapropath += kDirChar;
	mediapropath += "MEDIAPRO.XML";

	// Parse MEDIAPRO.XML.

	XML_NodePtr mediaproRootElem = 0;
	XML_NodePtr contentContext = 0/*, materialContext = 0*/;

	Host_IO::FileRef hostRef = Host_IO::Open ( mediapropath.c_str(), Host_IO::openReadOnly );
	if ( hostRef == Host_IO::noFileRef ) return;	// The open failed.
	XMPFiles_IO mediaproXMLFile ( hostRef, mediapropath.c_str(), Host_IO::openReadOnly );

	ExpatAdapter * expatMediaPro = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces );
	if ( expatMediaPro == 0 ) return;

	XMP_Uns8 buffer [64*1024];
	while ( true ) {
		XMP_Int32 ioCount = mediaproXMLFile.Read ( buffer, sizeof(buffer) );
		if ( ioCount == 0 ) break;
		expatMediaPro->ParseBuffer ( buffer, ioCount, false /* not the end */ );
	}

	expatMediaPro->ParseBuffer ( 0, 0, true );	// End the parse.
	mediaproXMLFile.Close();

	// Get the root node of the XML tree.

	XML_Node & mediaproXMLTree = expatMediaPro->tree;
	for ( size_t i = 0, limit = mediaproXMLTree.content.size(); i < limit; ++i ) {
		if ( mediaproXMLTree.content[i]->kind == kElemNode ) {
			mediaproRootElem = mediaproXMLTree.content[i];
		}
	}

	if ( mediaproRootElem == 0 ) CleanupAndExit
	XMP_StringPtr rlName = mediaproRootElem->name.c_str() + mediaproRootElem->nsPrefixLen;
	if ( ! XMP_LitMatch ( rlName, "MediaProfile" ) ) CleanupAndExit

	//  MediaProfile, Contents

	XMP_StringPtr ns = mediaproRootElem->ns.c_str();
	contentContext = mediaproRootElem->GetNamedElement ( ns, "Contents" );

	if ( contentContext != 0 ) {

		size_t numMaterialElems = contentContext->CountNamedElements ( ns, "Material" );

		for ( size_t i = 0; i < numMaterialElems; ++i ) {	// Iterate over Material tags.

			XML_NodePtr materialElement = contentContext->GetNamedElement ( ns, "Material", i );
			XMP_Assert ( materialElement != 0 );

			XMP_StringPtr umid = materialElement->GetAttrValue ( "umid" );
			XMP_StringPtr uri = materialElement->GetAttrValue ( "uri" );

			if ( umid == 0 ) umid = "";
			if ( uri == 0 ) uri = "";

			size_t numComponents = materialElement->CountNamedElements ( ns, "Component" );

			for ( size_t j = 0; j < numComponents; ++j ) {

				XML_NodePtr componentElement = materialElement->GetNamedElement ( ns, "Component", j );
				XMP_Assert ( componentElement != 0 );

				XMP_StringPtr compUMID = componentElement->GetAttrValue ( "umid" );

				if ( (compUMID != 0) && (compUMID == _clipUMID) ) {
					takeUMID = umid;
					takeXMLURI = uri;
					break;
				}

			}

			if ( ! takeUMID.empty() ) break;

		}

	}

	CleanupAndExit
	#undef CleanupAndExit

}

// =================================================================================================
// XDCAMEX_MetaHandler::ProcessXMP
// ===============================

void XDCAMEX_MetaHandler::ProcessXMP()
{

	// Some versions of gcc can't tolerate goto's across declarations.
	// *** Better yet, avoid this cruft with self-cleaning objects.
	#define CleanupAndExit	\
		{																								\
			bool openForUpdate = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate );	\
			if ( ! openForUpdate ) this->CleanupLegacyXML();											\
			xmlFile.Close();																			\
			return;																						\
		}

	if ( this->processedXMP ) return;
	this->processedXMP = true;	// Make sure only called once.

	if ( this->containsXMP ) {
		this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
	}

	// NonRealTimeMeta -> XMP by schema.
	std::string thisUMID, takeUMID, takeXMLURI, takeDuration;
	std::string xmlPath;
	this->MakeClipFilePath ( &xmlPath, "M01.XML" );

	Host_IO::FileRef hostRef = Host_IO::Open ( xmlPath.c_str(), Host_IO::openReadOnly );
	if ( hostRef == Host_IO::noFileRef ) return;	// The open failed.
	XMPFiles_IO xmlFile ( hostRef, xmlPath.c_str(), Host_IO::openReadOnly );

	this->expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces );
	if ( this->expat == 0 ) XMP_Throw ( "XDCAMEX_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory );

	XMP_Uns8 buffer [64*1024];
	while ( true ) {
		XMP_Int32 ioCount = xmlFile.Read ( buffer, sizeof(buffer) );
		if ( ioCount == 0 ) break;
		this->expat->ParseBuffer ( buffer, ioCount, false /* not the end */ );
	}
	this->expat->ParseBuffer ( 0, 0, true );	// End the parse.

	xmlFile.Close();

	// The root element should be NonRealTimeMeta in some namespace. Take whatever this file uses.

	XML_Node & xmlTree = this->expat->tree;
	XML_NodePtr rootElem = 0;

	for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) {
		if ( xmlTree.content[i]->kind == kElemNode ) rootElem = xmlTree.content[i];
	}

	if ( rootElem == 0 ) CleanupAndExit
	XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen;
	if ( ! XMP_LitMatch ( rootLocalName, "NonRealTimeMeta" ) ) CleanupAndExit

	this->legacyNS = rootElem->ns;

	// Check the legacy digest.

	XMP_StringPtr legacyNSPtr = this->legacyNS.c_str();
	this->clipMetadata = rootElem;	// ! Save the NonRealTimeMeta pointer for other use.

	std::string oldDigest, newDigest;
	bool digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAMEX", &oldDigest, 0 );
	if ( digestFound ) {
		this->MakeLegacyDigest ( &newDigest );
		if ( oldDigest == newDigest ) CleanupAndExit
	}

	// If we get here we need find and import the actual legacy elements using the current namespace.
	// Either there is no old digest in the XMP, or the digests differ. In the former case keep any
	// existing XMP, in the latter case take new legacy values.
	this->containsXMP = XDCAM_Support::GetLegacyMetadata ( &this->xmpObj, rootElem, legacyNSPtr, digestFound, thisUMID );

	// If this clip is part of a take, add the take number to the relation field, and get the
	// duration from the take metadata.
	GetTakeUMID ( thisUMID, takeUMID, takeXMLURI );

	// If this clip is part of a take, update the duration to reflect the take duration rather than
	// the clip duration, and add the take name as a shot name.

	if ( ! takeXMLURI.empty() ) {

		// Update duration. This property already exists from clip legacy metadata.
		GetTakeDuration ( takeXMLURI, takeDuration );
		if ( ! takeDuration.empty() ) {
			this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", takeDuration );
			containsXMP = true;
		}

		if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "shotName" )) ) {

			std::string takeName;
			XIO::SplitLeafName ( &takeXMLURI, &takeName );

			// Check for the xml suffix, and delete if it exists.
			size_t pos = takeName.rfind(".SMI");
			if ( pos != std::string::npos ) {

				takeName.erase ( pos );

				// delete the take number suffix if it exists.
				if ( takeName.size() > 3 ) {

					size_t suffix = takeName.size() - 3;
					char c1 = takeName[suffix];
					char c2 = takeName[suffix+1];
					char c3 = takeName[suffix+2];
					if ( ('U' == c1) && ('0' <= c2) && (c2 <= '9') && ('0' <= c3) && (c3 <= '9') ) {
						takeName.erase ( suffix );
					}

					this->xmpObj.SetProperty ( kXMP_NS_DM, "shotName", takeName, kXMP_DeleteExisting );
					containsXMP = true;

				}

			}

		}

	}

	if ( (! takeUMID.empty()) &&
		 (digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "relation" ))) ) {
		this->xmpObj.DeleteProperty ( kXMP_NS_DC, "relation" );
		this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, takeUMID );
		this->containsXMP = true;
	}

	this->containsXMP |= GetMediaProMetadata ( &this->xmpObj, thisUMID, digestFound );

	CleanupAndExit
	#undef CleanupAndExit

}	// XDCAMEX_MetaHandler::ProcessXMP


// =================================================================================================
// XDCAMEX_MetaHandler::UpdateFile
// ===============================
//
// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here.

void XDCAMEX_MetaHandler::UpdateFile ( bool doSafeUpdate )
{
	if ( ! this->needsUpdate ) return;
	this->needsUpdate = false;	// Make sure only called once.

	XMP_Assert ( this->parent->UsesLocalIO() );

	// Update the internal legacy XML tree if we have one, and set the digest in the XMP.

	bool updateLegacyXML = false;

	if ( this->clipMetadata != 0 ) {
		updateLegacyXML = XDCAM_Support::SetLegacyMetadata ( this->clipMetadata, &this->xmpObj, this->legacyNS.c_str());
	}

	std::string newDigest;
	this->MakeLegacyDigest ( &newDigest );
	this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAMEX", newDigest.c_str(), kXMP_DeleteExisting );
	this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() );

	// -----------------------------------------------------------------------
	// Update the XMP file first, don't let legacy XML failures block the XMP.

	std::string xmpPath;
	this->MakeClipFilePath ( &xmpPath, "M01.XMP" );

	bool haveXMP = Host_IO::Exists ( xmpPath.c_str() );
	if ( ! haveXMP ) {
		XMP_Assert ( this->parent->ioRef == 0 );
		Host_IO::Create ( xmpPath.c_str() );
		this->parent->ioRef = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), Host_IO::openReadWrite );
		if ( this->parent->ioRef == 0 ) XMP_Throw ( "Failure opening XDCAMEX XMP file", kXMPErr_ExternalFailure );
	}

	XMP_IO* xmpFile = this->parent->ioRef;
	XMP_Assert ( xmpFile != 0 );
	XIO::ReplaceTextFile ( xmpFile, this->xmpPacket, (haveXMP & doSafeUpdate) );

	// --------------------------------------------
	// Now update the legacy XML file if necessary.

	if ( updateLegacyXML ) {

		std::string legacyXML, xmlPath;
		this->expat->tree.Serialize ( &legacyXML );
		this->MakeClipFilePath ( &xmlPath, "M01.XML" );

		bool haveXML = Host_IO::Exists ( xmlPath.c_str() );
		if ( ! haveXML ) Host_IO::Create ( xmlPath.c_str() );

		Host_IO::FileRef hostRef = Host_IO::Open ( xmlPath.c_str(), Host_IO::openReadWrite );
		if ( hostRef == Host_IO::noFileRef ) XMP_Throw ( "Failure opening XDCAMEX legacy XML file", kXMPErr_ExternalFailure );
		XMPFiles_IO origXML ( hostRef, xmlPath.c_str(), Host_IO::openReadWrite );
		XIO::ReplaceTextFile ( &origXML, legacyXML, (haveXML & doSafeUpdate) );
		origXML.Close();

	}

}	// XDCAMEX_MetaHandler::UpdateFile

// =================================================================================================
// XDCAMEX_MetaHandler::WriteTempFile
// ==================================

void XDCAMEX_MetaHandler::WriteTempFile ( XMP_IO* /*tempRef*/ )
{

	// ! WriteTempFile is not supposed to be called for handlers that own the file.
	XMP_Throw ( "XDCAMEX_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure );

}	// XDCAMEX_MetaHandler::WriteTempFile

// =================================================================================================
