//
// C++ Interface: pluginmanager
//
// Description: 
//
//
// Author: Benjamin Mesing <bensmail@gmx.net>, (C) 2004
//
// Copyright: See COPYING file that comes with this distribution
//
//
#ifndef __PLUGINMANAGER_H_2004_08_11
#define __PLUGINMANAGER_H_2004_08_11

#include <string>
#include <map>
#include <set>
#include <vector>

#include <qdom.h>
#include <QDir>
#include <qobject.h>
#include <QTranslator>

// NXml
#include <xmldata.h>
#include <ixmlstorable.h>

// NPlugin
#include "plugininformer.h"
#include "ipluginuser.h"
#include "plugincontainer.h"	// also for PluginInformation


using namespace std;

class QDialog;
class QDir;
class QWidget;
class QTableWidget;
class QTranslator;
class PluginProgressDlg;

namespace NXml
{
	class XmlData;
}

namespace NUtil
{
	class IProgressObserver;
	class ProgressDisplayDlg;
}

namespace NPlugin 
{

class IProvider;
class PluginListItem;

/** @brief The PluginManager can be used to load and manage plugins.
  * 
  * It informs all IPluginUser instances registered via the 
  * addPluginUser() function about loading and unloading of plugins.
  *
  * The settings for all plugins will be loaded on loadSettings() and stored
  * in the PluginManager. If a plugin is loaded, the plugin manager will 
  * search for the settings of this plugin and call its loadSettings function.
  *
  * The settings of the manager and the plugins can be stored via the saveSettings 
  * function, where the settings for all active plugins and the plugins 
  * which are disabled will be stored. If a plugin is no longer available 
  * (i.e. neither disabled nor active) its settings will not be saved even
  * if they were loaded before.
  *
  * Only one plugin of a given name can be loaded at a time, however multiple 
  * plugins with that name can be available in different directories.
  *
  * @author Benjamin Mesing
  */
class PluginManager : public QObject, public PluginInformer, public NXml::IXmlStorable, public IPluginUser
{
	Q_OBJECT
	
	friend class PluginControlDlgMeditator;
private:
	typedef vector<IPluginUser*> PluginUserContainer;
	PluginUserContainer _users;
protected:

	/** @brief This holds information about the plugin. */
	struct PluginData
	{
		PluginData() :
			information()
		{
			libraryHandle = 0;
			pPlugin = 0;
			pTranslator = 0;
		}
		PluginInformation information;
		string directory;
		bool informationEquals(const PluginData& pd) const
		{
			return information == pd.information;
		}
		/** Accessors for convenience */
		const string& name() const { return information.name; }
		/** Accessors for convenience */
		const string& version() const { return information.version; }
		/** Accessors for convenience */
		const string& author() const { return information.author; }
		
		/** @brief A pointer to the instance of the plugin, 0 if the plugin is not loaded. */
		PluginContainer* pPlugin;
		
		/** @brief The translator for the plugin (if any, otherwise 0). */
		QTranslator* pTranslator;
		/** @brief A handle of the loaded library for this plugin, 0 if the plugin is not loaded. */
		void* libraryHandle;
		/** @brief operator so it can be used in sets */
		bool operator<(const PluginData& pd) const
		{
			if (name() != pd.name())
				return name() < pd.name();
			if (version() != pd.version())
				return version() < pd.version();
			if (author() != pd.author())
				return author() < pd.author();
//			if (directoy != pd.directoy)
				return directory < pd.directory;
		}
	};
	/** @brief Function object to find plugins with equal information. */
	struct PluginInformationEquals
	{
		PluginInformationEquals(const PluginData& pd) : _pd(pd)
		{}
		bool operator()(const PluginData& pd) const 
		{ return pd.information == _pd.information; }
		bool operator()(const pair<PluginContainer*, PluginData>& p) const
		{ return p.second.information == _pd.information; }
	private:
		const PluginData& _pd;
	};
	/** @brief Function object to find plugins which are equal. */
	struct PluginEquals
	{
		PluginEquals(const PluginData& pd) : _pd(pd)
		{}
		bool operator()(const PluginData& pd) const 
		{ return pd.information == _pd.information && pd.directory == _pd.directory; }
	private:
		const PluginData& _pd;
	};
	
	/** @brief Function object to find plugins point to the same plugin. */
	struct SamePlugin
	{
		SamePlugin(const PluginData& pd) : _pd(pd)
		{}
		bool operator()(const PluginData& pd) const 
		{ return _pd.name() == pd.name() && _pd.author() == pd.author(); }
	private:
		const PluginData& _pd;
	};

	/** This returns all plugins currently available.
	  *
	  * These are plugins that are in the plugin directories and plugins 
	  * which are loaded but not longer in any directory.
	  */
	vector<PluginData> getAvailablePlugins() const;
	
