// =================================================================================================
// 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 "Module.h"
#include "HostAPI.h"

namespace XMP_PLUGIN
{

static bool CheckAPICompatibility_V1 ( const PluginAPIRef pluginAPIs )
{
	// these plugin APIs are mandatory to run an XMP file handler
	if ( pluginAPIs->mTerminatePluginProc
		&& pluginAPIs->mSetHostAPIProc
		&& pluginAPIs->mInitializeSessionProc
		&& pluginAPIs->mTerminateSessionProc
		&& pluginAPIs->mCheckFileFormatProc
		&& pluginAPIs->mCheckFolderFormatProc
		&& pluginAPIs->mGetFileModDateProc
		&& pluginAPIs->mCacheFileDataProc
		&& pluginAPIs->mUpdateFileProc
		&& pluginAPIs->mWriteTempFileProc )
	{
		return true;
	}
	return false;
}

static bool CheckAPICompatibility_V2 ( const PluginAPIRef pluginAPIs )
{
	if ( CheckAPICompatibility_V1 ( pluginAPIs ) )
	{
		if ( pluginAPIs->mFillMetadataFilesProc 
			&& pluginAPIs->mFillAssociatedResourcesProc )
		{
			return true;
		}
	}
	return false;
}

static bool CheckAPICompatibility_V3 ( const PluginAPIRef pluginAPIs )
{
	if ( CheckAPICompatibility_V2 ( pluginAPIs ) )
	{
		if ( pluginAPIs->mIsMetadataWritableProc )
		{
			return true;
		}
	}
	return false;
}

static bool CheckAPICompatibility ( const PluginAPIRef pluginAPIs )
{
	// Note: This is the place where we can reject old plugins.
	//       For example if we consider all functionality of
	//       plugin API version 2 mandatory we can reject
	//       plugin version 1 by returning false in case 1.
	switch ( pluginAPIs->mVersion )
	{
	case 1:
		return CheckAPICompatibility_V1 ( pluginAPIs );
		break;

	case 2:
		return CheckAPICompatibility_V2 ( pluginAPIs );
		break;

	case 3:
		return CheckAPICompatibility_V3 ( pluginAPIs );
		break;

	default:
		// The loaded plugin is newer than the host.
		// Only basic functionality to run the plugin is required.
		return CheckAPICompatibility_V1 ( pluginAPIs );
		break;
	}
}

PluginAPIRef Module::getPluginAPIs() 
{ 
	//
	// return ref. to Plugin API, load module if not yet loaded
	//
	if ( !mPluginAPIs || mLoaded != kModuleLoaded )
	{
		if ( !load() )
		{
			XMP_Throw ( "Plugin API not available.", kXMPErr_Unavailable );
		}
	}
	return mPluginAPIs;
}

bool Module::load()
{
	XMP_AutoLock lock ( &mLoadingLock, kXMP_WriteLock );
	return loadInternal();
}

void Module::unload()
{
	XMP_AutoLock lock (&mLoadingLock, kXMP_WriteLock);
	unloadInternal();
}

void Module::unloadInternal()
{
	WXMP_Error error;

	//
	// terminate plugin
	//
	if( mPluginAPIs != NULL )
	{
		if( mPluginAPIs->mTerminatePluginProc ) 
		{
			mPluginAPIs->mTerminatePluginProc( &error );
		}
		delete mPluginAPIs;
		mPluginAPIs = NULL;
	}

	//
	// unload plugin module
	//
	if( mLoaded != kModuleNotLoaded )
	{
		UnloadModule(mHandle, false);
		mHandle = NULL;
		if( mLoaded == kModuleLoaded )
		{
			//
			// Reset mLoaded to kModuleNotLoaded, if the module was loaded successfully.
			// Otherwise let it remain kModuleErrorOnLoad so that we won't try to load 
			// it again if some other handler ask to do so.
			//
			mLoaded = kModuleNotLoaded;
		}
	}

	CheckError( error );
}

bool Module::loadInternal()
{
	if( mLoaded == kModuleNotLoaded )
	{
		const char * errorMsg = NULL;

		//
		// load module
		//
		mLoaded = kModuleErrorOnLoad;
		mHandle = LoadModule(mPath, false);

		if( mHandle != NULL )
		{
			//
			// get entry point function pointer
			//
			InitializePluginProc InitializePlugin = reinterpret_cast<InitializePluginProc>( 
				GetFunctionPointerFromModuleImpl(mHandle, "InitializePlugin") );	// legacy entry point

			InitializePlugin2Proc InitializePlugin2 = reinterpret_cast<InitializePlugin2Proc>( 
				GetFunctionPointerFromModuleImpl(mHandle, "InitializePlugin2") );

			if( InitializePlugin2 != NULL || InitializePlugin != NULL )
			{
				std::string moduleID;
				GetResourceDataFromModule(mHandle, "MODULE_IDENTIFIER", "txt", moduleID);
				mPluginAPIs = new PluginAPI();
				memset( mPluginAPIs, 0, sizeof(PluginAPI) );
				mPluginAPIs->mSize = sizeof(PluginAPI);
				mPluginAPIs->mVersion = XMP_PLUGIN_VERSION;	// informational: the latest version that the host knows about

				WXMP_Error error;

				//
				// initialize plugin by calling entry point function
				//
				if( InitializePlugin2 != NULL )
				{
					HostAPIRef hostAPI = PluginManager::getHostAPI( XMP_HOST_API_VERSION );
					InitializePlugin2( moduleID.c_str(), hostAPI, mPluginAPIs, &error );

					if ( error.mErrorID == kXMPErr_NoError )
					{
						// check all function pointers are correct based on version numbers
						if( CheckAPICompatibility( mPluginAPIs ) )
						{
							mLoaded = kModuleLoaded;
						}
						else
						{
							errorMsg = "Incompatible plugin API version.";
						}
					}
					else
					{
						errorMsg = "Plugin initialization failed.";
					}
				}
				else if( InitializePlugin != NULL )
				{
					// initialize through legacy plugin entry point
					InitializePlugin( moduleID.c_str(), mPluginAPIs, &error );

					if ( error.mErrorID == kXMPErr_NoError ) {
						// check all function pointers are correct based on version numbers
						bool compatibleAPIs = CheckAPICompatibility(mPluginAPIs);

						if ( compatibleAPIs )
						{
							//
							// set host API at plugin
							//
							HostAPIRef hostAPI = PluginManager::getHostAPI( mPluginAPIs->mVersion );
							
							mPluginAPIs->mSetHostAPIProc( hostAPI, &error );

							if( error.mErrorID == kXMPErr_NoError )
							{
								mLoaded = kModuleLoaded;
							}
							else
							{
								errorMsg = "Plugin API incomplete.";
							}
						}
						else
						{
							errorMsg = "Incompatible plugin API version.";
						}
					}
					else
					{
						errorMsg = "Plugin initialization failed.";
					}
				}
			}

			if( mLoaded != kModuleLoaded )
			{
				//
				// plugin wasn't loaded and initialized successfully,
				// so unload the module
				//
				this->unloadInternal();
			}
		}
		else
		{
			errorMsg = "Can't load module";
		}

		if ( mLoaded != kModuleLoaded && errorMsg )
		{
			//
			// error occurred
			//
			throw XMP_Error( kXMPErr_InternalFailure, errorMsg);
		}
	}

	return ( mLoaded == kModuleLoaded ); 

}

} //namespace XMP_PLUGIN
