/*****************************************************************************
 * $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 "SettingsDialog.h"
#include "Application.h"
#include "Core.h"
#include "Log.h"
#include "ExtensionManager.h"
#include "ComponentExtension.h"
#include "ActionExtension.h"
#include "Action.h"
#include "ViewerExtension.h"
#include "Viewer.h"
#include "ObjectController.h"
#include "ui_Settings.h"

// -- QT stuff
#include <QApplication>
#include <QMessageBox>
#include <QFileDialog>
#include <QHeaderView>
#include <QMetaObject>
#include <QMetaMethod>
#include <QSettings>

namespace camitk {
// ------------------------------ constructor -------------------------------
SettingsDialog::SettingsDialog() : QDialog(qApp->activeWindow()) {
    myUI = new Ui::ui_Settings;
    myUI->setupUi(this);

    // -- update plugin viewers
    readUserExtensions();
    updateComponentExtensionList();
    updateActionExtensionList();
    updateViewerExtensionList();

    // -- setup the general tab
    myUI->generalTab->setVisible(false);
    objectController = new ObjectController(myUI->propEditorFrame, ObjectController::BUTTON);
    myUI->propEditorFrame->layout()->addWidget(objectController);

    // and its buttons
    QFrame* buttonFrame = new QFrame(myUI->propEditorFrame);
    auto* buttonLayout = new QHBoxLayout;
    QPushButton* applyButton = new QPushButton("Apply");
    buttonLayout->addWidget(applyButton);

    QPushButton* revertButton = new QPushButton("Revert");
    buttonLayout->addWidget(revertButton);

    buttonFrame->setLayout(buttonLayout);
    myUI->propEditorFrame->layout()->addWidget(buttonFrame);

    // connect the buttons
    QObject::connect(applyButton, SIGNAL(clicked()), objectController, SLOT(apply()));
    QObject::connect(revertButton, SIGNAL(clicked()), objectController, SLOT(revert()));

    // -- show settings
    myUI->tabWidget->setCurrentWidget(myUI->componentExtensionsTab);
    adjustSize();
}

// ------------------------------ destructor -------------------------------
SettingsDialog::~SettingsDialog() {
    delete myUI;
    myUI = nullptr;
    delete objectController;
    objectController = nullptr;
}

// ------------------------------ readUserExtensions -------------------------------
void SettingsDialog::readUserExtensions() {
    QSettings& settings = Application::getSettings();
    settings.beginGroup("UserExtensions");
    userComponents = settings.value("components", QVariant(QStringList())).toStringList();
    userActions = settings.value("actions", QVariant(QStringList())).toStringList();
    userViewers = settings.value("viewers", QVariant(QStringList())).toStringList();
    settings.endGroup();
}

// ------------------------------ writeUserExtensions -------------------------------
void SettingsDialog::writeUserExtensions() {
    QSettings& settings = Application::getSettings();
    settings.beginGroup("UserExtensions");
    settings.setValue("components", userComponents);
    settings.setValue("actions", userActions);
    settings.setValue("viewers", userViewers);
    settings.endGroup();
}

// ------------------------------ editSettings -------------------------------
void SettingsDialog::editSettings(QObject* qObj) {
    editedObjectMap.insert(qObj->objectName(), qObj);

    //-- add the object to the property editor and the objectList
    objectController->setObject(nullptr); // force update
    objectController->setObject(qObj);
    objectController->adjustSize();
    objectController->show();

    QListWidgetItem* qObjNameWidget = new QListWidgetItem;
    qObjNameWidget->setText(qObj->objectName());
    myUI->objectList->addItem(qObjNameWidget);
    myUI->objectList->setCurrentItem(qObjNameWidget);

    // focus on it
    myUI->generalTab->setVisible(true);
    myUI->tabWidget->setCurrentWidget(myUI->generalTab);
}

// ------------------------------ updateComponentExtensionList -------------------------------
void SettingsDialog::updateComponentExtensionList() {
    const QList<ComponentExtension*>& ceList =  ExtensionManager::getComponentExtensionsList();
    const QList<ComponentExtension*>& ceddList =  ExtensionManager::getDataDirectoryComponentsList();

    // remove all the items
    myUI->componentExtensionList->clearContents();
    myUI->componentExtensionList->setRowCount(ceList.size() + ceddList.size());
    myUI->componentExtensionList->verticalHeader()->setVisible(false);
    myUI->componentExtensionList->setSortingEnabled(false); // temporarily for insertion!

    // loop over all ManagerExtensions
    // set the different columns text and pixmaps
    // columns: name, state (i.e loaded or not), extension, location
    int i;
    for (i = 0; i < ceList.size(); i++) {
        ComponentExtension* ce = ceList.at(i);
        // set the icon depending if this is a system-wide component (camitk icon) or user settings (user icon)
        myUI->componentExtensionList->setItem(i, 0, new QTableWidgetItem((userComponents.contains(ce->getLocation()) ? QPixmap(":/user") : QPixmap(":/camiTKIcon")), ce->getName()));
        myUI->componentExtensionList->setItem(i, 1, new QTableWidgetItem(ce->getFileExtensions().join(", ")));
        myUI->componentExtensionList->setItem(i, 2, new QTableWidgetItem(ce->getLocation()));
    }

    // list all data directory plugin
    for (; (i - ceList.size()) < ceddList.size(); i++) {
        ComponentExtension* ce = ceddList.at(i - ceList.size());
        myUI->componentExtensionList->setItem(i, 0, new QTableWidgetItem((userComponents.contains(ce->getLocation()) ? QPixmap(":/user") : QPixmap(":/camiTKIcon")), ce->getName()));
        myUI->componentExtensionList->setItem(i, 1, new QTableWidgetItem("[directory]"));
        myUI->componentExtensionList->setItem(i, 2, new QTableWidgetItem(ce->getLocation()));
    }

    myUI->componentExtensionList->resizeColumnsToContents();
    myUI->componentExtensionList->resizeRowsToContents();
    myUI->componentExtensionList->setSortingEnabled(true);
    myUI->componentExtensionList->sortByColumn(0, Qt::AscendingOrder);
}


// ------------------------------ updateActionExtensionList -------------------------------
void SettingsDialog::updateActionExtensionList() {
    const QList<ActionExtension*> aeList = ExtensionManager::getActionExtensionsList();

    // remove all the items
    myUI->actionExtensionList->clearContents();
    myUI->actionExtensionList->setRowCount(Application::getActions().size());
    myUI->actionExtensionList->verticalHeader()->setVisible(false);
    myUI->actionExtensionList->setSortingEnabled(false); // temporarily for insertion!

    // loop over all ActionExtensions
    unsigned int actionId = 0;
    for (ActionExtension* ae : aeList) {
        // create table row
        for (int i = 0; i < ae->getActions().size(); i++) {
            Action* action = ae->getActions().at(i);
            QPixmap actionIcon = action->getIcon();
            myUI->actionExtensionList->setItem(actionId, 0, new QTableWidgetItem((userActions.contains(ae->getLocation()) ? QPixmap(":/user") : ((actionIcon.isNull()) ? QPixmap(":/camiTKIcon") : actionIcon)), action->getName()));
            myUI->actionExtensionList->setItem(actionId, 1, new QTableWidgetItem(action->getComponentClassName()));
            myUI->actionExtensionList->setItem(actionId, 2, new QTableWidgetItem(ae->getLocation()));
            actionId++;
        }
    }

    myUI->actionExtensionList->resizeColumnsToContents();
    myUI->actionExtensionList->setSortingEnabled(true);
    myUI->actionExtensionList->sortByColumn(0, Qt::AscendingOrder);
}


// ------------------------------ updateViewerExtensionList -------------------------------
void SettingsDialog::updateViewerExtensionList() {
    const QList<ViewerExtension*> veList = ExtensionManager::getViewerExtensionsList();

    // remove all the items
    myUI->viewerExtensionList->clearContents();
    myUI->viewerExtensionList->setRowCount(Application::getViewers().size());
    myUI->viewerExtensionList->verticalHeader()->setVisible(false);
    myUI->viewerExtensionList->setSortingEnabled(false); // temporarily for insertion!

    // loop over all ActionExtensions
    unsigned int viewerId = 0;
    for (ViewerExtension* ve : veList) {
        // create table row
        for (int i = 0; i < ve->getViewers().size(); i++) {
            Viewer* viewer = ve->getViewers().at(i);
            QPixmap viewerIcon = viewer->getIcon();
            myUI->viewerExtensionList->setItem(viewerId, 0, new QTableWidgetItem((userViewers.contains(ve->getLocation()) ? QPixmap(":/user") : ((viewerIcon.isNull()) ? QPixmap(":/camiTKIcon") : viewerIcon)), viewer->getName()));
            myUI->viewerExtensionList->setItem(viewerId, 1, new QTableWidgetItem(ve->getName()));
            myUI->viewerExtensionList->setItem(viewerId, 2, new QTableWidgetItem(ve->getLocation()));
            viewerId++;
        }
    }

    myUI->viewerExtensionList->resizeColumnsToContents();
    myUI->viewerExtensionList->setSortingEnabled(true);
    myUI->viewerExtensionList->sortByColumn(0, Qt::AscendingOrder);
}

// ------------------------------ accept -------------------------------
void SettingsDialog::accept() {
    if (objectController->isModified()) {
        // CCC Exception: Use a QMessageBox::question as the user interaction is required
        if (QMessageBox::question(this, "Unsaved changes", tr("There are unsaved changes in current item.\nIf you click \"Discard\" now, they will be discarded,\nif you click \"Apply\" they will be applied."), QMessageBox::Discard, QMessageBox::Apply) == QMessageBox::Apply) {
            objectController->apply();
        }
    }

    QDialog::accept();
}

// ------------------------------ on_objectList_itemSelectionChanged -------------------------------
void SettingsDialog::on_objectList_itemSelectionChanged() {
    if (myUI->objectList->selectedItems().size() > 0) {
        if (objectController->isModified()) {
            // CCC Exception: Use a QMessageBox::question as the user interaction is required
            if (QMessageBox::question(this, "Unsaved changes", tr("There are unsaved changes in current item.\nIf you click \"Discard\" now, they will be discarded,\nif you click \"Apply\" they will be applied."), QMessageBox::Discard, QMessageBox::Apply) == QMessageBox::Apply) {
                objectController->apply();
            }
        }
        QListWidgetItem* selected = myUI->objectList->selectedItems()[0];
        objectController->setObject(editedObjectMap.value(selected->text()));
    }
    else {
        objectController->setObject(nullptr);
    }
}

// ------------------------------ on_resetConfigurationButton_released -------------------------------
void SettingsDialog::on_resetConfigurationButton_released() {
    // CCC Exception: Use a QMessageBox::warning instead of CAMITK_WARNING as the user interaction is required
    QMessageBox warnMsg(QMessageBox::Warning,
                        tr("Reset All Preferences?"),
                        tr("This action will erase all your modified settings."
                           "All user preferences will be deleted."
                           "The default configuration will be loaded instead"),
                        QMessageBox::Ok | QMessageBox::Cancel,
                        this);
    warnMsg.setDefaultButton(QMessageBox::Cancel);
    warnMsg.setButtonText(QMessageBox::Ok, tr("Reset Configuration"));

    if (QMessageBox::Ok == warnMsg.exec()) {

        QSettings& settings = Application::getSettings();
        settings.clear();

        CAMITK_WARNING(tr("Please, restart the application.\n")
                       + tr("All default configuration and user preference have been deleted.\n")
                       + tr("Please exit the application and restart for this action to take effect!"))

        QDialog::reject();
    }
}


// ------------------------------ on_addComponentExtensionButton_released -------------------------------
void SettingsDialog::on_addComponentExtensionButton_released() {
    QStringList files = QFileDialog::getOpenFileNames(nullptr, "Add a ComponentExtension (Component) Plugin", Core::getComponentDirectories().first(), "Component Plugin (*.so.* *.dll *.dylib)");

    QStringList pluginFile = files;
    QStringList::Iterator it = pluginFile.begin();

    while (it != pluginFile.end()) {
        if (!ExtensionManager::loadExtension(ExtensionManager::COMPONENT, *it)) {
            CAMITK_WARNING(tr("ComponentExtension error: cannot load plugin from file %1\nTry to recompile the plugin\n(plugin might be outdated)").arg((*it)))
        }
        else {
            Application::showStatusBarMessage("Plugin " + (*it) + " loaded.");
            // Add the filename in the application settings: this is a manually loaded plugin, it should therefore
            // be kept in a list for the next run
            userComponents += (*it);
            writeUserExtensions();
        }

        ++it; //next one!
    }

    // update the display
    updateComponentExtensionList();
}

// ----------------- on_removeComponentExtensionButton_released ----------------------------
void SettingsDialog::on_removeComponentExtensionButton_released() {
    // remove the plugin
    QString pluginExt = myUI->componentExtensionList->item(myUI->componentExtensionList->currentRow(), 1)->text();
    ExtensionManager::unloadComponentExtension(pluginExt);

    // remove from settings
    QString location = myUI->componentExtensionList->item(myUI->componentExtensionList->currentRow(), 2)->text();
    userComponents.removeAll(location);
    writeUserExtensions();

    // update the display
    updateComponentExtensionList();

    // removing is allowed by selecting a new component extension
    myUI->removeComponentExtensionButton->setEnabled(false);
    myUI->componentExtensionDescription->setText("Click on a registered component extension above to display its description.");
}

// ------------------------------ on_componentExtensionList_cellClicked -------------------------------
void SettingsDialog::on_componentExtensionList_cellClicked(int row, int column) {
    const ComponentExtension* ce = ExtensionManager::getComponentExtension(myUI->componentExtensionList->item(myUI->componentExtensionList->currentRow(), 0)->text());
    myUI->removeComponentExtensionButton->setEnabled(userComponents.contains(ce->getLocation()));
    myUI->componentExtensionDescription->setText(ce->getDescription());
}

// ------------------------------ on_addActionExtensionButton_released -------------------------------
void SettingsDialog::on_addActionExtensionButton_released() {
    QStringList files = QFileDialog::getOpenFileNames(nullptr, "Add an Action Extension", Core::getActionDirectories().first(), "Action Extension (*.so.* *.dll *.dylib)");

    QStringList pluginFile = files;
    QStringList::Iterator it = pluginFile.begin();

    while (it != pluginFile.end()) {
        if (!ExtensionManager::loadExtension(ExtensionManager::ACTION, *it)) {
            CAMITK_ERROR(tr("ActionExtension error: cannot load plugin from file:\n\"%1\"\nTry to recompile the plugin\n(plugin might be outdated)").arg((*it)))
        }
        else {
            Application::showStatusBarMessage("Plugin " + (*it) + " loaded.");
            // Add the filename in the application settings: this is a manually loaded plugin, it should therefore
            // be kept in a list for the next run
            userActions += (*it);
            writeUserExtensions();
        }

        ++it; //next one!
    }

    // update the display
    updateActionExtensionList();
}

// ----------------- on_removeActionExtensionButton_released ----------------------------
void SettingsDialog::on_removeActionExtensionButton_released() {
    // remove the plugin
    QString actionFileName = myUI->actionExtensionList->item(myUI->actionExtensionList->currentRow(), 2)->text();
    ActionList actions = ExtensionManager::getActionExtension(actionFileName)->getActions();

    // If the action extension contains other actions than the one selected, ask the user if this ok
    // (as removing one action removes the whole extension and the extension might contain more than one action)
    bool reallyDelete = true;
    if (actions.size() > 1) {
        QString actionName = myUI->actionExtensionList->item(myUI->actionExtensionList->currentRow(), 0)->text();
        QStringList actionNames;
        for (Action* a : actions) {
            if (a->getName() != actionName) {
                actionNames << a->getName();
            }
        }
        // CCC Exception: Use a QMessageBox::warning instead of CAMITK_WARNING as the user interaction is required
        reallyDelete = (QMessageBox::warning(this, "Removing multiple actions", tr("Removing action \"") + actionName + tr(" \" will also remove the following actions:<ul><li>") + actionNames.join("</li><li>") + tr("</li></ul>Are you sure you want to delete all these actions?"), QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes);
    }
    if (reallyDelete) {
        ExtensionManager::unloadActionExtension(actionFileName);
        // remove from settings
        userActions.removeAll(actionFileName);
        writeUserExtensions();

        // update the display
        updateActionExtensionList();

        // removing is allowed by selecting a new component extension
        myUI->removeActionExtensionButton->setEnabled(false);
        myUI->actionExtensionDescription->setText("Click on an registered action above to display its description.");
    }

}

// ------------------------------ on_actionExtensionList_cellClicked -------------------------------
void SettingsDialog::on_actionExtensionList_cellClicked(int row, int column) {
    myUI->removeActionExtensionButton->setEnabled(userActions.contains(myUI->actionExtensionList->item(myUI->actionExtensionList->currentRow(), 2)->text()));
    myUI->actionExtensionDescription->setText(Application::getAction
            (myUI->actionExtensionList->item(myUI->actionExtensionList->currentRow(), 0)->text())->getDescription());
}


// ------------------------------ on_addViewerExtensionButton_released -------------------------------
void SettingsDialog::on_addViewerExtensionButton_released() {
    QStringList files = QFileDialog::getOpenFileNames(nullptr, "Add an Viewer Extension", Core::getViewerDirectories().first(), "Viewer Extension (*.so.* *.dll *.dylib)");

    QStringList pluginFile = files;
    QStringList::Iterator it = pluginFile.begin();

    while (it != pluginFile.end()) {
        if (!ExtensionManager::loadExtension(ExtensionManager::VIEWER, *it)) {
            CAMITK_ERROR(tr("ViewerExtension error: cannot load plugin from file:\n\"%1\"\nTry to recompile the plugin\n(plugin might be outdated)").arg((*it)))
        }
        else {
            Application::showStatusBarMessage("Plugin " + (*it) + " loaded.");
            // Add the filename in the application settings: this is a manually loaded plugin, it should therefore
            // be kept in a list for the next run
            userViewers += (*it);
            writeUserExtensions();
        }

        ++it; //next one!
    }

    // update the display
    updateViewerExtensionList();
}

// ----------------- on_removeViewerExtensionButton_released ----------------------------
void SettingsDialog::on_removeViewerExtensionButton_released() {
    // remove the plugin
    QString viewerFileName = myUI->viewerExtensionList->item(myUI->viewerExtensionList->currentRow(), 2)->text();
    ViewerList viewers = ExtensionManager::getViewerExtension(viewerFileName)->getViewers();

    // If the viewer extension contains other viewers than the one selected, ask the user if this ok
    // (as removing one viewer removes the whole extension and the extension might manage more than one viewer)
    bool reallyDelete = true;
    if (viewers.size() > 1) {
        QString viewerName = myUI->viewerExtensionList->item(myUI->viewerExtensionList->currentRow(), 0)->text();
        QStringList viewerNames;
        for (Viewer* v : viewers) {
            if (v->getName() != viewerName) {
                viewerNames << v->getName();
            }
        }
        // CCC Exception: Use a QMessageBox::warning instead of CAMITK_WARNING as the user interaction is required
        reallyDelete = (QMessageBox::warning(this, "Removing multiple viewers", tr("Removing viewer \"") + viewerName + tr(" \" will also remove the following viewers:<ul><li>") + viewerNames.join("</li><li>") + tr("</li></ul>Are you sure you want to delete all these viewers?"), QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes);
    }
    if (reallyDelete) {
        ExtensionManager::unloadViewerExtension(viewerFileName);
        // remove from settings
        userViewers.removeAll(viewerFileName);
        writeUserExtensions();

        // update the display
        updateViewerExtensionList();

        // removing is allowed by selecting a new component extension
        myUI->removeViewerExtensionButton->setEnabled(false);
        myUI->viewerExtensionDescription->setText("Click on an registered viewer above to display its description.");
    }
}

// ------------------------------ on_viewerExtensionList_cellClicked -------------------------------
void SettingsDialog::on_viewerExtensionList_cellClicked(int row, int column) {
    myUI->removeViewerExtensionButton->setEnabled(userViewers.contains(myUI->viewerExtensionList->item(myUI->viewerExtensionList->currentRow(), 2)->text()));
    myUI->viewerExtensionDescription->setText(Application::getViewer
            (myUI->viewerExtensionList->item(myUI->viewerExtensionList->currentRow(), 0)->text())->getDescription());
}

}

