// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
// Copyright 2011 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.
// =================================================================================================

#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/HandlerRegistry.h"

#if EnablePluginManager
	#include "XMPFiles/source/PluginHandler/XMPAtoms.h"
#endif

#if EnablePhotoHandlers
	#include "XMPFiles/source/FileHandlers/JPEG_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/PSD_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/TIFF_Handler.hpp"
#endif

#if EnableDynamicMediaHandlers
	#include "XMPFiles/source/FileHandlers/AIFF_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/ASF_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/FLV_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/MP3_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/MPEG2_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/MPEG4_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/P2_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/WAVE_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/RIFF_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/SWF_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/XDCAM_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/WEBP_Handler.hpp"
#endif

#if EnableMiscHandlers
	#include "XMPFiles/source/FileHandlers/InDesign_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/PNG_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/PostScript_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/UCF_Handler.hpp"
	#include "XMPFiles/source/FileHandlers/GIF_Handler.hpp"
#endif

//#if EnablePacketScanning
//#include "XMPFiles/source/FileHandlers/Scanner_Handler.hpp"
//#endif

using namespace Common;

#if EnablePluginManager
	using namespace XMP_PLUGIN;
#endif

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

#if EnableDynamicMediaHandlers

static const char * kP2ContentChildren[] = { "CLIP", "VIDEO", "AUDIO", "ICON", "VOICE", "PROXY", 0 };

static inline bool CheckP2ContentChild ( const std::string & folderName )
{
	for ( int i = 0; kP2ContentChildren[i] != 0; ++i ) {
		if ( folderName == kP2ContentChildren[i] ) return true;
	}
	return false;
}

#endif

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

//
// Static init
//
HandlerRegistry* HandlerRegistry::sInstance = 0;

/*static*/ HandlerRegistry& HandlerRegistry::getInstance()
{
	if ( sInstance == 0 ) sInstance = new HandlerRegistry();
	return *sInstance;
}

/*static*/ void HandlerRegistry::terminate()
{
	delete sInstance;
	sInstance = 0;
}

HandlerRegistry::HandlerRegistry()
{
	mFolderHandlers		= new XMPFileHandlerTable;
	mNormalHandlers		= new XMPFileHandlerTable;
	mOwningHandlers		= new XMPFileHandlerTable;
	mReplacedHandlers	= new XMPFileHandlerTable;
}

HandlerRegistry::~HandlerRegistry()
{
	delete mFolderHandlers;
	delete mNormalHandlers;
	delete mOwningHandlers;
	delete mReplacedHandlers;
}

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

