// =================================================================================================
// Copyright Adobe
// Copyright 2007 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. 
// =================================================================================================
#define BUFFER_SIZE 255
#define TIMECODE_SIZE 256
#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/SonyHDV_Handler.hpp"
#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp"
#include "third-party/zuid/interfaces/MD5.h"

#if XMP_WinBuild
	#pragma warning ( disable : 4996 )	// '...' was declared deprecated
#endif

using namespace std;

// =================================================================================================
/// \file SonyHDV_Handler.cpp
/// \brief Folder format handler for Sony HDV.
///
/// This handler is for the Sony HDV video format. This is a pseudo-package, visible files but with
/// a very well-defined layout and naming rules.
///
/// A typical Sony HDV layout looks like:
///
/// .../MyMovie/
/// 	VIDEO/
/// 		HVR/
/// 			00_0001_2007-08-06_165555.IDX
/// 			00_0001_2007-08-06_165555.M2T
/// 			00_0001_2007-08-06_171740.M2T
/// 			00_0001_2007-08-06_171740.M2T.ese
/// 			tracks.dat
///
/// The logical clip name can be "00_0001" or "00_0001_" plus anything. We'll find the .IDX file,
/// which defines the existence of the clip. Full file names as input will pull out the camera/clip
/// parts and match in the same way. The .XMP file will use the date/time suffix from the .IDX file.
// =================================================================================================

// =================================================================================================
// SonyHDV_CheckFormat
// ===================
//
// This version does fairly simple checks. The top level folder (.../MyMovie) must contain the
// VIDEO/HVR subtree. The HVR folder must contain a .IDX file for the desired clip. The name checks
// are case insensitive.
//
// 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/00_0001", the parameters are:
//   rootPath   - ".../MyMovie"
//   gpName     - empty
//   parentName - empty
//   leafName   - "00_0001"
//
// If the client passed a full file path, like ".../MyMovie/VIDEO/HVR/00_0001_2007-08-06_165555.M2T",
// they are:
//   rootPath   - ".../MyMovie"
//   gpName     - "VIDEO"
//   parentName - "HVR"
//   leafName   - "00_0001_2007-08-06_165555.M2T"
//
// The logical clip name can be short like "00_0001", or long like "00_0001_2007-08-06_165555". We
// only key off of the portion before a second underscore.

// ! 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.

bool SonyHDV_CheckFormat ( XMP_FileFormat format,
						   const std::string & rootPath,
						   const std::string & gpName,
						   const std::string & parentName,
						   const std::string & leafName,
						   XMPFiles * parent )
{
	// Do some basic checks on the root path and component names.

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

	std::string tempPath = rootPath;
	tempPath += kDirChar;
	tempPath += "VIDEO";

	if ( gpName.empty() ) {
		// This is the logical clip path case. Look for VIDEO/HVR subtree.
		if ( Host_IO::GetChildMode ( tempPath.c_str(), "HVR" ) != Host_IO::kFMode_IsFolder ) return false;
	} else {
		// This is the existing file case. Check the parent and grandparent names.
		if ( (gpName != "VIDEO") || (parentName != "HVR") ) return false;
	}

	// Look for the clip's .IDX file. If found use that as the full clip name.

	tempPath += kDirChar;
	tempPath += "HVR";

	std::string clipName = leafName;
	
#if 0

	// Disabled until Sony HDV clip spanning is supported. Since segments of spanned clips are
	// currently considered separate entities, information such as frame count needs to be
	// considered on a per segment basis.

	int usCount = 0;
	size_t i, limit = leafName.size();
	for ( i = 0; i < limit; ++i ) {
		if ( clipName[i] == '_' ) {
			++usCount;
			if ( usCount == 2 ) break;
		}
	}
	if ( i < limit ) clipName.erase ( i );
	clipName += '_';	// Make sure a final '_' is there for the search comparisons.

	Host_IO::AutoFolder aFolder;
	std::string childName;
	bool found = false;

	aFolder.folder = Host_IO::OpenFolder ( tempPath.c_str() );
	while ( (! found) && Host_IO::GetNextChild ( aFolder.folder, &childName ) ) {
		size_t childLen = childName.size();
		if ( childLen < 4 ) continue;
		MakeUpperCase ( &childName );
		if ( childName.compare ( childLen-4, 4, ".IDX" ) != 0 ) continue;
		if ( childName.compare ( 0, clipName.size(), clipName ) == 0 ) {
			found = true;
			clipName = childName;
			clipName.erase ( childLen-4 );
		}
	}
	aFolder.Close();
	if ( ! found ) return false;

#endif

	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 SonyHDV clip info", kXMPErr_NoMemory );
	memcpy ( parent->tempPtr, tempPath.c_str(), pathLen );	// AUDIT: Safe, allocated above.

	return true;

}	// SonyHDV_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 leaf name, with the
		// extension removed. There are no extra suffixes on Sony HDV files. The movie root path ends
		// two levels up.
		
		std::string clipName, ignored;
		
		XIO::SplitLeafName ( &pseudoPath, &clipName );	// Extract the logical clip name.
		XIO::SplitFileExtension ( &clipName, &ignored );

		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 SonyHDV clip info", kXMPErr_NoMemory );
	memcpy ( tempPtr, pseudoPath.c_str(), pathLen );
	
	return tempPtr;

}	// CreatePseudoClipPath

