//! \~\ingroup System
//! \~\file piplugin.h
//! \~\brief
//! \~english Plugin control
//! \~russian Управление плагинами
//! \~\details
//! \~english Plugin helpers for dynamic library loading and plugin management
//! \~russian Вспомогательные средства для загрузки динамических библиотек и управления плагинами
/*
    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 MICRO_PIP

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

#  ifdef DOXYGEN


//! \relatesalso PIPluginLoader
//! \~\brief
//! \~english Declares plugin entry points and should be used before other \c PIP_PLUGIN_* macros.
//! \~russian Объявляет точки входа плагина и должен использоваться перед другими макросами \c PIP_PLUGIN_*.
#	define PIP_PLUGIN

//! \relatesalso PIPluginLoader
//! \~\brief
//! \~english Sets user version checked during loading, "version" is quoted string.
//! \~russian Устанавливает строковую пользовательскую версию, проверяемую при загрузке, "version" - строка в кавычках.
#	define PIP_PLUGIN_SET_USER_VERSION(version)

//! \relatesalso PIPluginLoader
//! \~\brief
//! \~english Registers a static section pointer for future merge. \a type is an integer key.
//! \~russian Регистрирует указатель на статическую секцию для последующего слияния. \a type - целочисленный ключ.
#	define PIP_PLUGIN_ADD_STATIC_SECTION(type, ptr)

//! \relatesalso PIPluginLoader
//! \~\brief
//! \~english Declares a function that merges plugin and application static sections.
//! \~russian Объявляет функцию для слияния статических секций плагина и приложения.
//! \~\details
//! \~english
//! 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.
//! \~russian
//! Этот метод имеет 3 аргумента: (int type, void * from, void * to).
//! Он вызывается первый раз при загрузке плагина с "from" - областью плагина,
//! "to" - областью приложения, и второй раз (необязательно) при вызове
//! \a PIPluginLoader::mergeStatic() с "from" - областью приложения и
//! "to" - областью плагина. Таким образом, этот макрос позволяет провести
//! слияние статических данных приложения и плагина.
//! \~\relatesalso PIPluginLoader
#	define PIP_PLUGIN_STATIC_SECTION_MERGE

//! \relatesalso PIPluginLoader
//! \~\brief
//! \~english Marks a plugin function for export.
//! \~russian Помечает функцию плагина для экспорта.
#	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


//! \~\ingroup System
//! \~\brief
//! \~english Metadata collected for one plugin or for the host application.
//! \~russian Метаданные, собираемые для одного плагина или для основного приложения.
class PIP_EXPORT PIPluginInfo {
public:
	//! \~english Constructs empty plugin metadata.
	//! \~russian Создает пустые метаданные плагина.
	PIPluginInfo();

	//! \~english Sets user version checked by \a PIPluginLoader::load().
	//! \~russian Устанавливает пользовательскую версию, проверяемую в \a PIPluginLoader::load().
	void setUserVersion(const PIString & v);

	//! \~english Associates a static section pointer with integer \a type.
	//! \~russian Связывает указатель на статическую секцию с целочисленным типом \a type.
	void setStaticSection(int type, void * ptr);

	//! \~english Returns configured user version string.
	//! \~russian Возвращает установленную строку пользовательской версии.
	PIString userVersion() const;

	//! \~english Returns registered static section pointers.
	//! \~russian Возвращает зарегистрированные указатели статических секций.
	PIMap<int, void *> staticSections() const;

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


//! \~\ingroup System
//! \~\brief
//! \~english Storage of plugin metadata for the application and loaded plugins.
//! \~russian Хранилище метаданных плагинов для приложения и загруженных плагинов.
class PIP_EXPORT PIPluginInfoStorage {
public:
	//! \~english Creates storage and initializes application metadata entry.
	//! \~russian Создает хранилище и инициализирует запись метаданных приложения.
	PIPluginInfoStorage();

	//! \~english Returns metadata of the current initialization scope.
	//! \~russian Возвращает метаданные текущей области инициализации.
	PIPluginInfo * currentInfo();

	//! \~english Returns metadata associated with plugin pointer \a p.
	//! \~russian Возвращает метаданные, связанные с указателем плагина \a p.
	PIPluginInfo * pluginInfo(void * p);

	//! \~english Returns metadata entry of the host application.
	//! \~russian Возвращает запись метаданных основного приложения.
	PIPluginInfo * applicationInfo();

	//! \~english Enters plugin scope \a p and creates metadata entry if needed.
	//! \~russian Переходит в область плагина \a p и при необходимости создает запись метаданных.
	PIPluginInfo * enterPlugin(void * p);

	//! \~english Removes metadata associated with plugin pointer \a p.
	//! \~russian Удаляет метаданные, связанные с указателем плагина \a p.
	void unloadPlugin(void * p);

	//! \~english Returns process-wide singleton storage.
	//! \~russian Возвращает глобальный синглтон хранилища.
	static PIPluginInfoStorage * instance();

private:
	NO_COPY_CLASS(PIPluginInfoStorage);

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


//! \~\ingroup System
//! \~\brief
//! \~english Plugin loader.
//! \~russian Загрузчик плагина.
//! \details
//! \~english Handles dynamic library loading, symbol resolution, and plugin lifecycle management
//! \~russian Управляет загрузкой динамических библиотек, разрешением символов и жизненным циклом плагинов
class PIP_EXPORT PIPluginLoader {
public:
	//! \~english Signature of the exported loader version function.
	//! \~russian Сигнатура экспортируемой функции версии загрузчика.
	typedef int (*FunctionLoaderVersion)();

	//! \~english Signature of the exported static merge function.
	//! \~russian Сигнатура экспортируемой функции слияния статических секций.
	typedef void (*FunctionStaticMerge)(int, void *, void *);

	//! \~english Possible load plugin error
	//! \~russian Возможные ошибки загрузки плагина
	enum Error {
		Unknown /** \~english No \a load() call yet \~russian Не было вызова \a load() */,
		NoError /** \~english No error \~russian Нет ошибки */,
		NoSuchFile /** \~english Can`t find library file \~russian Не найден файл библиотеки */,
		LibraryLoadError /** \~english System can`t load library \~russian Система не смогла загрузить библиотеку */,
		MissingSymbols /** \~english Can`t find necessary symbols \~russian Нет необходимых методов */,
		InvalidLoaderVersion /** \~english Internal version mismatch \~russian Неверная внутренняя версия */,
		InvalidUserVersion /** \~english User version mismatch \~russian Неверная пользовательская версия */,
	};

	//! \~english Contruct loader with base filename "name" and call \a load() if "name" not empty
	//! \~russian Создает загрузчик с базовым именем файла "name" и вызывает \a load() если "name" не пустое
	PIPluginLoader(const PIString & name = PIString());

	//! \~english Destructor, unload library
	//! \~russian Деструктор, выгружает библиотеку
	~PIPluginLoader();


	//! \~english Loads plugin by base filename \a name and updates error state on failure.
	//! \~russian Загружает плагин по базовому имени файла \a name и обновляет состояние ошибки при неудаче.
	bool load(const PIString & name);

	//! \~english Unload plugin and free library
	//! \~russian Выгружает плагин и освобождает библиотеку
	void unload();

	//! \~english Returns if plugin is successfully loaded
	//! \~russian Возвращает успешно ли загружен плагин
	bool isLoaded() const;

	//! \~english Returns error of last \a load() call
	//! \~russian Возвращает ошибку последнего вызова \a load()
	Error lastError() const;

	//! \~english Returns error message of last \a load() call
	//! \~russian Возвращает сообщение об ошибке последнего вызова \a load()
	PIString lastErrorText() const;

	//! \~english Set if %PIPluginLoader should print load messages, \b true by default
	//! \~russian Устанавливает должен ли %PIPluginLoader выводить сообщения, \b true по умолчанию
	void setMessages(bool yes);

	//! \~english Returns if %PIPluginLoader should print load messages, \b true by default
	//! \~russian Возвращает должен ли %PIPluginLoader выводить сообщения, \b true по умолчанию
	bool isMessages() const;

	//! \~english Returns loaded plugin library path
	//! \~russian Возвращает путь к загруженной библиотеке
	PIString libPath();

	//! \~english Obtain exported library method with name "symbol"
	//! \~russian Получает экспортированный метод библиотеки с именем "symbol"
	//! \~\sa PILibrary::resolve()
	void * resolve(const char * name);

	//! \~english Invoke plugin \a PIP_PLUGIN_STATIC_SECTION_MERGE
	//! \~russian Вызывает у плагина \a PIP_PLUGIN_STATIC_SECTION_MERGE
	//! \details
	//! \~english Calls the plugin's static section merge function to synchronize application and plugin data
	//! \~russian Вызывает функцию слияния статических секций плагина для синхронизации данных приложения и плагина
	void mergeStatic();

	//! \~english Returns existing plugin directories for subdirectory \a name.
	//! \~russian Возвращает существующие каталоги плагинов для подкаталога \a name.
	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 // MICRO_PIP
#endif // PIPLUGIN_H