void HandlerRegistry::initialize()
{

	bool allOK = true;	// All of the linked-in handler registrations must work, do one test at the end.
	
	// -----------------------------------------
	// Register the directory-oriented handlers.

#if EnableDynamicMediaHandlers
	allOK &= this->registerFolderHandler ( kXMP_P2File, kP2_HandlerFlags, P2_CheckFormat, P2_MetaHandlerCTor );
	allOK &= this->registerFolderHandler ( kXMP_SonyHDVFile, kSonyHDV_HandlerFlags, SonyHDV_CheckFormat, SonyHDV_MetaHandlerCTor );
	allOK &= this->registerFolderHandler ( kXMP_XDCAM_FAMFile, kXDCAM_HandlerFlags, XDCAM_CheckFormat, XDCAM_MetaHandlerCTor );
	allOK &= this->registerFolderHandler ( kXMP_XDCAM_SAMFile, kXDCAM_HandlerFlags, XDCAM_CheckFormat, XDCAM_MetaHandlerCTor );
	allOK &= this->registerFolderHandler ( kXMP_XDCAM_EXFile, kXDCAMEX_HandlerFlags, XDCAMEX_CheckFormat, XDCAMEX_MetaHandlerCTor );
#endif

	// ------------------------------------------------------------------------------------------
	// Register the file-oriented handlers that don't want to open and close the file themselves.

#if EnablePhotoHandlers
	allOK &= this->registerNormalHandler ( kXMP_JPEGFile, kJPEG_HandlerFlags, JPEG_CheckFormat, JPEG_MetaHandlerCTor );
	allOK &= this->registerNormalHandler ( kXMP_PhotoshopFile, kPSD_HandlerFlags, PSD_CheckFormat, PSD_MetaHandlerCTor );
	allOK &= this->registerNormalHandler ( kXMP_TIFFFile, kTIFF_HandlerFlags, TIFF_CheckFormat, TIFF_MetaHandlerCTor );
#endif

#if EnableDynamicMediaHandlers
	allOK &= this->registerNormalHandler ( kXMP_WMAVFile, kASF_HandlerFlags, ASF_CheckFormat, ASF_MetaHandlerCTor );
	allOK &= this->registerNormalHandler ( kXMP_MP3File, kMP3_HandlerFlags, MP3_CheckFormat, MP3_MetaHandlerCTor );
	allOK &= this->registerNormalHandler ( kXMP_WAVFile, kWAVE_HandlerFlags, WAVE_CheckFormat, WAVE_MetaHandlerCTor );
	allOK &= this->registerNormalHandler ( kXMP_AVIFile, kRIFF_HandlerFlags, RIFF_CheckFormat, RIFF_MetaHandlerCTor );
	allOK &= this->registerNormalHandler ( kXMP_WEBPFile, kWEBP_HandlerFlags, WEBP_CheckFormat, WEBP_MetaHandlerCTor );
	allOK &= this->registerNormalHandler ( kXMP_SWFFile, kSWF_HandlerFlags, SWF_CheckFormat, SWF_MetaHandlerCTor );
	allOK &= this->registerNormalHandler ( kXMP_MPEG4File, kMPEG4_HandlerFlags, MPEG4_CheckFormat, MPEG4_MetaHandlerCTor );
	allOK &= this->registerNormalHandler ( kXMP_MOVFile, kMPEG4_HandlerFlags, MPEG4_CheckFormat, MPEG4_MetaHandlerCTor );	// ! Yes, MPEG-4 includes MOV.
	allOK &= this->registerNormalHandler ( kXMP_FLVFile, kFLV_HandlerFlags, FLV_CheckFormat, FLV_MetaHandlerCTor );
	allOK &= this->registerNormalHandler ( kXMP_AIFFFile, kAIFF_HandlerFlags, AIFF_CheckFormat, AIFF_MetaHandlerCTor );
#endif

#if EnableMiscHandlers
	allOK &= this->registerNormalHandler ( kXMP_InDesignFile, kInDesign_HandlerFlags, InDesign_CheckFormat, InDesign_MetaHandlerCTor );
	allOK &= this->registerNormalHandler ( kXMP_PNGFile, kPNG_HandlerFlags, PNG_CheckFormat, PNG_MetaHandlerCTor );
	allOK &= this->registerNormalHandler ( kXMP_UCFFile, kUCF_HandlerFlags, UCF_CheckFormat, UCF_MetaHandlerCTor );
	allOK &= this->registerNormalHandler ( kXMP_GIFFile, kGIF_HandlerFlags, GIF_CheckFormat, GIF_MetaHandlerCTor );
	// ! EPS and PostScript have the same handler, EPS is a proper subset of PostScript.
	allOK &= this->registerNormalHandler ( kXMP_EPSFile, kPostScript_HandlerFlags, PostScript_CheckFormat, PostScript_MetaHandlerCTor );
	allOK &= this->registerNormalHandler ( kXMP_PostScriptFile, kPostScript_HandlerFlags, PostScript_CheckFormat, PostScript_MetaHandlerCTor );
#endif

	// ------------------------------------------------------------------------------------
	// Register the file-oriented handlers that need to open and close the file themselves.

#if EnableDynamicMediaHandlers
	allOK &= this->registerOwningHandler ( kXMP_MPEGFile, kMPEG2_HandlerFlags, MPEG2_CheckFormat, MPEG2_MetaHandlerCTor );
	allOK &= this->registerOwningHandler ( kXMP_MPEG2File, kMPEG2_HandlerFlags, MPEG2_CheckFormat, MPEG2_MetaHandlerCTor );
#endif

	if ( ! allOK ) XMP_Throw ( "Failure initializing linked-in file handlers", kXMPErr_InternalFailure );

}	// HandlerRegistry::initialize

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

bool HandlerRegistry::registerFolderHandler( XMP_FileFormat			format,
										     XMP_OptionBits			flags,
										     CheckFolderFormatProc	checkProc,
										     XMPFileHandlerCTor		handlerCTor,
											 bool					replaceExisting /*= false*/ )
{
	XMP_Assert ( format != kXMP_UnknownFile );

	XMP_Assert ( flags & kXMPFiles_HandlerOwnsFile );
	XMP_Assert ( flags & kXMPFiles_FolderBasedFormat );
	XMP_Assert ( (flags & kXMPFiles_CanInjectXMP) ? (flags & kXMPFiles_CanExpand) : 1 );
	
	if ( replaceExisting ) 
	{
		//
		// Remember previous file handler for this format.
		// Reject if there is already a replacement.
		//
		if( mReplacedHandlers->find( format ) == mReplacedHandlers->end() )
		{
			XMPFileHandlerInfo* standardHandler = this->getHandlerInfo( format );

			if( standardHandler != NULL )
			{
				mReplacedHandlers->insert( mReplacedHandlers->end(), XMPFileHandlerTablePair( format, *standardHandler ) );
			}
			else
			{
				// skip registration if there is nothing to replace
				return false;
			}
		}
		else
		{
			// skip registration if there is already a replacing handler registered for this format
			return false;
		}

		// remove existing handler
		this->removeHandler ( format );
	} 
	else 
	{
		// skip registration if there is already a handler registered for this format
		if ( this->getFormatInfo ( format ) ) return false;
	}

	//
	// register handler
	//
	XMPFileHandlerInfo handlerInfo ( format, flags, checkProc, handlerCTor );
	mFolderHandlers->insert ( mFolderHandlers->end(), XMPFileHandlerTablePair ( format, handlerInfo ) );

	return true;

}	// HandlerRegistry::registerFolderHandler

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