// =================================================================================================
// ReadIDXFile
// ===========

#define ExtractTimeCodeByte(ch,mask)	( (((ch & mask) >> 4) * 10) + (ch & 0xF) )

static bool ReadIDXFile ( const std::string& idxPath,
						  const std::string& clipName,
						  SXMPMeta* xmpObj,
						  bool& containsXMP,
						  MD5_CTX* md5Context,
						  bool digestFound )
{
	bool result = true;
	containsXMP = false;

	if ( clipName.size() != 25 ) return false;

	try {


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

		struct SHDV_HeaderBlock
		{
			char			mHeader[8];
			unsigned char	mValidFlag;
			unsigned char	mReserved;
			unsigned char	mECCTB;
			unsigned char   mSignalMode;
			unsigned char	mFileThousands;
			unsigned char	mFileHundreds;
			unsigned char	mFileTens;
			unsigned char	mFileUnits;
		};

		SHDV_HeaderBlock hdvHeaderBlock;
		memset ( &hdvHeaderBlock, 0, sizeof(SHDV_HeaderBlock) );

		idxFile.ReadAll ( hdvHeaderBlock.mHeader, 8 );
		idxFile.ReadAll ( &hdvHeaderBlock.mValidFlag, 1 );
		idxFile.ReadAll ( &hdvHeaderBlock.mReserved, 1 );
		idxFile.ReadAll ( &hdvHeaderBlock.mECCTB, 1 );
		idxFile.ReadAll ( &hdvHeaderBlock.mSignalMode, 1 );
		idxFile.ReadAll ( &hdvHeaderBlock.mFileThousands, 1 );
		idxFile.ReadAll ( &hdvHeaderBlock.mFileHundreds, 1 );
		idxFile.ReadAll ( &hdvHeaderBlock.mFileTens, 1 );
		idxFile.ReadAll ( &hdvHeaderBlock.mFileUnits, 1 );

		const int fileCount = (hdvHeaderBlock.mFileThousands - '0') * 1000 +
							  (hdvHeaderBlock.mFileHundreds  - '0') *  100 +
							  (hdvHeaderBlock.mFileTens      - '0') *   10 +
							  (hdvHeaderBlock.mFileUnits     - '0');

		// Read file info block.
		struct	SHDV_FileBlock
		{
			char			mDT[2];
			unsigned char	mFileNameYear;
			unsigned char	mFileNameMonth;
			unsigned char	mFileNameDay;
			unsigned char	mFileNameHour;
			unsigned char	mFileNameMinute;
			unsigned char	mFileNameSecond;
			unsigned char	mStartTimeCode[4];
			unsigned char	mTotalFrame[4];
		};

		SHDV_FileBlock hdvFileBlock;
		memset ( &hdvFileBlock, 0, sizeof(SHDV_FileBlock) );

		char filenameBuffer[256];
		std::string fileDateAndTime = clipName.substr(8);

		bool foundFileBlock = false;

		for ( int i=0; ((i < fileCount) && (! foundFileBlock)); ++i ) {

			idxFile.ReadAll ( hdvFileBlock.mDT, 2 );
			idxFile.ReadAll ( &hdvFileBlock.mFileNameYear, 1 );
			idxFile.ReadAll ( &hdvFileBlock.mFileNameMonth, 1 );
			idxFile.ReadAll ( &hdvFileBlock.mFileNameDay, 1 );
			idxFile.ReadAll ( &hdvFileBlock.mFileNameHour, 1 );
			idxFile.ReadAll ( &hdvFileBlock.mFileNameMinute, 1 );
			idxFile.ReadAll ( &hdvFileBlock.mFileNameSecond, 1 );
			idxFile.ReadAll ( &hdvFileBlock.mStartTimeCode, 4 );
			idxFile.ReadAll ( &hdvFileBlock.mTotalFrame, 4 );

			// Compose file name we expect from file contents and break out on match.
			snprintf ( filenameBuffer, sizeof(filenameBuffer), "%02d-%02d-%02d_%02d%02d%02d",
					  hdvFileBlock.mFileNameYear + 2000,
					  hdvFileBlock.mFileNameMonth,
					  hdvFileBlock.mFileNameDay,
					  hdvFileBlock.mFileNameHour,
					  hdvFileBlock.mFileNameMinute,
					  hdvFileBlock.mFileNameSecond );

			foundFileBlock = (fileDateAndTime==filenameBuffer);

		}

		idxFile.Close();
		if ( ! foundFileBlock ) return false;

		// If digest calculation requested, calculate it and return.
		if ( md5Context != 0 ) {
			MD5Update ( md5Context, (XMP_Uns8*)(&hdvHeaderBlock), sizeof(SHDV_HeaderBlock) );
			MD5Update ( md5Context, (XMP_Uns8*)(&hdvFileBlock), sizeof(SHDV_FileBlock) );
		}

		// The xmpObj parameter must be provided in order to extract XMP
		if ( xmpObj == 0 ) return (md5Context != 0);

		// Standard def?
		const bool isSD = ((hdvHeaderBlock.mSignalMode == 0x80) || (hdvHeaderBlock.mSignalMode == 0));

		// Progressive vs interlaced extracted from high bit of ECCTB byte
		const bool clipIsProgressive = ((hdvHeaderBlock.mECCTB & 0x80) != 0);

		// Lowest three bits contain frame rate information
		const int sfr = (hdvHeaderBlock.mECCTB & 7) + (clipIsProgressive ? 0 : 8);

		// Sample scale and sample size.
		int clipSampleScale = 0;
		int clipSampleSize  = 0;
		std::string frameRate;

		// Frame rate
		switch ( sfr ) {
			case 0	: break; // Not valid in spec, but it's happening in test files.
			case 1	: clipSampleScale = 24000;	clipSampleSize = 1001; frameRate = "23.98p"; break;
			case 3	: clipSampleScale = 25;		clipSampleSize = 1;	   frameRate =    "25p"; break;
			case 4	: clipSampleScale = 30000;	clipSampleSize = 1001; frameRate = "29.97p"; break;
			case 11	: clipSampleScale = 25;		clipSampleSize = 1;	   frameRate =    "50i"; break;
			case 12	: clipSampleScale = 30000;	clipSampleSize = 1001; frameRate = "59.94i"; break;
		}

		containsXMP = true;

		// Frame size and PAR for HD (not clear on SD yet).
		std::string xmpString;
		XMP_StringPtr xmpValue = 0;

		if ( ! isSD ) {

			if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoFrameSize" )) ) {

				xmpValue = "1440";
				xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "w", &xmpString, 0 );
				if ( xmpString != xmpValue ) {
					xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", xmpValue, 0 );
				}

				xmpValue = "1080";
				xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "h", &xmpString, 0 );
				if ( xmpString != xmpValue ) {
					xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "h", xmpValue, 0 );
				}

				xmpValue = "pixels";
				xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "unit", &xmpString, 0 );
				if ( xmpString != xmpValue ) {
					xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", xmpValue, 0 );
				}
			}

			xmpValue = "4/3";
			if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoPixelAspectRatio" )) ) {
				xmpObj->SetProperty ( kXMP_NS_DM, "videoPixelAspectRatio", xmpValue, kXMP_DeleteExisting );
			}

		}

		// Sample size and scale.
		if ( clipSampleScale != 0 ) {

			char buffer[BUFFER_SIZE];

			if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimeScale" )) ) {
				snprintf(buffer,BUFFER_SIZE, "%d", clipSampleScale);
				xmpValue = buffer;
				xmpObj->SetProperty ( kXMP_NS_DM, "startTimeScale", xmpValue, kXMP_DeleteExisting );
			}

			if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimeSampleSize" )) ) {
				snprintf(buffer,BUFFER_SIZE, "%d", clipSampleSize);
				xmpValue = buffer;
				xmpObj->SetProperty ( kXMP_NS_DM, "startTimeSampleSize", xmpValue, kXMP_DeleteExisting );
			}

			if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "duration" )) ) {

				const int frameCount = (hdvFileBlock.mTotalFrame[0] << 24) + (hdvFileBlock.mTotalFrame[1] << 16) +
									   (hdvFileBlock.mTotalFrame[2] << 8) + hdvFileBlock.mTotalFrame[3];

				snprintf ( buffer,BUFFER_SIZE, "%d", frameCount );
				xmpValue = buffer;
				xmpObj->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", xmpValue, 0 );

				snprintf ( buffer,BUFFER_SIZE, "%d/%d", clipSampleSize, clipSampleScale );
				xmpValue = buffer;
				xmpObj->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "scale", xmpValue, 0 );

			}

		}

		// Time Code.
		if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimecode" )) ) {

			if ( (clipSampleScale != 0) && (clipSampleSize != 0) ) {

				const bool dropFrame = ( (0x40 & hdvFileBlock.mStartTimeCode[0]) != 0 ) && ( sfr == 4 || sfr == 12 );
				const char chDF = dropFrame ? ';' : ':';
				const int tcFrames  = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[0], 0x30 );
				const int tcSeconds = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[1], 0x70 );
				const int tcMinutes = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[2], 0x70 );
				const int tcHours   = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[3], 0x30 );

				// HH:MM:SS:FF or HH;MM;SS;FF
				char timecode[TIMECODE_SIZE];
				snprintf ( timecode,TIMECODE_SIZE, "%02d%c%02d%c%02d%c%02d", tcHours, chDF, tcMinutes, chDF, tcSeconds, chDF, tcFrames );
				std::string sonyTimeString = timecode;

				xmpObj->GetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", &xmpString, 0 );
				if ( xmpString != sonyTimeString ) {

					xmpObj->SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", sonyTimeString, 0 );

					std::string timeFormat;
					if ( clipSampleSize == 1 ) {

						// 24, 25, 40, 50, 60
						switch ( clipSampleScale ) {
							case 24 : timeFormat = "24"; break;
							case 25 : timeFormat = "25"; break;
							case 50 : timeFormat = "50"; break;
							default : XMP_Assert ( false );
						}

						timeFormat += "Timecode";

					} else {

						// 23.976, 29.97, 59.94
						XMP_Assert ( clipSampleSize == 1001 );
						switch ( clipSampleScale ) {
							case 24000 : timeFormat = "23976"; break;
							case 30000 : timeFormat =  "2997"; break;
							case 60000 : timeFormat =  "5994"; break;
							default	   : XMP_Assert( false );  break;
						}

						timeFormat += dropFrame ? "DropTimecode" : "NonDropTimecode";

					}

					xmpObj->SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeFormat", timeFormat, 0 );

				}

			}

		}

		if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "CreateDate" )) ) {

			// Clip has date and time in the case of DT (otherwise date and time haven't been set).
			bool clipHasDate = ((hdvFileBlock.mDT[0] == 'D') && (hdvFileBlock.mDT[1] == 'T'));

			// Creation date
			if ( clipHasDate ) {

				// YYYY-MM-DDThh:mm:ssZ
				char date[256];
				snprintf ( date,sizeof(date), "%4d-%02d-%02dT%02d:%02d:%02dZ",
						  hdvFileBlock.mFileNameYear + 2000,
						  hdvFileBlock.mFileNameMonth,
						  hdvFileBlock.mFileNameDay,
						  hdvFileBlock.mFileNameHour,
						  hdvFileBlock.mFileNameMinute,
						  hdvFileBlock.mFileNameSecond );

				XMP_StringPtr xmpDate = date;
				xmpObj->SetProperty ( kXMP_NS_XMP, "CreateDate", xmpDate, kXMP_DeleteExisting );

			}

		}

		// Frame rate.
		if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoFrameRate" )) ) {

			if ( frameRate.size() != 0 ) {
				xmpString = frameRate;
				xmpObj->SetProperty ( kXMP_NS_DM, "videoFrameRate", xmpString, kXMP_DeleteExisting );
			}

		}

	} catch ( ... ) {

		result = false;

	}

	return result;

}	// ReadIDXFile

