/*! \file piplugin.h
* \brief Plugin helpers
*/
/*
	PIP - Platform Independent Primitives
	Plugin helpers
	Ivan Pelipenko peri4ko@yandex.ru

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	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 the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef PIPLUGIN_H
#define PIPLUGIN_H

#ifndef PIP_FREERTOS

#include "pilibrary.h"
#include "pistringlist.h"

#ifdef DOXYGEN


//! Declare plugin export functions, should be used before other PIP_PLUGIN_* macros
//! \relatedalso PIPluginLoader
#define PIP_PLUGIN

//! Set user version to check it while loading
//! \relatedalso PIPluginLoader
#define PIP_PLUGIN_SET_USER_VERSION(version)

//! Add pointer to future merge with plugin. Type is integer
//! \relatedalso PIPluginLoader
#define PIP_PLUGIN_ADD_STATIC_SECTION(type, ptr)

//! Declare function to merge static sections. This is functions
//! with 3 arguments: (int type, void * from, void * to).
//! This function invoked first while loading plugin with
//! "from" - plugin scope, "to" - application scope, and second
//! (optionally) on \a PIPluginLoader::mergeStatic() method with
//! "from" - application scope, "to" - plugin scope. So with macro
//! you can merge application and plugin static data.
//! \relatedalso PIPluginLoader
#define PIP_PLUGIN_STATIC_SECTION_MERGE

//! Mark method to export
//! \relatedalso PIPluginLoader
#define PIP_PLUGIN_EXPORT


#else


#ifdef WINDOWS
#  define PIP_PLUGIN_EXPORT __declspec(dllexport)
#else
#  define PIP_PLUGIN_EXPORT
#endif

#define __PIP_PLUGIN_LOADER_VERSION_FUNC__ pip_loader_version
#define __PIP_PLUGIN_STATIC_MERGE_FUNC__   pip_merge_static
#define __PIP_PLUGIN_LOADER_VERSION__      2

#define PIP_PLUGIN_SET_USER_VERSION(v) \
	STATIC_INITIALIZER_BEGIN \
		PIPluginInfo * pi = PIPluginInfoStorage::instance()->currentInfo(); \
		if (pi) pi->setUserVersion(v); \
	STATIC_INITIALIZER_END

#define PIP_PLUGIN_ADD_STATIC_SECTION(type, ptr) \
	STATIC_INITIALIZER_BEGIN \
		PIPluginInfo * pi = PIPluginInfoStorage::instance()->currentInfo(); \
		if (pi) pi->setStaticSection(type, ptr); \
	STATIC_INITIALIZER_END

#define PIP_PLUGIN \
	extern "C" { \
		PIP_PLUGIN_EXPORT int __PIP_PLUGIN_LOADER_VERSION_FUNC__() {return __PIP_PLUGIN_LOADER_VERSION__;} \
	}

#define PIP_PLUGIN_STATIC_SECTION_MERGE \
	extern "C" { \
		PIP_PLUGIN_EXPORT void __PIP_PLUGIN_STATIC_MERGE_FUNC__(int type, void * from, void * to); \
	} \
	void __PIP_PLUGIN_STATIC_MERGE_FUNC__(int type, void * from, void * to)


#endif



class PIP_EXPORT PIPluginInfo {
public:
	PIPluginInfo();

	void setUserVersion(const PIString & v);
	void setStaticSection(int type, void * ptr);
	PIString userVersion() const;
	PIMap<int, void*> staticSections() const;

private:
	PIString user_version;
	PIMap<int, void*> static_sections;

};



class PIP_EXPORT PIPluginInfoStorage {
public:
	PIPluginInfoStorage();

	PIPluginInfo * currentInfo();
	PIPluginInfo * pluginInfo(void * p);
	PIPluginInfo * applicationInfo();
	PIPluginInfo * enterPlugin(void * p);
	void unloadPlugin(void * p);

	static PIPluginInfoStorage * instance();

private:
	NO_COPY_CLASS(PIPluginInfoStorage)

	void * current;
	PIMap<void*, PIPluginInfo * > info;

};




class PIP_EXPORT PIPluginLoader {
public:
	typedef int(*FunctionLoaderVersion)();
	typedef void(*FunctionStaticMerge)(int, void *, void *);

	//! Possible load plugin error
	enum Error {
		Unknown              /** No \a load call yet */ ,
		NoError              /** No error */ ,
		LibraryLoadError     /** System can`t load library */ ,
		MissingSymbols       /** Can`t find necessary symbols */ ,
		InvalidLoaderVersion /** Integer version mismatch */ ,
		InvalidUserVersion   /** User version mismatch */ ,
	};

	//! Contruscts loader with filename "name"
	PIPluginLoader(const PIString & name = PIString());

	//! Destructor
	~PIPluginLoader();


	//! Load plugin with base filename "name". Loader try prefix "lib"
	//! and suffix ".dll", ".so" or ".dylib", depends on platform
	bool load(const PIString & name);

	//! Unload plugin and free library
	void unload();

	//! Returns if plugin is successfully loaded
	bool isLoaded() const;

	//! Returns error of last \a load() call
	Error lastError() const;

	//! Returns error message of last \a load() call
	PIString lastErrorText() const;

	//! Set if %PIPluginLoader should print load messages, \b true by default
	void setMessages(bool yes);

	//! Returns if %PIPluginLoader should print load messages, \b true by default
	bool isMessages() const;

	//! Returns loaded plugin library path
	PIString libPath();

	//! Resolve symbol "name" from plugin library
	void * resolve(const char * name);

	//! Invoke plugin PIP_PLUGIN_STATIC_SECTION_MERGE function
	//! on every common type, with "from" - application scope,
	//! "to" - plugin scope
	void mergeStatic();

	static PIStringList pluginsDirectories(const PIString & name);

private:
	NO_COPY_CLASS(PIPluginLoader)

	PIString findLibrary(const PIString & path);
	static PIString libExtension();

	PILibrary lib;
	FunctionLoaderVersion func_loader_version;
	FunctionStaticMerge func_static_merge;
	PIString error_str;
	Error error;
	bool loaded, messages;

};


#endif // PIP_FREERTOS
#endif // PIPLUGIN_H