bool HandlerRegistry::registerNormalHandler( XMP_FileFormat			format,
										     XMP_OptionBits			flags,
										     CheckFileFormatProc	checkProc,
											 XMPFileHandlerCTor		handlerCTor,
											 bool					replaceExisting /*= false*/ )
{
	XMP_Assert ( format != kXMP_UnknownFile );

	XMP_Assert ( ! (flags & kXMPFiles_HandlerOwnsFile) );
	XMP_Assert ( ! (flags & kXMPFiles_FolderBasedFormat) );
	XMP_Assert ( (flags & kXMPFiles_CanInjectXMP) ? (flags & kXMPFiles_CanExpand) : 1 );

	if ( replaceExisting ) 
	{
		//
		// Remember previous file handler for this format.
		// Reject if there is already a replacement.
		//
		if( mReplacedHandlers->find( format ) == mReplacedHandlers->end() )
		{
			XMPFileHandlerInfo* standardHandler = this->getHandlerInfo( format );

			if( standardHandler != NULL )
			{
				mReplacedHandlers->insert( mReplacedHandlers->end(), XMPFileHandlerTablePair( format, *standardHandler ) );
			}
			else
			{
				// skip registration if there is nothing to replace
				return false;
			}
		}
		else
		{
			// skip registration if there is already a replacing handler registered for this format
			return false;
		}

		// remove existing handler
		this->removeHandler ( format );
	} 
	else 
	{
		// skip registration if there is already a handler registered for this format
		if ( this->getFormatInfo ( format ) ) return false;
	}

	//
	// register handler
	//
	XMPFileHandlerInfo handlerInfo ( format, flags, checkProc, handlerCTor );
	mNormalHandlers->insert ( mNormalHandlers->end(), XMPFileHandlerTablePair ( format, handlerInfo ) );
	return true;

}	// HandlerRegistry::registerNormalHandler

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

bool HandlerRegistry::registerOwningHandler( XMP_FileFormat			format,
										     XMP_OptionBits			flags,
										     CheckFileFormatProc	checkProc,
											 XMPFileHandlerCTor		handlerCTor,
											 bool					replaceExisting /*= false*/ )
{
	XMP_Assert ( format != kXMP_UnknownFile );

	XMP_Assert ( flags & kXMPFiles_HandlerOwnsFile );
	XMP_Assert ( ! (flags & kXMPFiles_FolderBasedFormat) );
	XMP_Assert ( (flags & kXMPFiles_CanInjectXMP) ? (flags & kXMPFiles_CanExpand) : 1 );

	if ( replaceExisting ) 
	{
		//
		// Remember previous file handler for this format.
		// Reject if there is already a replacement.
		//
		if( mReplacedHandlers->find( format ) == mReplacedHandlers->end() )
		{
			XMPFileHandlerInfo* standardHandler = this->getHandlerInfo( format );

			if( standardHandler != NULL )
			{
				mReplacedHandlers->insert( mReplacedHandlers->end(), XMPFileHandlerTablePair( format, *standardHandler ) );
			}
			else
			{
				// skip registration if there is nothing to replace
				return false;
			}
		}
		else
		{
			// skip registration if there is already a replacing handler registered for this format
			return false;
		}

		// remove existing handler
		this->removeHandler ( format );
	} 
	else 
	{
		// skip registration if there is already a handler registered for this format
		if ( this->getFormatInfo ( format ) ) return false;
	}

	//
	// register handler
	//
	XMPFileHandlerInfo handlerInfo ( format, flags, checkProc, handlerCTor );
	mOwningHandlers->insert ( mOwningHandlers->end(), XMPFileHandlerTablePair ( format, handlerInfo ) );
	return true;

}	// HandlerRegistry::registerOwningHandler

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

void HandlerRegistry::removeHandler ( XMP_FileFormat format ) {

	XMPFileHandlerTablePos handlerPos;

	handlerPos = mFolderHandlers->find ( format );
	if ( handlerPos != mFolderHandlers->end() ) {
		mFolderHandlers->erase ( handlerPos );
		XMP_Assert ( ! this->getFormatInfo ( format ) );
		return;
	}

	handlerPos = mNormalHandlers->find ( format );
	if ( handlerPos != mNormalHandlers->end() ) {
		mNormalHandlers->erase ( handlerPos );
		XMP_Assert ( ! this->getFormatInfo ( format ) );
		return;
	}

	handlerPos = mOwningHandlers->find ( format );
	if ( handlerPos != mOwningHandlers->end() ) {
		mOwningHandlers->erase ( handlerPos );
		XMP_Assert ( ! this->getFormatInfo ( format ) );
		return;
	}

}	// HandlerRegistry::removeHandler

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