// =================================================================================================
// SonyHDV_MetaHandlerCTor
// =======================

XMPFileHandler * SonyHDV_MetaHandlerCTor ( XMPFiles * parent )
{
	return new SonyHDV_MetaHandler ( parent );

}	// SonyHDV_MetaHandlerCTor

// =================================================================================================
// SonyHDV_MetaHandler::SonyHDV_MetaHandler
// ========================================

SonyHDV_MetaHandler::SonyHDV_MetaHandler ( XMPFiles * _parent )
{

	this->parent = _parent;	// Inherited, can't set in the prefix.
	this->handlerFlags = kSonyHDV_HandlerFlags;
	this->stdCharForm  = kXMP_Char8Bit;

	// Extract the root path and clip name.

	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 );

}	// SonyHDV_MetaHandler::SonyHDV_MetaHandler

// =================================================================================================
// SonyHDV_MetaHandler::~SonyHDV_MetaHandler
// =========================================

SonyHDV_MetaHandler::~SonyHDV_MetaHandler()
{

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

}	// SonyHDV_MetaHandler::~SonyHDV_MetaHandler

// =================================================================================================
// SonyHDV_MetaHandler::MakeClipFilePath
// =====================================

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

	*path = this->rootPath;
	*path += kDirChar;
	*path += "VIDEO";
	*path += kDirChar;
	*path += "HVR";
	*path += kDirChar;
	*path += this->clipName;
	*path += suffix;

	if ( ! checkFile ) return true;
	return Host_IO::Exists ( path->c_str() );

}	// SonyHDV_MetaHandler::MakeClipFilePath

