/*****************************************************************************
 * $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$
 ****************************************************************************/

// -- Core stuff
#include "ExtensionManager.h"
#include "Core.h"
#include "Application.h"
#include "Action.h"
#include "Viewer.h"

// -- QT stuff
#include <QDir>
#include <QtGui>
#include <QSettings>
#include <QApplication>
#include <QRegularExpression>

// include Log.h last in order to solve MSVC wingdi.h macro redefinition of ERROR
#include "Log.h"

namespace camitk {

// -------------------- autoload --------------------
void ExtensionManager::autoload() {
    Application::showStatusBarMessage("Loading viewer extensions...");
    autoload(VIEWER);
    Application::setProgressBarValue(5);
    Application::showStatusBarMessage("Loading component extensions...");
    autoload(COMPONENT);
    Application::setProgressBarValue(10);
    Application::showStatusBarMessage("Loading standard action extensions...");
    autoload(ACTION);
    Application::setProgressBarValue(100);
    Application::showStatusBarMessage("All extensions loaded");
}

void ExtensionManager::autoload(ExtensionManager::ExtensionType type) {
    QStringList extensionDir;

    switch (type) {
        case ACTION:
            extensionDir = Core::getActionDirectories();
            break;

        case COMPONENT:
            extensionDir = Core::getComponentDirectories();
            break;

        case VIEWER:
            extensionDir = Core::getViewerDirectories();
            break;

        default:
            CAMITK_TRACE_ALT(tr("autoload of unknown type: type should be either ACTION or COMPONENT or VIEWER, other extensions are not implemented yet"))
            break;
    }

    //-- remove duplicates
    QStringList extensionFileNames;
    QStringList extensionAbsoluteFileNames;
    QMap<QString, QDir> extensionUniqueDir;

    for (QString dirName : extensionDir) {
        QDir dir(dirName);
        QStringList pluginFileNames = getPluginFileNames(dir);

        for (QString pluginFile : pluginFileNames) {
            QString pluginAbsoluteFileName = dir.absoluteFilePath(pluginFile);

            if (!extensionFileNames.contains(pluginFile)) {
                extensionFileNames.append(pluginFile);
                extensionUniqueDir.insert(pluginFile, dir);
                extensionAbsoluteFileNames.append(pluginAbsoluteFileName);
            }
            else {
                CAMITK_TRACE_ALT(tr("autoload warning: duplicate extension: %1 found in directory %2.\nUsing extension in %3 instead (higher priority)")
                                 .arg(pluginFile,
                                      dir.absolutePath(),
                                      extensionUniqueDir.value(pluginFile).absolutePath()))
            }
        }
    }

    //-- load all extensions, try five times to avoid error for internal dependencies
    // if there is more than 10 dependency levels, then you have two choices:
    // - consider simplifying your component
    // - increase maxNumberOfTries
    int maxNumberOfTries = 10;
    int tryNr = 0;

    do {
        QMutableListIterator<QString> it(extensionAbsoluteFileNames);

        while (it.hasNext()) {
            QString extFileName = it.next();

            if (loadExtension(type, extFileName)) {
                // this one is loaded, remove it from the list to load
                it.remove();
            }
        }

        tryNr++;
    }
    while (tryNr < maxNumberOfTries && extensionAbsoluteFileNames.size() > 0);

    //-- manage loading errors
    if (extensionAbsoluteFileNames.size() > 0) {
        // get the messages from Qt
        QStringList errorStrings;

        for (QString fileName : extensionAbsoluteFileNames) {
            QPluginLoader pluginLoader;
            // from Qt doc: "causes all symbols in a library to be resolved when it is loaded, not simply when resolve() is called."
            pluginLoader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
            pluginLoader.setFileName(fileName);

            QObject* plugin = pluginLoader.instance();

            if (!plugin) {
                errorStrings << QString("Plugin:\n\"" + pluginLoader.fileName() + "\"\n") << QString("Error:\n" + pluginLoader.errorString().remove(pluginLoader.fileName()).remove("(: ").remove(QRegularExpression("\\)$")) + "\n");
            }
            else {
                errorStrings << QString("Plugin:\n\"" + pluginLoader.fileName() + "\"\n") << QString("No error (you need to increase your number of dependency level, or contact the CamiTK SDK developer team)");
            }
        }

        QString osLibraryPath = "";
        QString osLibraryPathSeparator = "";
#ifdef WIN32
        osLibraryPath = "PATH";
        osLibraryPathSeparator = ";";
#else
        osLibraryPath = "LD_LIBRARY_PATH";
        osLibraryPathSeparator = ":";
#endif

        CAMITK_ERROR_ALT(tr("Extension manager error:\nLoading %1 extension failed after %2 tries.\n\n%3\nList of library paths:\n - %4\n\nList of path:\n - %5")
                         .arg((type == ACTION) ? "action" : ((type == COMPONENT) ? "component" : "viewer"),
                              QString::number(tryNr),
                              errorStrings.join("\n"),
                              Application::instance()->libraryPaths().join("\n - "),
                              QString(qgetenv(osLibraryPath.toStdString().c_str())).split(osLibraryPathSeparator.toStdString().c_str()).join("\n - "))
                        )

    }

    //-- load user extensions
    QSettings& settings = Application::getSettings();
    settings.beginGroup("UserExtensions");
    QStringList userRegisteredExtensions;

    switch (type) {
        case ACTION:
            userRegisteredExtensions = settings.value("actions", QVariant(QStringList())).toStringList();
            break;

        case COMPONENT:
            userRegisteredExtensions = settings.value("components", QVariant(QStringList())).toStringList();
            break;

        case VIEWER:
            userRegisteredExtensions = settings.value("viewers", QVariant(QStringList())).toStringList();
            break;

        default:
            break;
    }

    settings.endGroup();

    for (QString userRegisteredExtensionFile : userRegisteredExtensions) {
        loadExtension(type, userRegisteredExtensionFile);
    }

}

// -------------------- loadExtension --------------------
bool ExtensionManager::loadExtension(ExtensionManager::ExtensionType type, QString fileName) {
    // First of all, add the private library directories in this session PATH
    initPrivateLibDirs();

    bool returnValue = false;
    QPluginLoader pluginLoader;
    // from Qt doc: "causes all symbols in a library to be resolved when it is loaded, not simply when resolve() is called."
    pluginLoader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
    pluginLoader.setFileName(fileName);
    // load the plugin and get the instance
    QObject* extension = pluginLoader.instance();

    if (extension) {
        switch (type) {
            case ACTION: {
                ActionExtension* ext = qobject_cast<ActionExtension*> (extension);

                if (ext) {
                    ext->setLocation(fileName);
                    ext->initResources();

                    //-- register the filename
                    getActionExtensionMap().insert(fileName, ext);
                    // initialize all actions
                    ext->init();
                    //-- register all actions
                    Application::registerAllActions(ext);
                    returnValue = true;
                }
            }
            break;

            case VIEWER: {
                ViewerExtension* ext = qobject_cast<ViewerExtension*> (extension);

                if (ext) {
                    ext->setLocation(fileName);
                    ext->initResources();
                    // initialize all viewers
                    ext->init();

                    // check that ext is managing a new class
                    int i = 0;

                    while (i < getViewerExtensionMap().size() && getViewerExtensionsList().at(i)->getViewerClassName() != ext->getViewerClassName()) {
                        i++;
                    }

                    returnValue = (i >= getViewerExtensionMap().size());

                    if (returnValue) {
                        //-- register the filename
                        getViewerExtensionMap().insert(fileName, ext);
                        //-- register all viewers
                        Application::registerAllViewers(ext);
                    }
                    else {
                        CAMITK_INFO_ALT(tr("Extension Manager: duplicate extension management: viewer extension \"%1\" (in file \"%2\") declares management of \"%3\" while \"%4\" extension (loaded from file \"%5\") is already managing \"%6\".\nUsing extension in \"%7\" instead (higher priority) as only one viewer extension can manage an instance of viewer.")
                                        .arg(ext->getName(),
                                             fileName,
                                             ext->getViewerClassName(),
                                             getViewerExtensionsList().at(i)->getName(),
                                             getViewerExtensionsList().at(i)->getLocation(),
                                             ext->getViewerClassName(),
                                             getViewerExtensionsList().at(i)->getLocation()))
                    }
                }
            }
            break;

            case COMPONENT: {
                ComponentExtension* cp = qobject_cast<ComponentExtension*> (extension);
                returnValue = registerNewComponentExtension(cp, fileName);
            }
            break;

            default:
                CAMITK_TRACE_ALT(tr("ExtensionManager: loadExtension of unknown type: type should be either ACTION or COMPONENT, other extensions are not implemented yet"))
                break;
        }
    }
    else {
        pluginLoader.unload();  // to make sure we could try again later

        // try to load the missing shared object directly from private dir (give absolute path)
        QRegularExpression libname("\\((lib.*): cannot open shared object file");
        QRegularExpressionMatch match = libname.match(pluginLoader.errorString());
        if (match.hasMatch()) {
            QString privateLibToLoad = Core::getGlobalInstallDir() + "/" + QString(Core::libDir()) + "/" + QString(Core::shortVersion()) + "/" + match.captured(1);
            QLibrary privateLib(privateLibToLoad);
            bool foundInLibDir = privateLib.load();

            if (!foundInLibDir && QString(Core::libDir()) != "lib") {
                // now try "lib/" directly
                privateLibToLoad = Core::getGlobalInstallDir() + "/" + QString(Core::libDir()) + "/" + QString(Core::shortVersion()) + "/" + match.captured(1);
                privateLib.setFileName(privateLibToLoad);
                privateLib.load();
            }
        }
    }

    return returnValue;

}

// -------------------- registerNewComponentExtension --------------------
bool ExtensionManager::registerNewComponentExtension(ComponentExtension* ce, QString filename) {
    if (ce) {

        ce->setLocation(filename);
        ce->initResources();

        // count how many specific extension were inserted
        int extCount = 0;

        //-- insert the ComponentExtension plugin in the application wide list
        if (ce->hasDataDirectory()) {
            getDataDirectoryComponentExtensionMap().insert(ce->getName(), ce);
            extCount++;
        }
        else {
            // (cannot do that in the constructor because the virtual symbol table seems to be confused!)
            for (QString ext : ce->getFileExtensions()) {
                ComponentExtension* existingComponentExtension = getComponentExtensionMap().value(ext);

                if (existingComponentExtension != nullptr) {
                    CAMITK_INFO_ALT(tr("Extension Manager: duplicate extension management: component extension \"%1\" (in file \"%2\") declares management of \"%3\" file extension while \"%4\" extension (loaded from file \"%5\") is already managing \"%6\".\nUsing extension in \"%7\" instead (higher priority) as only one component extension can manage a give file extension.")
                                    .arg(ce->getName(),
                                         filename,
                                         ext,
                                         existingComponentExtension->getName(),
                                         existingComponentExtension->getLocation(),
                                         ext,
                                         existingComponentExtension->getLocation()))
                }
                else {
                    getComponentExtensionMap().insert(ext, ce);
                }

                // Even if the extension was not registered, a info message is displayed -> consider that the processing is done
                extCount++;
            }
        }

        // if the component only declared an extension that was already managed
        // then the component was loaded but is not valid
        // (very) special case: for component extension that do not manage any extension (component have to be created directly by actions). Then it is ok not to have registered any extension
        return (extCount > 0 || ce->getFileExtensions().empty());
    }
    else {
        return false;
    }
}

// -------------------- getInstallationString --------------------
QString ExtensionManager::getInstallationString(QString file, const QString& globalInstallDir, const QString& userInstallDir, const QString& currentWorkingDir) {
    QString whichInstallDir;
    QDir dir(QFileInfo(file).absolutePath());

    // go up three levels
    // - go up one level for the extension name (e.g. "actions")
    dir.cdUp();
    // - go up one level for the camitk short version name ("camitk-x.x")
    dir.cdUp();
    // - go up one level for the lib dirname ("lib")
    dir.cdUp();

    QString absolutePath = dir.absolutePath();

    if (absolutePath == currentWorkingDir) {
        whichInstallDir = "[W]";
    }
    else {
        if (absolutePath == userInstallDir) {
            whichInstallDir = "[L]";
        }
        else {
            if (absolutePath == globalInstallDir) {
                whichInstallDir = "[G]";
            }
            else {
                // Take special case of existence of a subdirectory in Core::libDir()
                // add one cdUp() for each "/" inside Core::libDir() (to take into account any multiarch configuration that
                //   landed in CMAKE_INSTALL_LIBDIR, eg. "lib/x86_64-linux-gnu")
                for (int i = 0; i < QString(Core::libDir()).count(QLatin1Char('/')); i++) {
                    dir.cdUp();
                }
                absolutePath = dir.absolutePath();

                if (absolutePath == currentWorkingDir) {
                    whichInstallDir = "[W]";
                }
                else {
                    if (absolutePath == userInstallDir) {
                        whichInstallDir = "[L]";
                    }
                    else {
                        if (absolutePath == globalInstallDir) {
                            whichInstallDir = "[G]";
                        }
                        else {
                            whichInstallDir = "[U]";
                        }
                    }
                }
            }
        }
    }

    return whichInstallDir;
}

// -------------------- getInstallationRepository --------------------
ExtensionManager::InstallationRepository ExtensionManager::getInstallationRepository(const QString& extensionName) {
    InstallationRepository installationRepository = NOT_REGISTERED; // default
    auto& aeList = ExtensionManager::getActionExtensionsList();
    auto it = std::find_if(aeList.begin(), aeList.end(), [extensionName](ActionExtension * ae) {
        return ae->getName() == extensionName;
    });
    if (it != aeList.end()) {
        // get the global, user and current working directories
        QString globalDir = Core::getGlobalInstallDir();
        QString userDir = Core::getUserInstallDir();
        QString currentWorkingDir = Core::getCurrentWorkingDir();
        // get the corresponding string
        QString installationString = ExtensionManager::getInstallationString((*it)->getLocation(), globalDir, userDir, currentWorkingDir);
        if (installationString == "[G]") {
            installationRepository = GLOBAL;
        }
        else if (installationString == "[L]") {
            installationRepository = LOCAL;
        }
        else if (installationString == "[W]") {
            installationRepository = WORKING_DIRECTORY;
        }
        else if (installationString == "[U]") {
            installationRepository = USER;
        }
    }
    return installationRepository;
}

// -------------------- getComponentExtensionMap --------------------
QMap< QString, ComponentExtension* >&  ExtensionManager::getComponentExtensionMap() {
    static QMap<QString, ComponentExtension*> componentExtensionMap;

    return componentExtensionMap;
}

// -------------------- getDataDirectoryComponentExtension --------------------
ComponentExtension* ExtensionManager::getDataDirectoryComponentExtension(QString pluginName) {
    return getDataDirectoryComponentExtensionMap().value(pluginName);
}

// -------------------- getDataDirectoryComponentExtensionMap --------------------
QMap< QString, ComponentExtension* >& ExtensionManager::getDataDirectoryComponentExtensionMap() {
    static QMap<QString, ComponentExtension*> dataDirectoryComponentExtensionMap;

    return dataDirectoryComponentExtensionMap;
}

// -------------------- getComponentExtension --------------------
ComponentExtension* ExtensionManager::getComponentExtension(QString extOrName) {
    ComponentExtension* cp = getComponentExtensionMap().value(extOrName);

    if (!cp) {
        cp = getDataDirectoryComponentExtensionMap().value(extOrName);

        if (!cp) {
            // look for the name in getComponentExtensionMap()
            QMapIterator<QString, ComponentExtension*> it(getComponentExtensionMap());

            while (it.hasNext() && !cp) {
                it.next();

                if (it.value()->getName() == extOrName) {
                    cp = it.value();
                }
            }
        }
    }

    return cp;
}

// -------------------- getComponentExtensionsList --------------------
const QList<ComponentExtension* > ExtensionManager::getComponentExtensionsList() {
    QList<ComponentExtension* > rawList = getComponentExtensionMap().values();

    // remove duplicates
    QSet<ComponentExtension* > rawSet(rawList.begin(), rawList.end());
    QList<ComponentExtension* > sortedList = QList<ComponentExtension* >(rawSet.begin(), rawSet.end());

    // sort
    std::sort(sortedList.begin(), sortedList.end(), ExtensionManager::componentExtensionLessThan);

    return sortedList;
}

// -------------------- getDataDirectoryComponentsList --------------------
const QList<ComponentExtension* > ExtensionManager::getDataDirectoryComponentsList() {
    QList<ComponentExtension* > rawList = getDataDirectoryComponentExtensionMap().values();

    // remove duplicates
    QSet<ComponentExtension* > rawSet(rawList.begin(), rawList.end());
    QList<ComponentExtension* > sortedList = QList<ComponentExtension* >(rawSet.begin(), rawSet.end());

    // sort
    std::sort(sortedList.begin(), sortedList.end(), ExtensionManager::componentExtensionLessThan);

    return sortedList;
}

// -------------------- getFileExtensions --------------------
QStringList ExtensionManager::getFileExtensions() {
    return ExtensionManager::getComponentExtensionMap().keys();
}

// -------------------- getDataDirectoryExtNames --------------------
QStringList ExtensionManager::getDataDirectoryExtNames() {
    return ExtensionManager::getDataDirectoryComponentExtensionMap().keys();
}


// -------------------- getActionExtensionMap --------------------
QMap<QString, ActionExtension*>& ExtensionManager::getActionExtensionMap() {
    static QMap<QString, ActionExtension*> actionExtensionMap;

    return actionExtensionMap;
}

// -------------------- getActionExtensionsList --------------------
const QList< ActionExtension* > ExtensionManager::getActionExtensionsList() {
    QList<ActionExtension* > rawList = getActionExtensionMap().values();

    // remove duplicates
    QSet<ActionExtension* > rawSet(rawList.begin(), rawList.end());
    QList<ActionExtension* > sortedList = QList<ActionExtension* >(rawSet.begin(), rawSet.end());

    // sort
    std::sort(sortedList.begin(), sortedList.end(), ExtensionManager::actionExtensionLessThan);

    return sortedList;
}

// -------------------- getActionExtension --------------------
ActionExtension* ExtensionManager::getActionExtension(QString file) {
    return getActionExtensionMap().value(file);
}

// -------------------- unloadAllActionExtensions --------------------
void ExtensionManager::unloadAllActionExtensions() {
    QList<QString> allExtensions = getActionExtensionMap().keys();

    while (!allExtensions.isEmpty()) {
        unloadActionExtension(allExtensions.takeFirst());
    }
}

// -------------------- getViewerExtensionMap --------------------
QMap<QString, ViewerExtension*>& camitk::ExtensionManager::getViewerExtensionMap() {
    static QMap<QString, ViewerExtension*> viewerExtensionMap;

    return viewerExtensionMap;
}

// -------------------- unloadAllViewerExtensions --------------------
void camitk::ExtensionManager::unloadAllViewerExtensions() {
    QList<QString> allExtensions = getViewerExtensionMap().keys();

    while (!allExtensions.isEmpty()) {
        unloadViewerExtension(allExtensions.takeFirst());
    }
}

// -------------------- getViewerExtensionsList --------------------
const QList<ViewerExtension*> camitk::ExtensionManager::getViewerExtensionsList() {
    QList<ViewerExtension* > rawList = getViewerExtensionMap().values();

    // remove duplicates
    QSet<ViewerExtension* > rawSet(rawList.begin(), rawList.end());
    QList<ViewerExtension* > sortedList = QList<ViewerExtension* >(rawSet.begin(), rawSet.end());

    // sort
    std::sort(sortedList.begin(), sortedList.end(), ExtensionManager::viewerExtensionLessThan);

    return sortedList;
}

// -------------------- getViewerExtension --------------------
ViewerExtension* camitk::ExtensionManager::getViewerExtension(QString file) {
    return getViewerExtensionMap().value(file);
}

// -------------------- unloadViewerExtension --------------------
bool ExtensionManager::unloadViewerExtension(QString fileName) {
    if (getViewerExtensionMap().contains(fileName)) {
        ViewerExtension* ext = getViewerExtensionMap().value(fileName);
        // unregister extension
        getViewerExtensionMap().remove(fileName);
        // unregister actions from application
        Application::unregisterAllViewers(ext);
        // delete extensions (and all its actions)
        delete ext;

        return true;
    }
    else {
        return false;
    }
}

// -------------------- registerFileExtension --------------------
void ExtensionManager::registerFileExtension(QString fileExtension) {

#ifdef WIN32
    // we get the application name and its binary path for the association
    QString appName = QApplication::applicationName(); // used for windows registry
    QString appFilePath = QApplication::applicationFilePath(); // needed for associating file for opening command

    // WINDOWS ONLY
    appFilePath.replace("/", "\\");
    // Associate the file extension with the current application for opening
    // Store that information in the windows registery
    QSettings* registry = new QSettings("HKEY_CURRENT_USER\\Software\\Classes\\camitk-" + appName + "." + fileExtension, QSettings::NativeFormat);
    registry->setValue("Default", "CamiTK Image file");

    registry = new QSettings("HKEY_CURRENT_USER\\Software\\Classes\\camitk-" + appName + "." + fileExtension + "\\DefaultIcon", QSettings::NativeFormat);
    registry->setValue("Default", "\"" + appFilePath + "\", 0");

    registry = new QSettings("HKEY_CURRENT_USER\\Software\\Classes\\camitk-" + appName + "." + fileExtension + "\\shell\\Open\\command", QSettings::NativeFormat);
    registry->setValue("Default", "\"" + appFilePath + "\" %1");

    registry = new QSettings("HKEY_CURRENT_USER\\Software\\Classes\\." + fileExtension + "\\ShellNew", QSettings::NativeFormat);
    registry->setValue("Default", "");

    registry = new QSettings("HKEY_CURRENT_USER\\Software\\Classes\\." + fileExtension, QSettings::NativeFormat);
    registry->setValue("Default", "camitk-" + appName + "." + fileExtension);
#endif

    // TODO : associate file opening on Linux & MacOS
}

// -------------------- getPluginFileNames --------------------
QStringList ExtensionManager::getPluginFileNames(QDir extensionsDir) {
    // loop to load component plugin, taking into account internal dependencies (i.e. dependency between
    // one component and another one.
    QStringList pluginFileNames = extensionsDir.entryList(Core::getExtensionFilter(), QDir::Files, QDir::Name);

#ifdef WIN32
    // Get the MSVC debug dlls
    QStringList pluginFileNamesDebugMSVC = pluginFileNames.filter(QRegularExpression(".*" + QString(Core::debugPostfix()) + ".dll"));

    if (Core::isDebugBuild()) {
        return pluginFileNamesDebugMSVC;
    }
    else {
        // remove debug dll one by one
        for (QString debugDLL : pluginFileNamesDebugMSVC) {
            pluginFileNames.removeAll(debugDLL);
        }

        return pluginFileNames;
    }

#endif

    return pluginFileNames;

}

// -------------------- unloadActionExtension --------------------
bool ExtensionManager::unloadActionExtension(QString fileName) {
    if (getActionExtensionMap().contains(fileName)) {
        ActionExtension* ext = getActionExtensionMap().value(fileName);

        //-- unregister extension
        getActionExtensionMap().remove(fileName);
        // -- unregister actions from application
        Application::unregisterAllActions(ext);
        //-- delete extensions (and all its actions)
        delete ext;
        return true;
    }
    else {
        return false;
    }
}

// -------------------- unloadComponentExtension --------------------
bool ExtensionManager::unloadComponentExtension(QString extOrName) {
    bool unloaded = false;
    // remove from the application wide list
    ComponentExtension* cp = getComponentExtensionMap().take(extOrName);

    if (cp != nullptr) {
        // remove all the other extensions
        QMutableMapIterator<QString, ComponentExtension*> it(getComponentExtensionMap());

        while (it.hasNext()) {
            it.next();

            if (it.value() == cp) {
                getComponentExtensionMap().take(it.key());
            }
        }

        // open the plugin
        QPluginLoader pluginLoader(cp->getLocation());
        // delete the pointer
        delete cp;
        // try to unload
        unloaded = pluginLoader.unload();
    }
    else {
        cp = getDataDirectoryComponentExtensionMap().take(extOrName);

        if (cp != nullptr) {
            // open the plugin
            QPluginLoader pluginLoader(cp->getLocation());
            // delete the pointer
            delete cp;
            // try to unload
            unloaded = pluginLoader.unload();
        }
        else {
            // look for the name in getComponentExtensionMap()
            QMutableMapIterator<QString, ComponentExtension*> it(getComponentExtensionMap());

            while (it.hasNext()) {
                it.next();

                if (it.value()->getName() == extOrName) {
                    return unloadComponentExtension(it.value()->getName());
                }
            }
        }
    }

    return unloaded;
}

// -------------------- addLibraryPath --------------------
void ExtensionManager::addLibraryPath(QDir potentialPath, QString potentialLibDir) {

    if (potentialPath.cd(potentialLibDir + "/" + QString(Core::shortVersion()))) {
        QByteArray confirmedLibDirPath = potentialPath.canonicalPath().toUtf8();

#ifdef WIN32 // for Windows
        QByteArray path = qgetenv("PATH");
        path.append(";");
        path.append(confirmedLibDirPath);
        path.append(";");
        path.append(confirmedLibDirPath + "/actions");
        path.append(";");
        path.append(confirmedLibDirPath + "/components");
        path.append(";");
        path.append(confirmedLibDirPath + "/viewers");
        qputenv("PATH", path);
#endif
        // update the application / qt plugins library path
        Application::instance()->addLibraryPath(confirmedLibDirPath);
        Application::instance()->addLibraryPath(confirmedLibDirPath + "/actions");
        Application::instance()->addLibraryPath(confirmedLibDirPath + "/components");
        Application::instance()->addLibraryPath(confirmedLibDirPath + "/viewers");
    }
}


// -------------------- initPrivateLibDirs --------------------
void ExtensionManager::initPrivateLibDirs() {
    // Add the private lib dirs to the current application environment's PATH variable
    // This modifies the current PATH variable in order for the OS to find the private
    // libraries compiled as shared objects/dll/dylib needed by some extensions
    static bool alreadyInitialized = false;

    if (!alreadyInitialized) {
        // Build directory / current working directory
        addLibraryPath(Core::getCurrentWorkingDir(), QString(Core::libDir()));

        // Local install directory
        addLibraryPath(Core::getUserInstallDir(), QString(Core::libDir()));

        // Global install directory
        addLibraryPath(Core::getGlobalInstallDir(), QString(Core::libDir()));

        // take multiarch into account
        if (QString(Core::libDir()) != "lib") {
            addLibraryPath(Core::getCurrentWorkingDir(), "lib");
            addLibraryPath(Core::getUserInstallDir(), "lib");
            addLibraryPath(Core::getGlobalInstallDir(), "lib");
        }

        alreadyInitialized = true;
    }
}

// -------------------- componentExtensionLessThan --------------------
bool ExtensionManager::componentExtensionLessThan(const ComponentExtension* left, const ComponentExtension* right) {
    return left->getName() < right->getName();
}

// -------------------- actionExtensionLessThan --------------------
bool ExtensionManager::actionExtensionLessThan(ActionExtension* left, ActionExtension* right) {
    return left->getName() < right->getName();
}

// -------------------- viewerExtensionLessThan --------------------
bool ExtensionManager::viewerExtensionLessThan(ViewerExtension* left, ViewerExtension* right) {
    return left->getName() < right->getName();
}

}


