/*======================================================================================

	Program:		Range Imaging Toolkit (RITK)
	Module:			ritkPluginManager.cxx
	Version:		$Revision: 2447 $
	Date:			$Date: 2012-01-23 16:26:19 +0100 (Mo, 23 Jan 2012) $

	Copyright (c) 2010-2011
	Pattern Recognition Lab, 
    University Erlangen-Nuremberg, Germany
    All rights reserved.

	This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
	See RITKCopyright.txt for more information.

	http://www5.informatik.uni-erlangen.de/research/software/ritk

========================================================================================*/

#include "ritkPluginManager.h"
#include "ritkPluginFactories.h"
#include "ritkManager.h"
#include "ritkDebugManager.h"

#include <QDirIterator>

#include <iostream>

namespace ritk
{

PluginManager::PluginManager()
{
}


PluginManager::~PluginManager()
{
}


//----------------------------------------------------------------------------
QString
PluginManager::GetPluginDir()
{
	return m_PluginDir;
}


//----------------------------------------------------------------------------
void 
PluginManager::UpdateAvailablePluginLoaders(QString Dir)
{	
	// Update the plugin directory
	m_PluginDir = Dir;
	typedef QList<PluginStruct>::Iterator IteratorType;

	// 1st step: Remove managed DLLs that are not valid anymore
	for ( IteratorType it = m_AvailablePluginLoaders.begin(); it != m_AvailablePluginLoaders.end(); it++ )
	{
		// Check if the DLL still provides a valid factory
		QObject *FactoryInstance = (*it).Loader->instance();
		ApplicationPluginFactory *AFac = qobject_cast<ApplicationPluginFactory*>(FactoryInstance);
		FilterPluginFactory *FFac = qobject_cast<FilterPluginFactory*>(FactoryInstance);
		SourcePluginFactory *PFac = qobject_cast<SourcePluginFactory*>(FactoryInstance);
		ImageTypePluginFactory *IFac = qobject_cast<ImageTypePluginFactory*>(FactoryInstance);
		GenericPluginFactory *GFac = qobject_cast<GenericPluginFactory*>(FactoryInstance);
		if ( FactoryInstance )
			delete FactoryInstance;
		if ( !AFac && !FFac && !PFac && !IFac && !GFac )
		{
			it = m_AvailablePluginLoaders.erase(it);
			if ( it == m_AvailablePluginLoaders.end() )
				break;
		}
	}

	// 2nd step: Search non-managed DLLs
	// Create directory walker
	QDirIterator DirWalker(m_PluginDir, QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories);
 
    // Scan directories
	while( DirWalker.hasNext() )
	{
		// Take next element
		DirWalker.next();
 
		// If the file is not a DLL continue
		/*if ( DirWalker.fileInfo().completeSuffix() != "dll" )
			continue;*/

		//std::cout << "Scanning " << DirWalker.fileInfo().absoluteFilePath().toStdString() << std::endl;

		// Found a DLL. Check if it is already managed.
		bool Managed = false;
		for ( IteratorType it = m_AvailablePluginLoaders.begin(); it != m_AvailablePluginLoaders.end(); it++ )
		{
			if ( DirWalker.fileInfo().absoluteFilePath() == (*it).Loader->fileName() )
			{
				Managed = true;
				break;
			}
		}

		// If the DLL is managed continue
		if ( Managed )
			continue;

		// Get the instance of this plugin factory
		QPluginLoader *Loader = new QPluginLoader(DirWalker.fileInfo().absoluteFilePath());
		QObject *FactoryInstance = Loader->instance();

		/*if ( !FactoryInstance )
		{
				std::cout << "Could not create a factory..." << std::endl;
				std::cout << "	Error: " << Loader->errorString().toStdString() << std::endl << std::endl;
		}*/

		// Check the type (continue if no valid factory is provided)
		PluginTypes Type = Generic;
		ApplicationPluginFactory *AFac = qobject_cast<ApplicationPluginFactory*>(FactoryInstance);
		FilterPluginFactory *FFac = qobject_cast<FilterPluginFactory*>(FactoryInstance);
		SourcePluginFactory *SFac = qobject_cast<SourcePluginFactory*>(FactoryInstance);
		ImageTypePluginFactory *IFac = qobject_cast<ImageTypePluginFactory*>(FactoryInstance);
		GenericPluginFactory *GFac = qobject_cast<GenericPluginFactory*>(FactoryInstance);
		if ( AFac )
		{
			Type = Application;
		}
		else if ( FFac )
		{
			Type = Filter;
		}
		else if ( SFac )
		{
			Type = Source;
		}
		else if ( IFac )
		{
			Type = ImageType;
		}
		else if ( GFac )
		{
			Type = Generic;
		}
		else
		{
			if ( FactoryInstance )
				delete FactoryInstance;
			//Loader->unload();
			delete Loader;
			continue;
		}

		// All tests passed, add the loader to the internal list
		PluginStruct NewLoader;
		NewLoader.Loader = Loader;
		NewLoader.PluginType = Type;
		NewLoader.ModuleName = DirWalker.fileInfo().fileName();
		//NewLoader.ModulName = DirWalker.fileInfo().absoluteFilePath();
		NewLoader.Started = false;
		PluginDescriptor *Descriptor = dynamic_cast<PluginDescriptor*>(FactoryInstance);
		if ( Descriptor )
		{
			NewLoader.PluginName = Descriptor->GetName();
			NewLoader.Description = Descriptor->GetDescription();
		}
		else
		{
			NewLoader.PluginName = "NO_NAME";
			NewLoader.Description = "NO_DESCRIPTION";
		}
		m_AvailablePluginLoaders.append(NewLoader);
		delete FactoryInstance;
		//Loader->unload();
    }
}


//----------------------------------------------------------------------------
QList<PluginManager::PluginStruct>&
PluginManager::GetAvailablePluginLoaders()
{
	return m_AvailablePluginLoaders;
}


//----------------------------------------------------------------------------
void
PluginManager::StartPluginAndConfigure(PluginStruct& p, QHash<QString, QString> ParameterHash)
{
	StartPlugin(p);

	// Due to invalid versions
	if ( !p.InstancePointer )
		return;

	// Configure/set parameters
	QHashIterator<QString, QString> itHash(ParameterHash);
	while (itHash.hasNext()) {
		itHash.next();
		p.InstancePointer->SetParameter(itHash.key(), itHash.value());
	}	
}


//----------------------------------------------------------------------------
void
PluginManager::StartPlugin(PluginStruct& p)
{
	// Load the plugin
	QObject *FactoryInstance = p.Loader->instance();
	ApplicationPluginFactory *AFac = qobject_cast<ApplicationPluginFactory*>(FactoryInstance);
	FilterPluginFactory *FFac = qobject_cast<FilterPluginFactory*>(FactoryInstance);
	SourcePluginFactory *SFac = qobject_cast<SourcePluginFactory*>(FactoryInstance);
	ImageTypePluginFactory *IFac = qobject_cast<ImageTypePluginFactory*>(FactoryInstance);
	GenericPluginFactory *GFac = qobject_cast<GenericPluginFactory*>(FactoryInstance);
	if ( !AFac && !FFac && !SFac && !IFac && !GFac )
	{
		LOG_DEB("Could not create factory!");
		if ( FactoryInstance )
			delete FactoryInstance;
		return;
	}

	// Create the plugin instance
	PluginBase *PluginInstance = NULL;
	if ( AFac )
		PluginInstance = AFac->CreateInstance();
	else if ( SFac )
		PluginInstance = SFac->CreateInstance();
	else if ( IFac )
		PluginInstance = IFac->CreateInstance();
	else if ( FFac )
		PluginInstance = FFac->CreateInstance();
	else if ( GFac )
		PluginInstance = GFac->CreateInstance();
	if ( !PluginInstance )
	{
		LOG_DEB("Could not create plugin from module " << p.ModuleName << "!");
		delete FactoryInstance;
		//p.Loader->unload();
		return;
	}

	// Register the plugin
	PluginDescriptor *Descriptor = dynamic_cast<PluginDescriptor*>(FactoryInstance);
	QString PluginName = "NO_NAME";
	if ( Descriptor )
		PluginName = Descriptor->GetName();
	bool b = Manager::GetInstance().RegisterPlugin(PluginInstance,PluginName);
	if ( !b )
	{
		LOG_DEB("Could not register plugin!");
		delete FactoryInstance;
		delete PluginInstance;
		//p.Loader->unload();
		return;
	}

	// Update the list element
	p.Started = true;
	p.InstancePointer = PluginInstance;

	// Delete the factory instance
	delete FactoryInstance;

	LOG_DEB("Plugin " << p.ModuleName << " started...");

	// Emit signal that the status of the plugin changed
	emit PluginStatusChanged(p);
}


//----------------------------------------------------------------------------
void
PluginManager::StopPlugin(PluginStruct& p)
{
	try
	{
		// Unregister the plugin
		bool b = Manager::GetInstance().UnregisterPlugin(p.InstancePointer);
		if ( !b )
		{
			LOG_DEB("Could not unregister plugin!");
			return;
		}

		// Delete the plugin instance
		delete p.InstancePointer;

		// Unload the plugin
		//p.Loader->unload();

		// Update the list element
		p.Started = false;
		p.InstancePointer = NULL;

		LOG_DEB("Plugin " << p.ModuleName << " stopped...");

		// Emit signal that the status of the plugin changed
		emit PluginStatusChanged(p);
	}
	catch ( itk::ExceptionObject &e )
	{
		LOG_DEB(e);
	}
}

} // end namespace ritk