// This method removes the timestamp information from a clip name. It returns the clip name with a following "_".
// For example: The clip name "00_0001_2007-08-06_165555" becomes "00_0001_".
static void RemoveTimeStampFromClipName(std::string &clipName)
{
	int usCount = 0;
	size_t i, limit = clipName.size();

	for ( i = 0; i < limit; ++i ) {
		if ( clipName[i] == '_' ) {
			++usCount;
			if ( usCount == 2 ) break;
		}
	}

	if ( i < limit ) clipName.erase ( i );
	clipName += '_';	// Make sure a final '_' is there for the search comparisons.
}

// =================================================================================================
// SonyHDV_MetaHandler::MakeIndexFilePath
// ======================================

bool SonyHDV_MetaHandler::MakeIndexFilePath ( std::string& idxPath, const std::string& _rootPath, const std::string& leafName )
{
	std::string tempPath;
	tempPath = _rootPath;
	tempPath += kDirChar;
	tempPath += "VIDEO";
	tempPath += kDirChar;
	tempPath += "HVR";

	idxPath = tempPath;
	idxPath += kDirChar;
	idxPath += leafName;
	idxPath += ".IDX";

	// Default case
	if ( Host_IO::GetFileMode ( idxPath.c_str() ) == Host_IO::kFMode_IsFile ) return true;

	// Spanned clip case

	// Scanning code taken from SonyHDV_CheckFormat
	// Can be isolated to a separate function.

	std::string clipNameStr = leafName;
	RemoveTimeStampFromClipName(clipNameStr);

	Host_IO::AutoFolder aFolder;
	std::string childName;
	bool found = false;

	aFolder.folder = Host_IO::OpenFolder ( tempPath.c_str() );
	while ( (! found) && Host_IO::GetNextChild ( aFolder.folder, &childName ) ) {
		size_t childLen = childName.size();
		if ( childLen < 4 ) continue;
		MakeUpperCase ( &childName );
		if ( childName.compare ( childLen-4, 4, ".IDX" ) != 0 ) continue;
		if ( childName.compare ( 0, clipNameStr.size(), clipNameStr ) == 0 ) {
			found = true;
			clipNameStr = childName;
			clipNameStr.erase ( childLen-4 );
		}
	}
	aFolder.Close();
	if ( ! found ) return false;

	idxPath = tempPath;
	idxPath += kDirChar;
	idxPath += clipNameStr;
	idxPath += ".IDX";

	return true;

}

