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

#include "HotPlugExtensionManager.h"

#include "Application.h"
#include "Log.h"

#include <CamiTKExtensionModel.h>

#include <QFileInfo>
#include <QSettings>
#include <QMessageBox>

namespace camitk {

// -------------------- getLoadedExtensionFiles --------------------
const QStringList& HotPlugExtensionManager::getLoadedExtensionFiles() {
    static QStringList keys;
    keys = loadedExtensions().keys();
    return keys;
}

// -------------------- getRegisteredExtensionFiles --------------------
const QStringList HotPlugExtensionManager::getRegisteredExtensionFiles() {
    QSettings& settings = Application::getSettings();
    settings.beginGroup(Application::getName() + ".HotPlug");
    QStringList camitkExtensionFilesInSettings = settings.value("CamiTKExtensionFiles", QVariant(QStringList())).toStringList();
    settings.endGroup();
    return camitkExtensionFilesInSettings;
}

// -------------------- getLoadedExtensions --------------------
const QList<HotPlugActionExtension*>& HotPlugExtensionManager::getLoadedExtensions() {
    static QList<HotPlugActionExtension*> actionExtensions;
    actionExtensions.clear();
    for (auto ae : loadedExtensions().values()) {
        actionExtensions.append(ae);
    }
    return actionExtensions;
}

// -------------------- loadedExtensions --------------------
QMap<QString, HotPlugActionExtension*>& HotPlugExtensionManager::loadedExtensions() {
    static QMap<QString, HotPlugActionExtension*> loadedExtensionList;

    return loadedExtensionList;
}

// -------------------- registerExtension --------------------
bool HotPlugExtensionManager::registerExtension(const QString& camitkExtensionFilePath) {
    // First check if the action's type can be registered
    CamiTKExtensionModel camitkExtensionModel(camitkExtensionFilePath);
    QString generationType = camitkExtensionModel.getModel()["generationType"];
    QString language = camitkExtensionModel.getModel()["language"];
    if (generationType != "HotPlug" && language == "C++") {
        CAMITK_WARNING_ALT(QString("Cannot register extension %1: neither a C++ HotPlug nor a Python extension.").arg(camitkExtensionFilePath));
        return false;
    }

    QSettings& settings = Application::getSettings();
    settings.beginGroup(Application::getName() + ".HotPlug");
    QStringList camitkExtensionFilesInSettings = settings.value("CamiTKExtensionFiles", QVariant(QStringList())).toStringList();

    bool registered = true;
    if (!camitkExtensionFilesInSettings.contains(camitkExtensionFilePath)) {
        HotPlugActionExtension* actionExtension = load(camitkExtensionFilePath);
        if (actionExtension) {
            QStringList camitkExtensionFilesInSettings = settings.value("CamiTKExtensionFiles", QVariant(QStringList())).toStringList();
            camitkExtensionFilesInSettings.append(camitkExtensionFilePath);
            settings.setValue("CamiTKExtensionFiles", camitkExtensionFilesInSettings);
        }
        else {
            registered = false;
        }
    }

    settings.endGroup();
    return registered;
}

// -------------------- unregisterExtension --------------------
bool HotPlugExtensionManager::unregisterExtension(const QString& camitkExtensionFilePath) {
    // remove from settings (unconditionally)
    QSettings& settings = Application::getSettings();
    settings.beginGroup(Application::getName() + ".HotPlug");
    QStringList camitkExtensionFilesInSettings = settings.value("CamiTKExtensionFiles", QVariant(QStringList())).toStringList();
    if (camitkExtensionFilesInSettings.contains(camitkExtensionFilePath)) {
        camitkExtensionFilesInSettings.removeAll(camitkExtensionFilePath);
        settings.setValue("CamiTKExtensionFiles", camitkExtensionFilesInSettings);
    }
    settings.endGroup();

    // remove from memory and return true if successful
    return unload(camitkExtensionFilePath);
}

// -------------------- loadAll --------------------
bool HotPlugExtensionManager::loadAll() {
    bool success = true;
    // At startup 20% of extension has been done (see ExtensionManager::autoload)
    int progressBarValue = 20;
    // The remaining has to be distributed among the hotplug extension
    float extensionProgressBarRange = 80.0 / getRegisteredExtensionFiles().size();

    for (QString camitkExtensionFile : getRegisteredExtensionFiles()) {
        if (load(camitkExtensionFile, false, progressBarValue, progressBarValue + extensionProgressBarRange) == nullptr) {
            success = false;
        }
        progressBarValue += extensionProgressBarRange;
        Application::setProgressBarValue(progressBarValue);
    }
    return success;
}

// -------------------- unloadAll --------------------
bool HotPlugExtensionManager::unloadAll() {
    bool success = true;
    for (auto& loadedExtensionFile : loadedExtensions().keys()) {
        if (!unload(loadedExtensionFile)) {
            success = false;
        }
    }
    loadedExtensions().clear();
    return success;
}

// -------------------- load --------------------
HotPlugActionExtension* HotPlugExtensionManager::load(const QString& camitkExtensionFilePath, bool forceRebuild, int progressMinimum, int progressMaximum) {
    float progressValue = progressMinimum;
    float progressStep = (progressMaximum - progressMinimum) / 2.0;
    QString extensionShortName = QFileInfo(camitkExtensionFilePath).fileName();

    Application::showStatusBarMessage("Loading registered action extension " + extensionShortName + "...");

    HotPlugActionExtension* actionExtension = loadedExtensions().value(camitkExtensionFilePath);
    if (!actionExtension) {
        // instantiate extension
        Application::setProgressBarValue(progressMinimum);
        actionExtension = HotPlugActionExtension::newHotPlugActionExtension(camitkExtensionFilePath, forceRebuild);
        progressValue += progressStep;
        Application::setProgressBarValue(progressValue);

        if (actionExtension) {
            // the extension was instantiated, all good so far
            int expected = actionExtension->declaredActionCount();

            // initialize/create/instantiate all actions
            Application::showStatusBarMessage("Initializing actions for " + extensionShortName + "...");
            if (!actionExtension->initActions(progressValue, progressMaximum)) {
                CAMITK_WARNING_ALT(QString("Extension %1 failed to initialize.").arg(camitkExtensionFilePath))
                // there was a problem, delete the extension
                delete actionExtension;
                Application::resetProgressBar();
                return nullptr;
            }

            // check if everything went alright
            int actual = actionExtension->getActions().size();
            if (actual != expected) {
                QString wasOrWere = (actual == 1) ? "was" : "were";
                QString only = (actual >= 1) ? " only" : "";
                QString created = (actual == 0) ? "none" : QString::number(actual);
                CAMITK_WARNING_ALT(QString("Extension %1 declared %2 actions but%3 %4 %5 created.").arg(camitkExtensionFilePath).arg(expected).arg(only).arg(created).arg(wasOrWere));
            }

            //-- register all actions that were created
            int registered = Application::registerAllActions(actionExtension);

            //-- check that all the available actions were registered properly and that at list one action is registered
            if (registered == actual || registered > 0) {
                loadedExtensions().insert(camitkExtensionFilePath, actionExtension);
                Application::showStatusBarMessage("Action extension " + extensionShortName + " loaded");
                Application::resetProgressBar();
                return actionExtension;
            }
            else {
                // there was a problem, just remove them all
                delete actionExtension;
                Application::resetProgressBar();
                return nullptr;
            }
        }
        else {
            // There was a problem during the extension instantiation
            // ask if this extension should be removed from the settings (if it was)
            // remove from settings (unconditionally)
            QStringList registeredExtensions = getRegisteredExtensionFiles();
            if (registeredExtensions.contains(camitkExtensionFilePath)) {
                QMessageBox::StandardButton reply = QMessageBox::warning(nullptr, "Loading HotPlug Action Extension failed",
                                                    tr("Action extension \"%1\" is registered, but an error occurred while loading it.<br/>Do you want to unregister the extension for now?").arg(camitkExtensionFilePath),
                                                    QMessageBox::Yes | QMessageBox::No);
                if (reply == QMessageBox::Yes) {
                    unregisterExtension(camitkExtensionFilePath);

                }
            }
        }
    }

    Application::resetProgressBar();
    return actionExtension;
}

// -------------------- unload --------------------
bool HotPlugExtensionManager::unload(const QString& camitkExtensionFilePath) {
    if (loadedExtensions().contains(camitkExtensionFilePath)) {
        HotPlugActionExtension* actionExtension = loadedExtensions().value(camitkExtensionFilePath);
        // -- unregister actions from application
        Application::unregisterAllActions(actionExtension);
        //-- delete extensions (and all its actions)
        delete actionExtension;

        // remove from the loaded list
        loadedExtensions().remove(camitkExtensionFilePath);
        return true;
    }
    else {
        return false;
    }
}

// -------------------- setVerifyOrRebuildOption --------------------
void HotPlugExtensionManager::setVerifyOrRebuildOption(bool alwaysRebuild) {
    QSettings& settings = Application::getSettings();
    settings.beginGroup(Application::getName() + ".HotPlug");
    settings.setValue("AlwaysRebuild", alwaysRebuild);
    settings.endGroup();
}

// -------------------- getVerifyOrRebuildOption --------------------
bool HotPlugExtensionManager::getVerifyOrRebuildOption() {
    QSettings& settings = Application::getSettings();
    settings.beginGroup(Application::getName() + ".HotPlug");
    bool alwaysRebuild = settings.value("AlwaysRebuild", false).toBool();
    settings.endGroup();
    return alwaysRebuild;
}

} // namespace camitk