XMP_FileFormat HandlerRegistry::getFileFormat( const std::string & fileExt, bool addIfNotFound /*= false*/ )
{
	if ( ! fileExt.empty() ) {
		for ( int i=0; kFileExtMap[i].format != 0; ++i ) {
			if ( fileExt == kFileExtMap[i].ext ) return kFileExtMap[i].format;
		}
	}

	#if EnablePluginManager
		return ResourceParser::getPluginFileFormat ( fileExt, addIfNotFound );
	#else
		return kXMP_UnknownFile;
	#endif
}

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

XMPFileHandlerInfo*	HandlerRegistry::getHandlerInfo( XMP_FileFormat format )
{
	XMPFileHandlerTablePos handlerPos;

	handlerPos = mFolderHandlers->find( format );

	if( handlerPos != mFolderHandlers->end() ) 
	{
		return &(handlerPos->second);
	}

	handlerPos = mNormalHandlers->find ( format );

	if( handlerPos != mNormalHandlers->end() ) 
	{
		return &(handlerPos->second);
	}

	handlerPos = mOwningHandlers->find ( format );

	if( handlerPos != mOwningHandlers->end() ) 
	{
		return &(handlerPos->second);
	}

	return NULL;
}

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

XMPFileHandlerInfo*	HandlerRegistry::getStandardHandlerInfo( XMP_FileFormat format )
{
	XMPFileHandlerTablePos handlerPos = mReplacedHandlers->find( format );

	if( handlerPos != mReplacedHandlers->end() )
	{
		return &(handlerPos->second);
	}
	else
	{
		return this->getHandlerInfo( format );
	}
}

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

bool HandlerRegistry::isReplaced( XMP_FileFormat format )
{
	return ( mReplacedHandlers->find( format ) != mReplacedHandlers->end() );
}

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

bool HandlerRegistry::getFormatInfo( XMP_FileFormat format, XMP_OptionBits* flags /*= 0*/ )
{
	if ( flags == 0 ) flags = &voidOptionBits;

	XMPFileHandlerInfo*	handler = this->getHandlerInfo( format );

	if( handler != NULL )
	{
		*flags = handler->flags;
	}

	return ( handler != NULL );
}	// HandlerRegistry::getFormatInfo

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

XMPFileHandlerInfo* HandlerRegistry::pickDefaultHandler ( XMP_FileFormat format, const std::string & fileExt )
{
	if ( format == kXMP_UnknownFile ) format = this->getFileFormat ( fileExt );
	if ( format == kXMP_UnknownFile ) return 0;

	XMPFileHandlerTablePos handlerPos;

	handlerPos = mNormalHandlers->find ( format );
	if ( handlerPos != mNormalHandlers->end() ) return &handlerPos->second;

	handlerPos = mOwningHandlers->find ( format );
	if ( handlerPos != mOwningHandlers->end() ) return &handlerPos->second;

	handlerPos = mFolderHandlers->find ( format );
	if ( handlerPos != mFolderHandlers->end() ) return &handlerPos->second;

	return 0;
}

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