// =================================================================================================
// SonyHDV_MetaHandler::MakeLegacyDigest
// =====================================

#define kHexDigits "0123456789ABCDEF"

void SonyHDV_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
{
	std::string idxPath;
	if ( ! this->MakeIndexFilePath ( idxPath, this->rootPath, this->clipName ) ) return;

	MD5_CTX context;
	unsigned char digestBin [16];
	bool dummy = false;
	MD5Init ( &context );
	ReadIDXFile ( idxPath, this->clipName, 0,  dummy, &context, false );
	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->erase();
	digestStr->append ( buffer, 32 );

}	// MakeLegacyDigest

// =================================================================================================
// SonyHDV_MetaHandler::GetFileModDate
// ===================================

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

bool SonyHDV_MetaHandler::GetFileModDate ( XMP_DateTime * modDate )
{

	// The Sony HDV locations of metadata:
	//	VIDEO/
	//		HVR/
	//			00_0001_2007-08-06_165555.IDX
	//			00_0001_2007-08-06_165555.XMP

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

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

	ok = this->MakeClipFilePath ( &fullPath, ".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;

}	// SonyHDV_MetaHandler::GetFileModDate

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

	noExtPath = rootPath + kDirChar + "VIDEO" + kDirChar + "HVR" + kDirChar + clipName;

	filePath = noExtPath + ".XMP";
	metadataFiles->push_back ( filePath );
	filePath = noExtPath + ".IDX";
	metadataFiles->push_back ( filePath );

}	// 	FillMetadataFiles_SonyHDV

