/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK 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 version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/


#ifndef EXTENSIONS_MANAGER_H
#define EXTENSIONS_MANAGER_H

// -- Core stuff
#include "ComponentExtension.h"
#include "ActionExtension.h"
#include "ViewerExtension.h"
#include "CamiTKAPI.h"

// -- QT stuff
#include <QtPlugin>
#include <QPluginLoader>
#include <QStringList>
#include <QMap>
#include <QDir>

namespace camitk {
/**
 * @ingroup group_sdk_libraries_core
 *
 * @brief
 * This class is used to manage all plugins loaded by the application.
 *
 *  The current version is able to load dynamic library for
 *  - ComponentExtension
 *  - ActionExtension
 *
 *  This class is a contained for all the loaded extension. It contains only
 *  static members.
 */
class CAMITK_API ExtensionManager : public QObject {

public:
    /** \enum ExtensionType describes the CamiTK Extension Type (Action, Component, Application...).
     * CamiTK follows Component-Based programming architecture. The Service Layer defines four types
     * of extensions.
     */
    enum ExtensionType {
        ACTION,         ///< Action extensions: manages the processing logic
        COMPONENT,      ///< Component extensions: manages the data logic
        APPLICATION,    ///< MainWindow extensions: manages the application logic
        VIEWER          ///< Viewer extensions: manages the presentation and user interaction logic.
    };

    /// \enum InstallationRepository corresponds to where the extension is installed
    enum InstallationRepository {
        GLOBAL,            ///< the extension is installed on the system (global install)
        LOCAL,             ///< the extension is installed in the user defined installation directory (local install)
        WORKING_DIRECTORY, ///< the extension is installed in the current working directory (development stage)
        USER,              ///< the extension is registered in the user configuration file (could be anywhere)
        NOT_REGISTERED     ///< the extension is not registered in any of the recognized installation repository
    };

    /// Autoload component, action and viewer extensions (dlls) as well as registered CamiTK extension file
    static void autoload();

    /** Autoload all the extension of a given type.
     *  This method try to load all the extensions of the given type that are found (in this specific order) :
     *  - in the current working directory (considered as an install directory), this should be the build directory (load extensions that are just being compiled, before their installation
     *  - in the user config directory (CamiTK user install)
     *  - in the system-wide install (CamiTK global install)
     *  - and in the list of user-registered extensions (stored in the configuration directory)
     *
     * Duplicates found within the installation directory are removed (e.g. if you have the extension x.so in the build directory as
     * well as the user directory, only the extension in the build directory will be loaded (it takes precedence).
     *
     * @param type the extension type
     */
    static void autoload(ExtensionType type);

    /** Load a specific extension from a file.
     *  This methods loads a ActionExtension, ComponentExtension,... plugin from a .dll/.so/.dylib filename
     *
     * @param type the extension type
     * @param file the plugin filename (.dll/.so/.dylib)
     * @return false if it cannot be loaded
     */
    static bool loadExtension(ExtensionType type, QString file);

    /** Register the given ComponentExtension.
     *  If the given ComponentExtension is managing file extensions that are already registered, it
     *  will not be registered, and this method will return false and a info message will be printed
     *  with more information.
     *
     *  @param ce valid ComponentExtension pointer (loaded from a shared library plugin (.dll/.so/.dylib) or instantiated programmatically)
     *  @param filename (optional) if the extension was loaded from a shared library (.dll/.so/.dylib), path to this file
     * @return true if the extension was registered without error
     */
    static bool registerNewComponentExtension(ComponentExtension* ce, QString filename = QString());

    /** Returns a three char string that corresponds to the installation directory of a given shared library.
     *
     *  The shared library should be the complete/fullpath filename corresponding to a component, action or viewer extension.
     *
     *  The following enum value is returned:
     *  - "[G]" for an extension installed in the global installation directory (where CamiTK SDK is installed)
     *  - "[L]" for an extension installed in the user configuration directory (local install)
     *  - "[W]" for an extension loaded directly from the current working directory (build installation tree)
     *  - "[U]" for an extension loaded manually by the user
     *
     *  @return the string coding for the installation
     *  @param file the extension plugin file
     *  @param globalInstallDir the global installation directory
     *  @param userInstallDir the user installation directory
     *  @param currentWorkingDir the current working directory
     */
    static QString getInstallationString(QString file, const QString& globalInstallDir, const QString& userInstallDir, const QString& currentWorkingDir);