XMPFileHandlerInfo* HandlerRegistry::selectSmartHandler( XMPFiles* session, XMP_StringPtr clientPath, XMP_FileFormat format, XMP_OptionBits openFlags )
{

	// The normal case for selectSmartHandler is when OpenFile is given a string file path. All of
	// the stages described below have slight special cases when OpenFile is given an XMP_IO object
	// for client-managed I/O. In that case the only handlers considered are those for embedded XMP
	// that do not need to own the file.
	//
	// There are 4 stages in finding a handler, ending at the first success:
	//   1. If the client passes in a format, try that handler.
	//   2. Try all of the folder-oriented handlers.
	//   3. Try a file-oriented handler based on the file extension.
	//   4. Try all of the file-oriented handlers.
	//
	// The most common case is almost certainly #3, so we want to get there quickly. Most of the
	// time the client won't pass in a format, so #1 takes no time. The folder-oriented handler
	// checks are preceded by minimal folder checks. These checks are meant to be fast in the
	// failure case. The folder-oriented checks have to go before the general file-oriented checks
	// because the client path might be to one of the inner files, and we might have a file-oriented
	// handler for that kind of file, but we want to recognize the clip. More details are below.
	//
	// In brief, the folder-oriented formats use shallow trees with specific folder names and
	// highly stylized file names. The user thinks of the tree as a collection of clips, each clip
	// is stored as multiple files for video, audio, metadata, etc. The folder-oriented stage has
	// to be first because there can be files in the structure that are also covered by a
	// file-oriented handler.
	//
	// In the file-oriented case, the CheckProc should do as little as possible to determine the
	// format, based on the actual file content. If that is not possible, use the format hint. The
	// initial CheckProc calls (steps 1 and 3) has the presumed format in this->format, the later
	// calls (step 4) have kXMP_UnknownFile there.
	//
	// The folder-oriented checks need to be well optimized since the formats are relatively rare,
	// but have to go first and could require multiple file system calls to identify. We want to
	// get to the first file-oriented guess as quickly as possible, that is the real handler most of
	// the time.
	//
	// The folder-oriented handlers are for things like P2 and XDCAM that use files distributed in a
	// well defined folder structure. Using a portion of P2 as an example:
	//	.../MyMovie
	//		CONTENTS
	//			CLIP
	//				0001AB.XML
	//				0002CD.XML
	//			VIDEO
	//				0001AB.MXF
	//				0002CD.MXF
	//			VOICE
	//				0001AB.WAV
	//				0002CD.WAV
	//
	// The user thinks of .../MyMovie as the container of P2 stuff, in this case containing 2 clips
	// called 0001AB and 0002CD. The exact folder structure and file layout differs, but the basic
	// concepts carry across all of the folder-oriented handlers.
	//
	// The client path can be a conceptual clip path like .../MyMovie/0001AB, or a full path to any
	// of the contained files. For file paths we have to behave the same as the implied conceptual
	// path, e.g. we don't want .../MyMovie/CONTENTS/VOICE/0001AB.WAV to invoke the WAV handler.
	// There might also be a mapping from user friendly names to clip names (e.g. Intro to 0001AB).
	// If so that is private to the handler and does not affect this code.
	//
	// In order to properly handle the file path input we have to look for the folder-oriented case
	// before any of the file-oriented cases. And since these are relatively rare, hence fail most of
	// the time, we have to get in and out fast in the not handled case. That is what we do here.
	//
	// The folder-oriented processing done here is roughly:
	//
	// 1. Get the state of the client path: does-not-exist, is-file, is-folder, is-other.
	// 2. Reject is-folder and is-other, they can't possibly be a valid case.
	// 3. For does-not-exist:
	//	3a. Split the client path into a leaf component and root path.
	//	3b. Make sure the root path names an existing folder.
	//	3c. Make sure the root folder has a viable top level child folder (e.g. CONTENTS).
	// 4. For is-file:
	//	4a. Split the client path into a root path, grandparent folder, parent folder, and leaf name.
	//	4b. Make sure the parent or grandparent has a viable name (e.g. CONTENTS).
	// 5. Try the registered folder handlers.
	//
	// For the common case of "regular" files, we should only get as far as 3b. This is just 1 file
	// system call to get the client path state and some string processing.

	bool readOnly = XMP_OptionIsClear( openFlags, kXMPFiles_OpenForUpdate );

	Host_IO::FileMode clientMode;
	std::string rootPath;
	std::string leafName;
	std::string fileExt;
	std::string emptyStr;

	XMPFileHandlerInfo* handlerInfo	= 0;
	bool foundHandler				= false;
	
	if ( openFlags & kXMPFiles_ForceGivenHandler ) {
		// We're being told to blindly use the handler for the given format and nothing else.
		return this->pickDefaultHandler ( format, emptyStr );	// Picks based on just the format.
	}

	if ( session->UsesClientIO() ) {

		XMP_Assert ( session->ioRef != 0 );
		clientMode = Host_IO::kFMode_IsFile;

	} else {

		clientMode = Host_IO::GetFileMode( clientPath );
		if ( (clientMode == Host_IO::kFMode_IsFolder) || (clientMode == Host_IO::kFMode_IsOther) ) return 0;

		rootPath = clientPath;
		XIO::SplitLeafName ( &rootPath, &leafName );
		
		if ( leafName.empty() ) return 0;

		if ( clientMode == Host_IO::kFMode_IsFile ) {
			// Only extract the file extension for existing files. Non-existing files can only be
			// logical clip names, and they don't have file extensions.
			XIO::SplitFileExtension ( &leafName, &fileExt );
		}

	}

	session->format	= kXMP_UnknownFile;	// Make sure it is preset for later checks.
	session->openFlags = openFlags;

	// If the client passed in a format, try that first.

	if( format != kXMP_UnknownFile ) 
	{
		handlerInfo = this->pickDefaultHandler( format, emptyStr );	// Picks based on just the format.

		if( handlerInfo != 0 ) 
		{
			if( ( session->ioRef == 0 ) && (! ( handlerInfo->flags & kXMPFiles_HandlerOwnsFile ) ) ) 
			{
				session->ioRef = XMPFiles_IO::New_XMPFiles_IO( clientPath, readOnly, &session->errorCallback);
				if ( session->ioRef == 0 ) return 0;
			}
			
			session->format = format;	// ! Hack to tell the CheckProc session is an initial call.

			if( handlerInfo->flags & kXMPFiles_FolderBasedFormat ) 
			{
#if 0
				std::string gpName, parentName;
				if ( clientMode == Host_IO::kFMode_IsFile ) {
					XIO::SplitLeafName( &rootPath, &parentName );
					XIO::SplitLeafName( &rootPath, &gpName );
					if ( format != kXMP_XDCAM_FAMFile ) MakeUpperCase( &gpName );	// ! Save the original case for XDCAM-FAM.
					MakeUpperCase( &parentName );
				}
				CheckFolderFormatProc CheckProc = (CheckFolderFormatProc) (handlerInfo->checkProc);
				foundHandler = CheckProc ( handlerInfo->format, rootPath, gpName, parentName, leafName, session );
#else
				// *** Don't try here yet. These are messy, needing existence checking and path processing.
				// *** CheckFolderFormatProc CheckProc = (CheckFolderFormatProc) (handlerInfo->checkProc);
				// *** foundHandler = CheckProc ( handlerInfo->format, rootPath, gpName, parentName, leafName, session );
				// *** Don't let OpenStrictly cause an early exit:
				if( openFlags & kXMPFiles_OpenStrictly ) openFlags ^= kXMPFiles_OpenStrictly;
#endif
			} 
			else 
			{
				bool tryThisHandler = true;

				if( session->UsesClientIO() ) 
				{
					if ( (handlerInfo->flags & kXMPFiles_UsesSidecarXMP) ||
						(handlerInfo->flags & kXMPFiles_HandlerOwnsFile) ) tryThisHandler = false;
				}

				if( tryThisHandler ) 
				{
					CheckFileFormatProc CheckProc = (CheckFileFormatProc) (handlerInfo->checkProc);
					foundHandler = CheckProc ( format, clientPath, session->ioRef, session );
				}
			}

			XMP_Assert( foundHandler || (session->tempPtr == 0) );
			
			if ( foundHandler ) return handlerInfo;
			
			handlerInfo = 0;	// ! Clear again for later use.
		}

		if ( openFlags & kXMPFiles_OpenStrictly ) return 0;

	}

#if EnableDynamicMediaHandlers	// All of the folder handlers are for dynamic media.

	// Try the folder handlers if appropriate.

	if( session->UsesLocalIO() ) 
	{
		XMP_Assert ( handlerInfo == 0 );
		XMP_Assert ( (clientMode == Host_IO::kFMode_IsFile) || (clientMode == Host_IO::kFMode_DoesNotExist) );

		std::string gpName, parentName;

		if( clientMode == Host_IO::kFMode_DoesNotExist ) 
		{
			// 3. For does-not-exist:
			//	3a. Split the client path into a leaf component and root path.
			//	3b. Make sure the root path names an existing folder.
			//	3c. Make sure the root folder has a viable top level child folder.

			// ! This does "return 0" on failure, the file does not exist so a normal file handler can't apply.

			if ( Host_IO::GetFileMode ( rootPath.c_str() ) != Host_IO::kFMode_IsFolder ) return 0;
			
			session->format = checkTopFolderName ( rootPath );
			
			if ( session->format == kXMP_UnknownFile ) return 0;

			handlerInfo = this->tryFolderHandlers( session->format, rootPath, gpName, parentName, leafName, session );	// ! Parent and GP are empty.

			return handlerInfo;	// ! Return found handler or 0.
		}

		XMP_Assert ( clientMode == Host_IO::kFMode_IsFile );

		// 4. For is-file:
		//	4a. Split the client path into root, grandparent, parent, and leaf.
		//	4b. Make sure the parent or grandparent has a viable name.

		// ! Don't "return 0" on failure, this has to fall through to the normal file handlers.

		XIO::SplitLeafName( &rootPath, &parentName );
		XIO::SplitLeafName( &rootPath, &gpName );
		std::string origGPName ( gpName );	// ! Save the original case for XDCAM-FAM.
		MakeUpperCase( &parentName );
		MakeUpperCase( &gpName );

		session->format = checkParentFolderNames( rootPath, gpName, parentName, leafName );

		if( session->format != kXMP_UnknownFile ) 
		{
			if( (session->format == kXMP_XDCAM_FAMFile) &&
			    ( (parentName == "CLIP") || (parentName == "EDIT") || (parentName == "SUB") ) ) 
			{
					// ! The standard says Clip/Edit/Sub, but we just shifted to upper case.
					gpName = origGPName;	// ! XDCAM-FAM has just 1 level of inner folder, preserve the "MyMovie" case.
			}

			handlerInfo = tryFolderHandlers ( session->format, rootPath, gpName, parentName, leafName, session );
			if ( handlerInfo != 0 ) return handlerInfo;

		}
	}

#endif	// EnableDynamicMediaHandlers

	// Try an initial file-oriented handler based on the extension.

	if( session->UsesLocalIO() ) 
	{
		handlerInfo = pickDefaultHandler ( kXMP_UnknownFile, fileExt );	// Picks based on just the extension.

		if( handlerInfo != 0 ) 
		{
			if( (session->ioRef == 0) && (! (handlerInfo->flags & kXMPFiles_HandlerOwnsFile)) ) 
			{
				session->ioRef = XMPFiles_IO::New_XMPFiles_IO ( clientPath, readOnly, &session->errorCallback);
				if ( session->ioRef == 0 ) return 0;
			} 
			else if( (session->ioRef != 0) && (handlerInfo->flags & kXMPFiles_HandlerOwnsFile) ) 
			{
				delete session->ioRef;	// Close is implicit in the destructor.
				session->ioRef = 0;
			}
			
			session->format = handlerInfo->format;	// ! Hack to tell the CheckProc this is an initial call.
			CheckFileFormatProc CheckProc = (CheckFileFormatProc) (handlerInfo->checkProc);
			foundHandler = CheckProc ( handlerInfo->format, clientPath, session->ioRef, session );
			XMP_Assert ( foundHandler || (session->tempPtr == 0) );
			
			if ( foundHandler ) return handlerInfo;
		}
	}

	// Search the handlers that don't want to open the file themselves.

	if( session->ioRef == 0 ) 
	{
		session->ioRef = XMPFiles_IO::New_XMPFiles_IO ( clientPath, readOnly, &session->errorCallback );
		if ( session->ioRef == 0 ) return 0;
	}
	
	XMPFileHandlerTablePos handlerPos = mNormalHandlers->begin();

	for( ; handlerPos != mNormalHandlers->end(); ++handlerPos ) 
	{
		session->format = kXMP_UnknownFile;	// ! Hack to tell the CheckProc this is not an initial call.
		handlerInfo = &handlerPos->second;
		CheckFileFormatProc CheckProc = (CheckFileFormatProc) (handlerInfo->checkProc);
		foundHandler = CheckProc ( handlerInfo->format, clientPath, session->ioRef, session );
		XMP_Assert ( foundHandler || (session->tempPtr == 0) );
		if ( foundHandler ) return handlerInfo;
	}

	// Search the handlers that do want to open the file themselves.

	if( session->UsesLocalIO() ) 
	{
		delete session->ioRef;	// Close is implicit in the destructor.
		session->ioRef = 0;
		handlerPos = mOwningHandlers->begin();

		for( ; handlerPos != mOwningHandlers->end(); ++handlerPos ) 
		{
			session->format = kXMP_UnknownFile;	// ! Hack to tell the CheckProc this is not an initial call.
			handlerInfo = &handlerPos->second;
			CheckFileFormatProc CheckProc = (CheckFileFormatProc) (handlerInfo->checkProc);
			foundHandler = CheckProc ( handlerInfo->format, clientPath, session->ioRef, session );
			XMP_Assert ( foundHandler || (session->tempPtr == 0) );
			if ( foundHandler ) return handlerInfo;
		}
	}

	// Failed to find a smart handler.

	return 0;

}	// HandlerRegistry::selectSmartHandler

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