// =================================================================================================
// SonyHDV_MetaHandler::IsMetadataWritable
// =======================================

bool SonyHDV_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.
	return Host_IO::Writable( itr->c_str(), true );
}// SonyHDV_MetaHandler::IsMetadataWritable


// =================================================================================================
// SonyHDV_MetaHandler::FillAssociatedResources
// ======================================
//
// This method returns all clip associated "media files","index files" whose name
// starts with XX_CCCC_ and side cars starting with XX_CCCC.
void SonyHDV_MetaHandler::FillAssociatedResources ( std::vector<std::string> * resourceList )
{
	// The possible associated resources:
	//	VIDEO/
	//		HVR/
	//			XX_CCCC_YYYY-MM-DD_hhmmss.M2T	// HDV media
	//			XX_CCCC_YYYY-MM-DD_hhmmss.IDX	// Metadata Index file
	//			XX_CCCC_YYYY-MM-DD_hhmmss.XMP	// sidecar
	//
	//			XX_CCCC_YYYY-MM-DD_hhmmss.AVI	// DV(AVI) medi
	//			XX_CCCC_YYYY-MM-DD_hhmmss.IDX	// Metadata Index file
	//			XX_CCCC_YYYY-MM-DD_hhmmss.XMP	// sidecar
	//
	//			XX_CCCC_YYYY-MM-DD_hhmmss.DV	// DV(RAW) media
	//			XX_CCCC_YYYY-MM-DD_hhmmss.IDX	// Metadata Index file
	//			XX_CCCC_YYYY-MM-DD_hhmmss.XMP	// sidecar
	//
	//			tracks.dat						// Clip database file

	std:: string hvrPath = this->rootPath + kDirChar + "VIDEO" + kDirChar + "HVR";
	std::string filePath;

	//Add RootPath
	filePath = this->rootPath + kDirChar;
	PackageFormat_Support::AddResourceIfExists( resourceList, filePath );

	// If XX_CCCC_YYYY-MM-DD_hhmmss is clip name then we remove YYYY-MM-DD_hhmmss from this and return
	// all files starting with XX_CCCC_ and having required extension.
	std::string clipNameWithoutTimeStamp = this->clipName;
	RemoveTimeStampFromClipName(clipNameWithoutTimeStamp);

	// Add media files.
	// We don't know the extension of the media so we will check for all
	// three possible extensions and add whichever is existing.
	
	// "AddResourceIfExists" will add all spanned clips that match the clip prefix "clipNameWithoutTimeStamp" 
	// and specified extensions.
	PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".M2T");

	PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".AVI");

	PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".DV");

	// Add Index files.
	PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".IDX");

	// Add sidecars.
	// For sidecars we will look for XX_CCCC*.XMP instead of XX_CCCC_*.XMP because we may generate such files
	// in case of spanning (in future) or logical paths.
	clipNameWithoutTimeStamp.erase(clipNameWithoutTimeStamp.end()-1);
	PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".XMP");

	//Add clip database file
	filePath = hvrPath + kDirChar + "tracks.dat";
	PackageFormat_Support::AddResourceIfExists(resourceList, filePath);

}	// SonyHDV_MetaHandler::FillAssociatedResources