    /**
     * @brief Returns the enum corresponding to the installation repository (directory) of a given shared library.
     *
     *  The shared library should be the name corresponding to the value of getName() of an
     *  existing ComponentExtension, ActionExtension or ViewerExtension.
     *
     *  The following string code are returned:
     *  - GLOBAL for an extension installed in the global installation directory (where CamiTK SDK is installed)
     *  - LOCAL for an extension installed in the user configuration directory (local install)
     *  - WORKING for an extension loaded directly from the current working directory (build installation tree)
     *  - USER for an extension loaded manually by the user
     *  - NOT_REGISTERED the extension name was not found in the list of loaded extensions
     *
     *  \note calls getInstallationString and Core methods.
     *
     *  @return the enum value corresponding to the type of installation repository/directory
     *  @param extensionName the extension plugin name (i.e. the extension getName() value)
     */
    static InstallationRepository getInstallationRepository(const QString& extensionName);

    /** @name Component extension list management
       */
    ///@{
    /** @brief get the plugin corresponding to a given extension or name.
     *
     */
    static ComponentExtension* getComponentExtension(QString);

    /**
     * @brief Get the list of registered ComponentExtension.
     * The list contains unique elements (no duplicates possible) and is alphabetically sorted.
     *
     * @note
     * The list is read-only and cannot be modified.
     * This list is useful to loop over the loaded ComponentExtension, especially when order counts.
     *
     * @return list of unique ComponentExtension loaded in the Core.
     */
    static const QList<ComponentExtension*> getComponentExtensionsList();

    /** @brief get the plugin corresponding to a given data directory component extension's name.
     *
     */
    static ComponentExtension* getDataDirectoryComponentExtension(QString);

    /**
     * @brief Get the list of registered ComponentExtension working on directory.
     * The list contains unique elements (no duplicates possible) and is alphabetically sorted.
     *
     * @note
     * The list is read-only and cannot be modified.
     * This list is useful to loop over the loaded ComponentExtension, especially when order counts.
     *
     * @return list of unique ComponentExtension working on data directory loaded in the Core.
     */
    static const QList<ComponentExtension*> getDataDirectoryComponentsList();

    /// get the list of all the suffixes managed by registered component extensions (all possible file suffix)
    static QStringList getFileExtensions();

    /// get the list of all the name of the registered Component data directory
    static QStringList getDataDirectoryExtNames();

    /** Register the file extension with the current application for opening
     *  This function is called by registerFileExtensions
     *  @param fileExtension the file extension to associate with the current application for opening
     **/
    static void registerFileExtension(QString fileExtension);

    /** Unload a plugin corresponding to a given extension or component name.
     * @return false if the plugin could not be unloaded - still in use somewhere
     */
    static bool unloadComponentExtension(QString);
    ///@}


    /// @name Action extension list management
    ///@{

    /// unload all action extensions and delete instantiated actions
    static void unloadAllActionExtensions();

    /**
     * @brief Get the list of registered ActionExtension.
     * The list contains unique elements (no duplicates possible) and is alphabetically sorted.
     *
     * @note
     * The list is read-only and cannot be modified.
     * This list is useful to loop over the loaded ActionExtension, especially when order counts.
     *
     * @return list of unique ActionExtension loaded in the Core.
     */
    static const QList<ActionExtension*> getActionExtensionsList();

    /**
     * @brief Get the ActionExtension corresponding to the given shared library file.
     *
     * If the given shared library is not loaded return nullptr. The shared library file should
     * be given as a complete filepath in the QString.
     *
     * @return nullptr if the given shared library complete path is not loaded.
     */
    static ActionExtension* getActionExtension(QString);

    /** Unload an action extension using its .dll/.so/.dylib filename
     * @return false if the plugin could not be unloaded - still in use somewhere
     */
    static bool unloadActionExtension(QString);
    ///@}

    /// @name Viewer extension list management
    ///@{

    /// unload all viewer extensions and delete instantiated actions
    static void unloadAllViewerExtensions();

    /**
    * @brief Get the list of registered ViewerExtension.
    * The list contains unique elements (no duplicates possible) and is alphabetically sorted.
    *
    * @note
    * The list is read-only and cannot be modified.
    * This list is useful to loop over the loaded ViewerExtension, especially when order counts.
    *
    * @return list of unique ViewerExtension loaded in the Core.
    */
    static const QList<ViewerExtension*> getViewerExtensionsList();

    /**
    * @brief Get the Viewer Extension corresponding to the given shared library file.
    *
    * If the given shared library is not loaded return nullptr. The shared library file should
    * be given as a complete filepath in the QString.
    *
    * @return nullptr if the given shared library complete path is not loaded.
    */
    static ViewerExtension* getViewerExtension(QString);