#if EnableDynamicMediaHandlers

XMPFileHandlerInfo* HandlerRegistry::tryFolderHandlers( XMP_FileFormat format,
													    const std::string & rootPath,
														const std::string & gpName,
														const std::string & parentName,
														const std::string & leafName,
														XMPFiles * parentObj )
{
	bool foundHandler = false;
	XMPFileHandlerInfo * handlerInfo = 0;
	XMPFileHandlerTablePos handlerPos;

	// We know we're in a possible context for a folder-oriented handler, so try them.

	if( format != kXMP_UnknownFile ) 
	{

		// Have an explicit format, pick that or nothing.
		handlerPos = mFolderHandlers->find ( format );
	
		if( handlerPos != mFolderHandlers->end() ) 
		{
			handlerInfo = &handlerPos->second;
			CheckFolderFormatProc CheckProc = (CheckFolderFormatProc) (handlerInfo->checkProc);
			foundHandler = CheckProc ( handlerInfo->format, rootPath, gpName, parentName, leafName, parentObj );
			XMP_Assert ( foundHandler || (parentObj->tempPtr == 0) );
		}
	} 
	else 
	{
		// Try all of the folder handlers.
		for( handlerPos = mFolderHandlers->begin(); handlerPos != mFolderHandlers->end(); ++handlerPos ) 
		{
			handlerInfo = &handlerPos->second;
			CheckFolderFormatProc CheckProc = (CheckFolderFormatProc) (handlerInfo->checkProc);
			foundHandler = CheckProc ( handlerInfo->format, rootPath, gpName, parentName, leafName, parentObj );
			XMP_Assert ( foundHandler || (parentObj->tempPtr == 0) );
			if ( foundHandler ) break;	// ! Exit before incrementing handlerPos.
		}
	}

	if ( ! foundHandler ) handlerInfo = 0;

	return handlerInfo;
}