// =================================================================================================
// SonyHDV_MetaHandler::CacheFileData
// ==================================

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

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

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

	std::string xmpPath;
	this->MakeClipFilePath ( &xmpPath, ".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 ( "SonyHDV XMP file open failure", kXMPErr_InternalFailure );
	this->parent->ioRef = xmpFile;

	XMP_Int64 xmpLen = xmpFile->Length();
	if ( xmpLen > 100*1024*1024 ) {
		XMP_Throw ( "SonyHDV 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;

}	// SonyHDV_MetaHandler::CacheFileData

// =================================================================================================
// SonyHDV_MetaHandler::ProcessXMP
// ===============================

void SonyHDV_MetaHandler::ProcessXMP()
{
	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() );
	}

	// Check the legacy digest.
	std::string oldDigest, newDigest;
	bool digestFound;
	digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "SonyHDV", &oldDigest, 0 );
	if ( digestFound ) {
		this->MakeLegacyDigest ( &newDigest );
		if ( oldDigest == newDigest ) return;
	}

	// Read the IDX legacy.
	std::string idxPath;
	if ( ! this->MakeIndexFilePath ( idxPath, this->rootPath, this->clipName ) ) return;
	ReadIDXFile ( idxPath, this->clipName, &this->xmpObj,  this->containsXMP, 0, digestFound );

}	// SonyHDV_MetaHandler::ProcessXMP

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

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

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

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

	this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() );

	// -------------------------------------------------
	// Update just the XMP file not the native IDX file.

	std::string xmpPath;
	this->MakeClipFilePath ( &xmpPath, ".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 SonyHDV XMP file", kXMPErr_ExternalFailure );
	}

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

}	// SonyHDV_MetaHandler::UpdateFile

// =================================================================================================
// SonyHDV_MetaHandler::WriteTempFile
// ==================================

void SonyHDV_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
{

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

}	// SonyHDV_MetaHandler::WriteTempFile

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