    /** Unload an viewer extension using its .dll/.so/.dylib filename
    * @return false if the plugin could not be unloaded - still in use somewhere
    */
    static bool unloadViewerExtension(QString);
    ///@}

private:
    /** get the singleton map of loaded component plugins for files (the key is the file extension)
    * This is the private (intern) method.
    * The component extension map is updated by loadComponentExtension, unloadComponentExtension and autoloadComponentExtensions.
    * This method follows the "construct on first use" idiom/design-pattern.
    * It therefore avoids the infamous "static initialization order fiasco",
    * see http://www.parashift.com/c++-faq/ctors.html
    */
    static QMap<QString, ComponentExtension*>& getComponentExtensionMap();

    /** get the singleton map of loaded component plugins for data directory (the key is the name)
    * This is the private (intern) method.
    * The data directory component extension map is updated by loadExtension, unloadExtension and autoloadExtensions.
    * This method follows the "construct on first use" idiom/design-pattern.
    * It therefore avoids the infamous "static initialization order fiasco",
    * see http://www.parashift.com/c++-faq/ctors.html
    */
    static QMap<QString, ComponentExtension*>& getDataDirectoryComponentExtensionMap();

    /** get the singleton map of loaded action plugins (the key is the shared object/dll/dylib filename)
     * This is the private (intern) method.
     * The action extension map is updated by loadActionExtension, unloadActionExtension and autoloadActionExtensions.
     * This method follows the "construct on first use" idiom/design-pattern.
     * It therefore avoids the infamous "static initialization order fiasco",
     * see http://www.parashift.com/c++-faq/ctors.html
     */
    static QMap<QString, ActionExtension*>& getActionExtensionMap();

    /** get the singleton map of loaded viewer plugins (the key is the shared object/dll/dylib filename)
    * This is the private (intern) method.
    * The viewer extension map is updated by loadViewerExtension, unloadViewerExtension and autoloadViewerExtensions.
    * This method follows the "construct on first use" idiom/design-pattern.
    * It therefore avoids the infamous "static initialization order fiasco",
    * see http://www.parashift.com/c++-faq/ctors.html
    */
    static QMap<QString, ViewerExtension*>& getViewerExtensionMap();

    /// return the list of shared objects in a directory considering debug/release on MSVC
    static QStringList getPluginFileNames(QDir);

    /** Add the private library directories (lib/camitk-version) in the current session PATH:
     *  - the current build library directory
     *  - the local installation library directory
     *  - the global installation library directory
     *  Note that we also add the actions and component private library subdirectories.
     *  Uses addLibraryPath().
     *  Takes multiarch into account.
     */
    static void initPrivateLibDirs();

    /// check if the given directory contains an extension repository and if it does
    /// update PATH (on windows) and call QCoreApplication::addLibraryPath for
    /// the given directory and all derived possible extension
    /// directories (actions, components and viewers)
    /// To check if it contains an extension repository, the given QString is used
    static void addLibraryPath(QDir potentialPath, QString potentialLibDir);

    /// @name Custom comparaison methods
    /// @{
    /**
     * @brief Custom comparaison method between two ComponentExtension*
     * This method uses the QString::<() (less than operator) to perform the comparaison
     * of two ComponentExtension* based on their name.
     * This method is used for sorting alphabetically a list of ComponentExtension*.
     *
     * @param left left ComponentExtension* to compare
     * @param right right ComponentExtension* to compare
     * @return the comparaison result, based on their name (alphabetically order)
     */
    static bool componentExtensionLessThan(const ComponentExtension* left, const ComponentExtension* right);

    /**
     * @brief Custom comparaison method between two ActionExtension*
     * This method uses the QString::<() (less than operator) to perform the comparaison
     * of two ActionExtension* based on their name.
     * This method is used for sorting alphabetically a list of ActionExtension*.
     *
     * @param left left ActionExtension* to compare
     * @param right right ActionExtension* to compare
     * @return the comparaison result, based on their name (alphabetically order)
     *
     * @note This method does not take const input arguments as the ActionExtension::getName() accessor
     * is a mutable method (not const).
     * This should be updated, but would change the CamiTK API
     * check the Wiki for Code developer guidelines
     */
    static bool actionExtensionLessThan(ActionExtension* left, ActionExtension* right);

    /**
     * @brief Custom comparaison method between two ViewerExtension*
     * based on the same principle as actionExtensionLessThan(..)
     * @see actionExtensionLessThan(..) for more information
     */
    static bool viewerExtensionLessThan(ViewerExtension* left, ViewerExtension* right);
    ///@}
};

}


#endif //EXTENSIONS_MANAGER_H