#endif

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

#if EnableDynamicMediaHandlers

/*static*/ XMP_FileFormat HandlerRegistry::checkTopFolderName( const std::string & rootPath )
{
	// This is called when the input path to XMPFiles::OpenFile does not name an existing file (or
	// existing anything). We need to quickly decide if this might be a logical path for a folder
	// handler. See if the root contains the top content folder for any of the registered folder
	// handlers. This check does not have to be precise, the handler will do that. This does have to
	// be fast.
	//
	// Since we don't have many folder handlers, this is simple hardwired code.

	std::string childPath = rootPath;
	childPath += kDirChar;
	size_t baseLen = childPath.size();

	// P2 .../MyMovie/CONTENTS/<group>/... - only check for CONTENTS/CLIP
	childPath += "CONTENTS";
	childPath += kDirChar;
	childPath += "CLIP";
	if ( Host_IO::GetFileMode ( childPath.c_str() ) == Host_IO::kFMode_IsFolder ) return kXMP_P2File;
	childPath.erase ( baseLen );

	// XDCAM-FAM .../MyMovie/<group>/... - only check for Clip and MEDIAPRO.XML
	childPath += "Clip";	// ! Yes, mixed case.
	if ( Host_IO::GetFileMode ( childPath.c_str() ) == Host_IO::kFMode_IsFolder ) {
		childPath.erase ( baseLen );
		childPath += "MEDIAPRO.XML";
		if ( Host_IO::GetFileMode ( childPath.c_str() ) == Host_IO::kFMode_IsFile ) return kXMP_XDCAM_FAMFile;
	}
	childPath.erase ( baseLen );

	// XDCAM-SAM .../MyMovie/PROAV/<group>/... - only check for PROAV/CLPR
	childPath += "PROAV";
	childPath += kDirChar;
	childPath += "CLPR";
	if ( Host_IO::GetFileMode ( childPath.c_str() ) == Host_IO::kFMode_IsFolder ) return kXMP_XDCAM_SAMFile;
	childPath.erase ( baseLen );

	// XDCAM-EX .../MyMovie/BPAV/<group>/... - check for BPAV/CLPR
	childPath += "BPAV";
	childPath += kDirChar;
	childPath += "CLPR";
	if ( Host_IO::GetFileMode ( childPath.c_str() ) == Host_IO::kFMode_IsFolder ) return kXMP_XDCAM_EXFile;
	childPath.erase ( baseLen );

	// Sony HDV .../MyMovie/VIDEO/HVR/<file>.<ext> - check for VIDEO/HVR
	childPath += "VIDEO";
	childPath += kDirChar;
	childPath += "HVR";
	if ( Host_IO::GetFileMode ( childPath.c_str() ) == Host_IO::kFMode_IsFolder ) return kXMP_SonyHDVFile;
	childPath.erase ( baseLen );

	return kXMP_UnknownFile;

}	// CheckTopFolderName