	/** @brief This loads the plugin and informs the plugin users about the loading. 
	  *
	  * The plugin will be added to the list of loaded plugins. If a plugin with
	  * the same pluginName is already loaded, loading will be aborted.
	  * @param directory the directory where the plugin is located
	  * @param pluginName the name of the plugin
	  * @returns a pointer to the plugin loaded, 0 if loading failed or a plugin of
	  * the given name was already loaded.
	  * @note You should have called loadSettings before because else
	  * this function will be unloaded. 
	  */
	PluginContainer* loadPlugin( const string& directory, const string& pluginName);
	/** @brief This unloads the plugin and informs the plugin users about the unloading. 
	  * 
	  * It will delete the plugin and close the library, so make sure that every reference
	  * is removed in the plugin users.
	  * @param pPlugin the plugin to be unloaded 
	  * @pre the plugin must be loaded
	  */
	void unloadPlugin(PluginContainer* pPlugin);
	/** @brief This unloads the plugin and informs the plugin users about the unloading. 
	  * 
	  * It will delete the plugin and close the library, so make sure that every reference
	  * is removed in the plugin users.
	  * @param pluginName the name of the plugin to be unloaded 
	  * @pre the plugin must be loaded
	  */
	void unloadPlugin(const string& pluginName);
private:
	
	friend class PluginListItem;
	/** @brief The directories the plugin manager searches for plugins. */
	vector<string> _directories;
	/** @brief The provider which provides informations for the plugins. */
	IProvider* _pProvider;
	/** @brief This holds the settings for all available plugins. 
	  *
	  * This is done to hold the settings for plugins which are not
	  * loaded so they can also be saved.
	  *
	  * Implementation note: The QDomElements here come from the QDomTree
	  * loaded on loadSettings(). Because of the QDom semantics (including 
	  * garbage collection) they won't be deleted. So it is save to access
	  * them.
	  */
	map<string, const QDomElement> _pluginSettings;
	/** @brief The version of the settings tree used by this class. */
	const QString _settingsVersion;
	/** @brief This holds a pointer to the dialog displaying the status of the plugin 
	  * loading process 
	  *
	  * This is only set if the dialog is currently active, else it holds 0.
	  */
 	NUtil::ProgressDisplayDlg* _pProgressDlg;
	/** @brief This holds a pointer to the control dialog.
	  *
	  * This is only set if the dialog is currently active, else it holds 0.
	  */
	QDialog* _pControlDialog;
	/** @brief This check if an error occured in a dl* function and if so prints
	  * the error message to stderr.
	  * @returns true if an error occured, else false
	  */
	bool checkDlError() const;
	
	typedef map<string, PluginData> PluginToDataMap;
	/** @brief This datastructure maps all plugins that were loaded at least once 
	  * to their plugin data.
	  *
	  * When plugins are unloaded the will not be removed from this map.
	  */
	PluginToDataMap _plugins;
	
	
// 	typedef map<PluginContainer*, PluginData> PluginToDataMap;
	/** @brief This holds the plugins currently loaded.
	  *
	  * It maps them to the corresponding data. */
// 	PluginToDataMap _loadedPlugins;
 	/** @brief Holds the plugins which were marked as disabled in the settings. 
 	  *
 	  * This is not kept up to date, but only set on loadSettings(). It is
 	  * used by loadPlugins() to not load the plugins disabled according to
 	  * the settings.
 	  */
 	set<string> _disabledPlugins;
	/** Holds the listview where the plugins are shown to the user, only set
	  * while the plugin dialog is shown.  */
	QTableWidget* _pPluginDisplay;
public:
	/**
	  * 
	  * @param directories the directories the the manager searches for plugins 
	  * @param pProvider the provider which provides informations for the plugins
	  * @param pUser the user that cares for the plugin changes
	  */
	PluginManager(vector<string> directories, IProvider* pProvider);
	~PluginManager();

	/** Returns true if a plugin with the given name is already loaded, else false. */
	bool isLoaded(QString name) const;

	/** @brief Save the settings from this plugin container into the given XML tree
	  * 
	  * Currently the only information is the plugins which are disabled.
	  * @param outData XML Document which owns parent
	  * @param parent the parent where under to add the settings
	  */
	virtual void saveSettings(NXml::XmlData& outData, QDomElement parent) const;
	/** @brief Loads the settings for the plugins from inData.
	  * 
	  * Currently the only information is the plugins which are disabled.
	  * @param source the element where the information is stored
	  * @returns the next sibling of source or source if source did was not a 
	  * node which contains information for the pluginmanager
	  */
	virtual QDomElement loadSettings(QDomElement source);
	/** @brief This shows a dialog which can be used to control the plugins.
	  *
	  * It allows to enable and disable the different plugins.
	  * 
	  * @param pParent the widget this dialog should be have as parent,
	  * hand 0 if it should be a top level dialog
	  */
	void showControlDialog(QWidget* pParent);
	/** @brief This loads all available plugins excluding the disabled ones.
	  *
	  * This loads all plugins found in the plugin directories. It does not load plugins
	  * which are disabled.\n
	  * Use loadSettings() to read the disabled plugins. */
	void loadPlugins();

	/** This returns the plugins currently loaded
	  * 
	  */
	vector<PluginContainer*> getLoadedPlugins() const;
	/** @brief Returns the observer where to report progress to.
	  *
	  * @returns 0 if no observer is available
	  */
	NUtil::IProgressObserver* progressObserver();

	/** @name IPluginUser Interface
	  * 
	  * Implementation of the IPluginUser interface. This works as a multiplex, 
	  * forwarding each addPlugin/removePlugin from the controlled PluginContainers
	  * to all IPluginUsers registered.
	  */
	//@{
	virtual void addPlugin(Plugin* pPlugin);
	virtual void removePlugin(Plugin* pPlugin);
	//@}
};

};

#endif	//  __PLUGINMANAGER_H_2004_08_11