#endif

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

#if EnableDynamicMediaHandlers

/*static*/ XMP_FileFormat HandlerRegistry::checkParentFolderNames( const std::string& rootPath,
																   const std::string& gpName,
																   const std::string& parentName, 
																   const std::string& leafName )
{
	IgnoreParam ( parentName );

	// This is called when the input path to XMPFiles::OpenFile names an existing file. We need to
	// quickly decide if this might be inside a folder-handler's structure. See if the containing
	// folders might match any of the registered folder handlers. This check does not have to be
	// precise, the handler will do that. This does have to be fast.
	//
	// Since we don't have many folder handlers, this is simple hardwired code. Note that the caller
	// has already shifted the names to upper case.

	// P2  .../MyMovie/CONTENTS/<group>/<file>.<ext> - check CONTENTS and <group>
	if ( (gpName == "CONTENTS") && CheckP2ContentChild ( parentName ) ) return kXMP_P2File;

	// XDCAM-EX  .../MyMovie/BPAV/CLPR/<clip>/<file>.<ext> - check for BPAV/CLPR
	// ! This must be checked before XDCAM-SAM because both have a "CLPR" grandparent.
	if ( gpName == "CLPR" ) {
		std::string tempPath, greatGP;
		tempPath = rootPath;
		XIO::SplitLeafName ( &tempPath, &greatGP );
		MakeUpperCase ( &greatGP );
		if ( greatGP == "BPAV" ) return kXMP_XDCAM_EXFile;
	}

	// XDCAM-FAM  .../MyMovie/<group>/<file>.<ext> - check that <group> is CLIP, or EDIT, or SUB
	// ! The standard says Clip/Edit/Sub, but the caller has already shifted to upper case.
	if ( (parentName == "CLIP") || (parentName == "EDIT") || (parentName == "SUB") ) return kXMP_XDCAM_FAMFile;

	// XDCAM-SAM  .../MyMovie/PROAV/<group>/<clip>/<file>.<ext> - check for PROAV and CLPR or EDTR
	if ( (gpName == "CLPR") || (gpName == "EDTR") ) {
		std::string tempPath, greatGP;
		tempPath = rootPath;
		XIO::SplitLeafName ( &tempPath, &greatGP );
		MakeUpperCase ( &greatGP );
		if ( greatGP == "PROAV" ) return kXMP_XDCAM_SAMFile;
	}

	// Sony HDV  .../MyMovie/VIDEO/HVR/<file>.<ext> - check for VIDEO and HVR
	if ( (gpName == "VIDEO") && (parentName == "HVR") ) return kXMP_SonyHDVFile;

	return kXMP_UnknownFile;

}	// CheckParentFolderNames

#endif
