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

// -- Application Imp stuff
#include "ImpMainWindow.h"

// -- Core stuff
#include <Application.h>
#include <ExtensionManager.h>
#include <SettingsDialog.h>
#include <Component.h>
#include <Action.h>
#include <Viewer.h>
#include <Core.h>
#include <PropertyObject.h>
#include <HotPlugExtensionManager.h>
#include <Log.h>

using namespace camitk;

// -- QT stuff
#include <QAction>
#include <QFileDialog>
#include <QPixmap>
#include <QWhatsThis>
#include <QMenu>
#include <QMenuBar>
#include <QStatusBar>
#include <QDockWidget>
#include <QStackedWidget>
#include <QTranslator>
#include <QMessageBox>
#include <QTimer>
#include <QStyle>

// -- Std stuff
#include <utility> // as_const

// ------------- constructor -----------------
ImpMainWindow::ImpMainWindow() : MainWindow("imp") {
    // set the specific actions state machine icon
    setWindowIcon(QPixmap(":/applicationIcon"));

    fileOpenDataDirectoryMenu = nullptr;

    // init all other GUI
    initActions();
    initMenuBar();

    // initialize architecture
    updateActionStates();

    // check that all required viewers are loaded
    QStringList requiredViewerNames = {"Medical Image Viewer", "Component Explorer", "Transformation Explorer", "Property Explorer", "Action Viewer"};
    QMap<QString, Viewer*> impViewers;
    QStringList missingViewers;
    for (QString name : requiredViewerNames) {
        Viewer* v = Application::getViewer(name);
        if (v) {
            impViewers.insert(name, v);
        }
        else {
            missingViewers.append(name);
        }
    }
    if (missingViewers.size() > 0) {
        CAMITK_ERROR(tr("The following mandatory viewer(s) are missing:\n- %1\nThese viewers are required by camitk-imp but could not be found\nIn any of the following extension directories:\n - %2\nPlease check your CamiTK configuration and/or installation using \"camitk-config --config\"").arg(missingViewers.join("\n- "), Core::getActionDirectories().join("\n - ")))
    }

    // now add the different required viewers
    setCentralViewer(impViewers["Medical Image Viewer"]);

    // Merge ComponentExplorer and TransformationExplorer viewers in one layout
    addDockViewer(Qt::LeftDockWidgetArea, impViewers["Component Explorer"]);
    addDockViewer(Qt::LeftDockWidgetArea, impViewers["Transformation Explorer"]);    
    QDockWidget* dockWidgetComponentExplorer = dockWidgetMap.value(impViewers["Component Explorer"], nullptr);
    QDockWidget* dockWidgetTransformationExplorer = dockWidgetMap.value(impViewers["Transformation Explorer"], nullptr);
    if (dockWidgetComponentExplorer != nullptr && dockWidgetTransformationExplorer != nullptr) {
        tabifyDockWidget(dockWidgetComponentExplorer, dockWidgetTransformationExplorer);
        dockWidgetComponentExplorer->raise();
    }
    
    addDockViewer(Qt::LeftDockWidgetArea, impViewers["Property Explorer"]);    

    addDockViewer(Qt::RightDockWidgetArea, impViewers["Action Viewer"]);
    showDockViewer(impViewers["Action Viewer"], false);

    showStatusBar(true);
}

// ------------- destructor -----------------
ImpMainWindow::~ImpMainWindow() {
}

// ------------- aboutToShow -----------------
void ImpMainWindow::aboutToShow() {
    MainWindow::aboutToShow(); // calls init settings

    // now that initSettings was called, populate the recent document menu
    updateRecentDocumentsMenu();

    // check autoload for files and load if needed
    if (Application::getPropertyObject()->getPropertyValue("Auto Load Last Opened Component").toBool() && Application::getRecentDocuments().size() > 0) {
        Application::open(Application::getRecentDocuments().last().absoluteFilePath());
    }
}

// ------------- refresh -----------------
void ImpMainWindow::refresh() {
    MainWindow::refresh();

    // update all the action states
    updateActionStates();

    // update menu
    updateRecentDocumentsMenu();
}

// ------------- addDockViewer -----------------
void ImpMainWindow::addDockViewer(Qt::DockWidgetArea dockingArea, Viewer* theViewer) {
    MainWindow::addDockViewer(dockingArea, theViewer);
    // update the menu
    updateViewMenu();
}

// ------------- setCentralViewer -----------------
void ImpMainWindow::setCentralViewer(Viewer* theViewer) {
    MainWindow::setCentralViewer(theViewer);
    // update the menu
    updateViewMenu();
}

// ------------- initActions -----------------
void ImpMainWindow::initActions() {
    //-- Extract applications level action (file, edit and help)
    QStringList requiredExtensionNames = {"File Application Level Actions", "Edit Application Level Actions", "Help Application Level Actions"};
    ActionList allActions = Application::getActions();
    QSet<QString> existingExtensionNames;
    QMap<QString, Action*> applicationLevelActions;
    std::for_each(allActions.begin(), allActions.end(), [&](Action * a) {
        if (a && requiredExtensionNames.contains(a->getExtensionName())) {
            existingExtensionNames.insert(a->getExtensionName());
            applicationLevelActions.insert(a->getName(), a);
        }
    });

    // check for missing extension
    QStringList missingExtensions;
    for (QString name : requiredExtensionNames) {
        if (!existingExtensionNames.contains(name)) {
            missingExtensions.append(name);
        }
    }
    if (missingExtensions.size() > 0) {
        CAMITK_ERROR(tr("The following mandatory extension are missing:\n- %1\nThese action extensions are required by camitk-imp but could not be found\nIn any of the following extension directories:\n - %2\nPlease check your CamiTK configuration and/or installation using \"camitk-config --config\"").arg(missingExtensions.join("\n- "), Core::getActionDirectories().join("\n - ")))
        exit(EXIT_FAILURE);
    }

    // check for missing actions
    QStringList requiredActionNames = {"Open", "Close", "Close All", "Save", "Save As", "Save All", "Save Workspace", "Quit", "Clear Selection", "About...", "Toggle Log Console", "Change Language"};
    QStringList missingActions;
    for (QString name : requiredActionNames) {
        if (!applicationLevelActions.contains(name)) {
            missingActions.append(name);
        }
    }
    if (missingActions.size() > 0) {
        CAMITK_ERROR(tr("The following mandatory actions are missing:\n- %1\nThese actions are required by camitk-imp but could not be found\nIn any of the following extension directories:\n - %2\nPlease check your CamiTK configuration and/or installation using \"camitk-config --config\"").arg(missingActions.join("\n- "), Core::getActionDirectories().join("\n - ")))
        exit(EXIT_FAILURE);
    }

    //--- actions of the File menu
    // get the CamiTK actions
    fileOpen = applicationLevelActions["Open"]->getQAction();
    fileClose = applicationLevelActions["Close"]->getQAction();
    fileCloseAll = applicationLevelActions["Close All"]->getQAction();
    fileSave = applicationLevelActions["Save"]->getQAction();
    fileSaveAs = applicationLevelActions["Save As"]->getQAction();
    fileSaveAll = applicationLevelActions["Save All"]->getQAction();
    workspaceSave = applicationLevelActions["Save Workspace"]->getQAction();
    fileQuit = applicationLevelActions["Quit"]->getQAction();

    recentDocumentSeparator = new QAction(tr("Recent Documents"), this);
    recentDocumentSeparator->setSeparator(true);
    recentDocumentSeparator->setVisible(false);

    for (int i = 0; i < Application::getMaxRecentDocuments(); i++) {
        recentDocumentActions.append(new QAction(this));
        recentDocumentActions.last()->setVisible(false);
        connect(recentDocumentActions.last(), SIGNAL(triggered()), this, SLOT(openRecentDocuments()));
    }

    //--- actions of the Edit menu
    editClearSelection = applicationLevelActions["Clear Selection"]->getQAction();

    // settings
    editApplicationSettings = new QAction(tr("&Preferences..."), this);
    editApplicationSettings->setShortcut(tr("Ctrl+P"));
    editApplicationSettings->setStatusTip(tr("Edit the preferences"));
    editApplicationSettings->setWhatsThis(tr("Preferences\n\nEdit the settings and preferences of imp"));
    connect(editApplicationSettings, SIGNAL(triggered()), this, SLOT(editSettings()));

    // save history as SCXML
    saveHistory = new QAction(tr("&Save History"), this);
    saveHistory->setStatusTip(tr("Save the history of actions processed as an SCXML file."));
    saveHistory->setWhatsThis(tr("Save the history of actions processed as an SCXML file."));
    connect(saveHistory, SIGNAL(triggered()), this, SLOT(saveHistoryAsSCXML()));

    //--- actions of the View menu
    viewMenuBar = new QAction(tr("Menu bar"), this);
    viewMenuBar->setCheckable(true);
    viewMenuBar->setChecked(true);
    viewMenuBar->setStatusTip(tr("Hide/show the menu bar (Ctrl-M to show again)."));
    viewMenuBar->setWhatsThis(tr("Hide/show the menu bar (Ctrl-M to show again)."));
    viewMenuBar->setShortcut(tr("Ctrl+M"));
    // global shortcut, NOTE: the action should also be added as an ImpMainWindow action, otherwise the shortcut will not work when
    // the menu bar is hidden! (see initMenuBar() method)
    viewMenuBar->setShortcutContext(Qt::ApplicationShortcut);
    connect(viewMenuBar, SIGNAL(triggered()), this, SLOT(toggleMenuBar()));
    // NOTE: viewMenuBar can be used to hide the menu bar, if the menu bar is not visible,
    // since Qt4, it does not receive any event (and thus there is no way to set the menu
    // visible again! Which is quite annoying).
    // To prevent this, the viewMenuBar action has to be added to the QMainWindow as well
    // This should be done everytime the shortcut context is set
    // to Qt::ApplicationShortcut using setShortcutContext(Qt::ApplicationShortcut);
    this->addAction(viewMenuBar);

    viewStatusBar = new QAction(tr("Status bar"), this);
    viewStatusBar->setCheckable(true);
    viewStatusBar->setChecked(true);
    viewStatusBar->setStatusTip(tr("Hide/show the status bar."));
    viewStatusBar->setWhatsThis(tr("Hide/show the status bar."));
    connect(viewStatusBar, SIGNAL(toggled(bool)), this, SLOT(showStatusBar(bool)));

    viewResetWindows = new QAction(tr("Reset Windows"), this);
    viewResetWindows->setStatusTip(tr("Reset all windows in the initial state"));
    connect(viewResetWindows, SIGNAL(triggered()), this, SLOT(resetWindows()));

    //--- actions for the Dev menu
    QMap<QString, Action*> devStudioActions;
    std::for_each(allActions.begin(), allActions.end(), [&devStudioActions](Action * a) {
        if (a && a->getExtensionName() == "Developer Studio") {
            devStudioActions.insert(a->getName(), a);
        }
    });

    // check for missing actions
    QStringList requiredDevActionNames = {"New Extension File", "Open Extension File", "Register Extension", "Verify Or Rebuild Extensions On Startup", "Open Extension In IDE", "Unregister Extension", "Rebuild And Reload Extension"};
    missingActions.clear();
    for (QString name : requiredDevActionNames) {
        if (!devStudioActions.contains(name)) {
            missingActions.append(name);
        }
    }
    if (missingActions.size() > 0) {
        CAMITK_INFO(tr("The following mandatory developer studio actions are missing:\n- %1\nThese actions are required to add the \"Dev\" menu. \"Dev\" menu is therefore disabled.").arg(missingActions.join("\n- ")))
    }
    else {
        newExtensionFileAction = devStudioActions["New Extension File"]->getQAction();
        openExtensionFileAction = devStudioActions["Open Extension File"]->getQAction();
        registerExtensionAction = devStudioActions["Register Extension"]->getQAction();
        verifyOrRebuildOnStartup = devStudioActions["Verify Or Rebuild Extensions On Startup"]->getQAction();
    }

    //--- actions of the Help menu
    helpAboutApp = applicationLevelActions["About..."]->getQAction();
    helpShowConsole = applicationLevelActions["Toggle Log Console"]->getQAction();
    changeLanguage = applicationLevelActions["Change Language"]->getQAction();
}

// ------------- initMenuBar -----------------
void ImpMainWindow::initMenuBar() {
    // -- file
    fileMenu = new QMenu(tr("&File"));
    fileMenu->addAction(fileOpen);

    updateOpenDirectoryMenu();

    fileMenu->addAction(fileClose);
    fileMenu->addAction(fileCloseAll);
    fileMenu->addSeparator();
    fileMenu->addAction(fileSave);
    fileMenu->addAction(fileSaveAs);
    fileMenu->addAction(fileSaveAll);
    fileMenu->addAction(workspaceSave);
    fileMenu->addSeparator();
    fileMenu->addAction(saveHistory);
    fileMenu->addAction(recentDocumentSeparator);

    for (QAction* recentAction : recentDocumentActions) {
        fileMenu->addAction(recentAction);
    }

    fileMenu->addSeparator();
    QAction* restartAction = nullptr;
    if (Action* restart = Application::getAction("Restart")) {
        restartAction = restart->getQAction();
    }
    else {
        CAMITK_INFO("Cannot find restart action !")
    }
    if (restartAction) {
        fileMenu->addSeparator();
        restartAction->setText("Restart imp");
        fileMenu->addAction(restartAction);
    }
    fileMenu->addSeparator();
    fileMenu->addAction(fileQuit);

    // -- edit
    editMenu = new QMenu(tr("&Edit"));
    editMenu->addAction(editClearSelection);
    editMenuSeparator = editMenu->addSeparator();
    editMenu->addAction(editApplicationSettings);

    // -- View menu
    viewMenu = new QMenu(tr("&View"));

    // -- help
    helpMenu = new QMenu(tr("&Help"));
    QAction* whatsThisAction = QWhatsThis::createAction(this);
    whatsThisAction->setStatusTip(tr("What's This and Viewer Keyboard Shortcuts."));
    whatsThisAction->setWhatsThis(tr("What's This and Viewer Keyboard Shortcuts."));
    helpMenu->addAction(whatsThisAction);
    this->addAction(whatsThisAction);
    helpMenu->addSeparator();
    helpMenu->addAction(helpShowConsole);
    helpMenu->addAction(Application::getAction("Logger Parameters")->getQAction());
    helpMenu->addSeparator();
    helpMenu->addAction(changeLanguage);
    helpMenuSeparator = helpMenu->addSeparator();
    helpMenu->addAction(helpAboutApp);

    // -- action
    actionMenu = new QMenu(tr("&Actions"));

    // -- Dev menu
    devMenu = new QMenu(tr("&Dev"));

    // -- add everything in the menu bar
    menuBar()->addMenu(fileMenu);
    menuBar()->addMenu(editMenu);
    menuBar()->addMenu(viewMenu);
    menuBar()->addMenu(actionMenu);
    menuBar()->addMenu(devMenu);
    menuBar()->addSeparator();
    menuBar()->addMenu(helpMenu);
}

// ------------------------ updateActionStates ----------------------------
void ImpMainWindow::updateActionStates() {
    unsigned int nrOfSelectedItems = Application::getSelectedComponents().size();
    bool selectedIsTopLevel = (nrOfSelectedItems > 0 && Application::getSelectedComponents().last()->isTopLevel());
    unsigned int nrOfComponents = Application::getTopLevelComponents().size();

    //-- update file menu
    fileCloseAll->setEnabled(nrOfComponents > 0);
    fileSaveAll->setEnabled(nrOfComponents > 0);
    fileSave->setEnabled(selectedIsTopLevel && Application::getSelectedComponents().first()->getTopLevelComponent()->getModified());    // save available only if needed
    fileSaveAs->setEnabled(nrOfSelectedItems > 0);     // no need to be top level to be saved as in a compatible format
    fileClose->setEnabled(selectedIsTopLevel);

    //-- update edit menu
    editClearSelection->setEnabled(nrOfSelectedItems > 0);

    //-- update dev menu
    deferUpdateDevMenu();

    //-- update the action menu
    actionMenu->clear();
    actionMenu->setEnabled(false);

    if (nrOfSelectedItems > 0) {
        // use the selection to populate the menu
        Component* comp = Application::getSelectedComponents().last();

        if (comp) {
            QMenu* compActionsMenu = comp->getActionMenu();

            if (compActionsMenu) {
                actionMenu->addActions(compActionsMenu->actions());
                actionMenu->setEnabled(true);
            }
        }
    }
    else {
        // 1. create a list with all the generic actions using all the action but exclude
        // the actions that are already available in the file, edit, view, dev and help menus.
        // As some extensions are not directly in the view menus, but are inside a submenu,
        // the exclusion algorithm also excludes all actions from a given list of action extensions.
        QStringList actionExtensionToExclude = {"Medical Image Viewer Visibility", "Developer Studio"};
        QList<QMenu*> existingMenus = {fileMenu, editMenu, viewMenu, devMenu, helpMenu};
        ActionList genericActions;
        for (Action* action : Application::getActions(nullptr)) {
            QAction* qAction = action->getQAction();
            bool isInExistingMenu = std::any_of(existingMenus.begin(), existingMenus.end(), [qAction](QMenu * menu) {
                return menu->actions().contains(qAction);
            });
            // do not include actions available in file, edit, dev and help or in the list of exclusion
            if (!isInExistingMenu && !actionExtensionToExclude.contains(action->getExtensionName())) {
                genericActions.append(action);
            }
        }

        // 2. organize by family
        QMap<QString, ActionList> familyMap;
        for (Action* action : genericActions) {
            familyMap[action->getFamily().toLower()].append(action);
        }

        // if the actions are in a family that have the same name as an existing menu, this map
        // defines where it should be inserted/integrated in the menu
        QMap<QMenu*, QAction*> existingMenusInsertBefore = {
            {fileMenu, recentDocumentSeparator},
            {editMenu, editMenuSeparator},
            {viewMenu, viewMenuSeparator},
            {devMenu, devMenuSeparator},
            {helpMenu, helpMenuSeparator}
        };
        //-- create one sub menu per family (unless there is only one action)
        for (ActionList actionInSameFamily : std::as_const(familyMap)) {
            QString actionFamily = actionInSameFamily.first()->getFamily();
            QMenu* actionFamilyMenu = actionMenu;
            QAction* insertBefore = nullptr;

            // Check if the family match an existing menu
            auto it = std::find_if(existingMenus.begin(), existingMenus.end(), [actionFamily](QMenu * menu) {
                // remove the accelerator symbols
                return menu->title().remove('&') == actionFamily;
            });
            if (it != existingMenus.end()) {
                // insert in the existing menu instead
                actionFamilyMenu = (*it);
                insertBefore = existingMenusInsertBefore[actionFamilyMenu];
                actionFamilyMenu->insertSeparator(insertBefore);
            }
            else {
                // create a specific menu if there are more than one actions
                if (actionInSameFamily.size() >= 1) {
                    // Create family menu
                    actionFamilyMenu = actionMenu->addMenu(actionFamily);
                }
            }

            // sort actions by name
            std::sort(actionInSameFamily.begin(), actionInSameFamily.end(), [](Action * a, Action * b) {
                return a->getName() < b->getName();
            });

            // insert all actions in the proper menu
            for (Action* action : actionInSameFamily) {
                // ownership not taken here
                if (insertBefore == nullptr) {
                    actionFamilyMenu->addAction(action->getQAction());
                }
                else {
                    actionFamilyMenu->insertAction(insertBefore, action->getQAction());
                }
            }
        }
        actionMenu->setEnabled(actionMenu->actions().size() > 0);
    }

    // update the application window title
    if (nrOfSelectedItems > 0) {
        setWindowSubtitle(Application::getSelectedComponents().last()->getFileName() + ((Application::getSelectedComponents().last()->getTopLevelComponent()->getModified()) ? "*" : ""));
    }
    else {
        setWindowSubtitle("");
    }
}

// ------------- deferUpdateDevMenu -----------------
void ImpMainWindow::deferUpdateDevMenu() {
    // Defer the menu rebuilding to avoid crashes
    QTimer::singleShot(0, this, &ImpMainWindow::updateDevMenu);
}

// ------------- updateDevMenu -----------------
void ImpMainWindow::updateDevMenu() {
    // Prevent updating dev menu when dev studio actions just have been unloaded
    // (which can happen during close)
    if (!Application::isAlive(Application::getAction("New Extension File"))) {
        return;
    }

    // clear submenu "Registered Extensions" see Qt bug/behaviour https://bugreports.qt.io/browse/QTBUG-63556
    devMenu->clear();

    // if these three are found, we have the devstudio extension
    if (newExtensionFileAction != nullptr && openExtensionFileAction != nullptr && registerExtensionAction != nullptr && verifyOrRebuildOnStartup != nullptr) {
        devMenu->setEnabled(true);
        devMenu->addAction(newExtensionFileAction);
        devMenu->addAction(openExtensionFileAction);
        devMenu->addAction(registerExtensionAction);

        Action* openExtensionFileCamiTKAction = Application::getAction(openExtensionFileAction->text());
        Action* openExtensionInIDEAction = Application::getAction("Open Extension In IDE");
        Action* unregisterExtensionAction = Application::getAction("Unregister Extension");
        Action* rebuildAndReloadExtensionAction = Application::getAction("Rebuild And Reload Extension");

        // remove from the action menu
        actionMenu->removeAction(newExtensionFileAction);
        actionMenu->removeAction(openExtensionFileAction);
        actionMenu->removeAction(registerExtensionAction);
        actionMenu->removeAction(openExtensionInIDEAction->getQAction());
        actionMenu->removeAction(unregisterExtensionAction->getQAction());
        actionMenu->removeAction(rebuildAndReloadExtensionAction->getQAction());
        actionMenu->removeAction(verifyOrRebuildOnStartup);

        devMenuSeparator = devMenu->addSeparator();

        if (HotPlugExtensionManager::getRegisteredExtensionFiles().size() > 0) {
            for (auto actionExtension : HotPlugExtensionManager::getLoadedExtensions()) {
                QMenu* extensionMenu = devMenu->addMenu(actionExtension->getName());
                // set status icon
                extensionMenu->setIcon(this->style()->standardIcon((actionExtension->isSuccessfullyLoaded() ? QStyle::SP_DialogApplyButton : QStyle::SP_DialogCancelButton)));

                QAction* editExtension = extensionMenu->addAction("Edit Extension File");
                QString tipString = openExtensionFileCamiTKAction->getDescription();
                editExtension->setStatusTip(tr(tipString.toStdString().c_str()));
                editExtension->setWhatsThis(tr(tipString.toStdString().c_str()));
                connect(editExtension, &QAction::triggered, this, [ = ](bool) {
                    openExtensionFileCamiTKAction->setParameterValue("CamiTK File", actionExtension->getLocation());
                    openExtensionFileCamiTKAction->trigger();
                });

                if (openExtensionInIDEAction) {
                    QAction* openInIDE = extensionMenu->addAction("Open In IDE");
                    QString tipString = openExtensionInIDEAction->getDescription();
                    openInIDE->setStatusTip(tr(tipString.toStdString().c_str()));
                    openInIDE->setWhatsThis(tr(tipString.toStdString().c_str()));
                    connect(openInIDE, &QAction::triggered, this, [ = ](bool) {
                        openExtensionInIDEAction->setParameterValue("CamiTK File", actionExtension->getLocation());
                        openExtensionInIDEAction->trigger();
                    });
                }

                if (unregisterExtensionAction) {
                    QAction* unregisterExtension = extensionMenu->addAction("Unregister");
                    QString tipString = unregisterExtensionAction->getDescription();
                    unregisterExtension->setStatusTip(tr(tipString.toStdString().c_str()));
                    unregisterExtension->setWhatsThis(tr(tipString.toStdString().c_str()));
                    connect(unregisterExtension, &QAction::triggered, this, [ = ](bool) {
                        unregisterExtensionAction->setParameterValue("CamiTK File", actionExtension->getLocation());
                        unregisterExtensionAction->trigger();
                    });
                }

                if (rebuildAndReloadExtensionAction) {
                    QAction* rebuildAndReloadExtension = extensionMenu->addAction(rebuildAndReloadExtensionAction->getName());
                    QString tipString = rebuildAndReloadExtensionAction->getDescription();
                    rebuildAndReloadExtension->setStatusTip(tr(tipString.toStdString().c_str()));
                    rebuildAndReloadExtension->setWhatsThis(tr(tipString.toStdString().c_str()));
                    if (actionExtension->getProgrammingLanguage() == "Python") {
                        rebuildAndReloadExtension->setText("Verify Requirements");
                    }
                    else {
                        rebuildAndReloadExtension->setText("Rebuild From Source");
                    }

                    connect(rebuildAndReloadExtension, &QAction::triggered, this, [ = ](bool) {
                        rebuildAndReloadExtensionAction->setParameterValue("CamiTK File", actionExtension->getLocation());
                        rebuildAndReloadExtensionAction->trigger();
                    });
                }
            }
        }

        devMenu->addSeparator();
        devMenu->addAction(verifyOrRebuildOnStartup);
    }
    else {
        devMenu->setEnabled(false);
    }
}

// ------------- toggleMenuBar -----------------
void ImpMainWindow::toggleMenuBar() {
    bool menuBarVisible = menuBar()->isVisible();

    if (menuBarVisible) {
        // warn the user first
        CAMITK_WARNING(tr("Hide menu: the menu is going to be hidden. There is only one way to make it reappear: you need to press CTRL+M again."))
    }

    menuBar()->setVisible(!menuBarVisible);
    viewMenuBar->blockSignals(true);
    viewMenuBar->setChecked(!menuBarVisible);
    viewMenuBar->blockSignals(false);
}

// ------------- showStatusBar -----------------
void ImpMainWindow::showStatusBar(bool b) {
    MainWindow::showStatusBar(b);
}

// ------------------------ resetWindows ----------------------------
void ImpMainWindow::resetWindows() {
    for (QMap<Viewer*, QDockWidget*>::iterator it = dockWidgetMap.begin(); it != dockWidgetMap.end(); ++it) {
        if (it.key()->getName() == "Component Explorer") {
            removeDockWidget(it.value());
            addDockWidget(Qt::LeftDockWidgetArea, it.value());
            it.value()->show();
        }
        else {
            if (it.key()->getName() == "Transformation Explorer") {
                removeDockWidget(it.value());
                addDockWidget(Qt::LeftDockWidgetArea, it.value());
                it.value()->show();
            }
            else {
                if (it.key()->getName() == "Property Explorer") {
                    removeDockWidget(it.value());
                    addDockWidget(Qt::LeftDockWidgetArea, it.value());
                    it.value()->show();
                }
                else {
                    if (it.key()->getName() == "Action Viewer") {
                        removeDockWidget(it.value());
                        addDockWidget(Qt::RightDockWidgetArea, it.value());
                        it.value()->show();
                    }
                }
            }
        }
    }

    // Merge ComponentExplorer and TransformationExplorer viewers in one layout
    QDockWidget* dockWidgetComponentExplorer = dockWidgetMap.value(Application::getViewer("Component Explorer"), nullptr);
    QDockWidget* dockWidgetTransformationExplorer = dockWidgetMap.value(Application::getViewer("Transformation Explorer"), nullptr);

    if (dockWidgetComponentExplorer != nullptr && dockWidgetTransformationExplorer != nullptr) {
        tabifyDockWidget(dockWidgetComponentExplorer, dockWidgetTransformationExplorer);
        dockWidgetComponentExplorer->raise();
    }

    showStatusBar(true);
    menuBar()->setVisible(true);

    // reset geometry to default
    resize(1024, 768);
    move(0, 0);
}

// ------------- openDataDirectory -----------------
void ImpMainWindow::openDataDirectory(QString plugin) {
    QString pluginName = plugin;

    statusBar()->showMessage(tr(QString("Opening " + pluginName + " directory...").toStdString().c_str()));

    // Open more than one file
    QString dir = QFileDialog::getExistingDirectory(this, tr(QString("Choose a " + pluginName + " directory ").toStdString().c_str()), Application::getLastUsedDirectory().absolutePath());

    if (!dir.isEmpty()) {
        // set waiting cursor
        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

        // instantiate a Component to represent the data contained in the directory
        Component* comp = Application::openDirectory(dir, pluginName);

        if (!comp) {
            statusBar()->showMessage(tr("Error loading directory:") + dir);
        }
        else {
            // TODO add to recent document and manage directories as recent documents
            statusBar()->showMessage(tr(QString("Directory " + dir + " successfully loaded").toStdString().c_str()));

            // refresh all
            refresh();
        }

        // restore the normal cursor
        QApplication::restoreOverrideCursor();
        getProgressBar()->setValue(0.0);
    }
}

// ------------- updateViewMenu -----------------
void ImpMainWindow::updateViewMenu() {
    // From Qt documentation:
    // "Removes all the menu's actions. Actions owned by the menu and not shown in any other widget are deleted"
    // this will therefore automatically delete the toggle central viewer's action
    viewMenu->clear();

    // insert viewers menu
    for (Viewer* v : viewers) {
        QMenu* viewerMenu = v->getMenu();

        if (viewerMenu) {
            viewMenu->addMenu(viewerMenu);
        }
    }

    viewMenuSeparator = viewMenu->addSeparator();

    QMenu* toggleCentralWidgetMenu = viewMenu->addMenu(tr("Toggle Central Viewer"));

    // add other viewers that are not initially part of imp, but that are
    // added by supplementary viewer extensions
    for (Viewer* viewer : Application::getViewers()) {
        // only control the embedded viewer that are not already embedded somewhere or is already in the centralViewer stack
        if (viewer->getType() == Viewer::EMBEDDED && (viewer->getEmbedder() == centralWidget()->layout() || viewer->getEmbedder() == nullptr)) {
            // viewerAction is own by viewMenu, viewMenu->clear() will delete it
            QAction* viewerAction = toggleCentralWidgetMenu->addAction(viewer->getName());
            viewerAction->setCheckable(true);
            viewerAction->setChecked(viewer == centralViewer);
            viewerAction->setIcon(viewer->getIcon());
            QString tipString = "Hide/show the " + viewer->getName() + " in the central viewer";
            viewerAction->setStatusTip(tr(tipString.toStdString().c_str()));
            viewerAction->setWhatsThis(tr(tipString.toStdString().c_str()));
            // add the toggle action slot using C++11 lambda so that everything is contained inside viewMenu
            connect(viewerAction, &QAction::toggled, this, [ this, viewer ](bool) {
                setCentralViewer(viewer);
            });
        }
    }

    // insert viewers on/off actions
    QMenu* toggleDockedMenu = viewMenu->addMenu(tr("Toggle Docked Viewers"));

    toggleDockedMenu->addAction(viewResetWindows);

    for (Viewer* viewer : Application::getViewers()) {
        // add the
        if (viewer->getType() == Viewer::DOCKED) {
            QDockWidget* dockWidget = dockWidgetMap.value(viewer);

            if (dockWidget != nullptr) {
                dockWidget->toggleViewAction()->setIcon(viewer->getIcon());
                toggleDockedMenu->addAction(dockWidget->toggleViewAction());
            }
            else {
                // add a specific action to add this viewer in the right dock area
                // viewerAction is own by viewMenu, viewMenu->clear() will delete it
                QAction* viewerDockAction = toggleDockedMenu->addAction(viewer->getName());
                viewerDockAction->setCheckable(true);
                viewerDockAction->setChecked(false);
                viewerDockAction->setIcon(viewer->getIcon());
                QString tipString = "Hide/show the " + viewer->getName() + " inside a dock";
                viewerDockAction->setStatusTip(tr(tipString.toStdString().c_str()));
                viewerDockAction->setWhatsThis(tr(tipString.toStdString().c_str()));
                // add the toggle action slot using C++11 lambda so that everything is contained inside viewMenu
                connect(viewerDockAction, &QAction::toggled, this, [ this, viewer ](bool) {
                    addDockViewer(Qt::RightDockWidgetArea, viewer);
                });
            }
        }
    }

    // insert generic on/off actions
    QMenu* otherToggleMenu = viewMenu->addMenu(tr("Other Toggle"));
    otherToggleMenu->addAction(viewMenuBar);
    otherToggleMenu->addAction(viewStatusBar);

    updateActionStates();
}

// ------------------------------ slotEditSettings -------------------------------
void ImpMainWindow::editSettings() {
    statusBar()->showMessage(tr("Edit application preferences..."));
    // ask the ImpMainWindowDoc to show the settings

    // create the dialog
    SettingsDialog settingsDialog;

    // show the properties of the CamiTK application
    settingsDialog.editSettings(dynamic_cast<Application*>(qApp)->getPropertyObject());

    // edit all viewers that have properties
    for (Viewer* v : viewers) {
        QObject* viewerProp = v->getPropertyObject();

        if (viewerProp) {
            settingsDialog.editSettings(viewerProp);
        }
    }

    // execute the dialog
    if (settingsDialog.exec() == QDialog::Accepted) {
        // update recent docs
        updateRecentDocumentsMenu();
    }

    // check if there are anything to change in the data directory menu (for data directory manager)
    ImpMainWindow::updateOpenDirectoryMenu();
}

// ------------- setApplicationConsole -----------------
void ImpMainWindow::redirectToConsole(bool visible) {
    MainWindow::redirectToConsole(visible);
    helpShowConsole->setEnabled(visible);
}


// ------------------------------ slotRecentDocuments -------------------------------
void ImpMainWindow::openRecentDocuments() {
    QAction* action = qobject_cast<QAction*> (sender());

    if (action) {
        // check if this recent document still exists!
        if (QFileInfo::exists(action->data().toString())) {
            Action* openFileAction = Application::getAction("Open File");
            openFileAction->setParameterValue("File Name", action->data().toString()); // avoid opening a dialog, as the file path is already know
            openFileAction->applyAndRegister();
        }
        else {
            CAMITK_WARNING(tr("Open Recent Documents: document \"%1\" does not exist anymore.").arg(action->data().toString()))
        }
    }

}

// ------------------------------ updateRecentDocumentsMenu -------------------------------
void ImpMainWindow::updateRecentDocumentsMenu() {
    const QList<QFileInfo>& recentDocuments = Application::getRecentDocuments();
    recentDocumentSeparator->setVisible(recentDocuments.size() > 0);

    int id = 0;

    for (int i = recentDocuments.size() - 1; i >= 0 && id < Application::getMaxRecentDocuments(); i--, id++) {
        QString text = tr("&%1 %2").arg(id + 1).arg(recentDocuments[i].fileName());
        recentDocumentActions[id]->setText(text);
        recentDocumentActions[id]->setData(recentDocuments[i].absoluteFilePath());
        recentDocumentActions[id]->setStatusTip(recentDocuments[i].absoluteFilePath());
        recentDocumentActions[id]->setWhatsThis(recentDocuments[i].absoluteFilePath());
        recentDocumentActions[id]->setVisible(true);
    }

    for (; id < recentDocumentActions.size(); id++) {
        recentDocumentActions[id]->setVisible(false);
    }
}

// ------------------------------ saveHistoryAsSCXML -------------------------------
void ImpMainWindow::saveHistoryAsSCXML() {
    Application::saveHistoryAsSCXML();
}

// ------------------------- updateOpenDirectoryMenu ---------------------------
void ImpMainWindow::updateOpenDirectoryMenu() {
    QStringList dirExt = ExtensionManager::getDataDirectoryExtNames();

    if (dirExt.size() > 1) {
        if (fileOpenDataDirectoryMenu == nullptr) {
            fileOpenDataDirectoryMenu = new QMenu("Open Data Directory");
            fileMenu->addMenu(fileOpenDataDirectoryMenu);
        }
        else {
            disconnect(fileOpenDataDirectoryMenu, 0, 0, 0);
            fileOpenDataDirectoryMenu->clear();
        }

        fileOpenDataDirectoryMenu->setIcon(QPixmap(":/fileOpen"));

        for (auto& dirExtName : std::as_const(dirExt)) {
            QAction* openDirectory = new QAction(dirExtName, this);
            openDirectory->setStatusTip(tr(QString("Opens data directory for " + dirExtName).toStdString().c_str()));
            openDirectory->setWhatsThis(tr(QString("Opens data directory for " + dirExtName).toStdString().c_str()));
            connect(openDirectory, &QAction::triggered, this, [ this, dirExtName ]() {
                this->openDataDirectory(dirExtName);
            });

            fileOpenDataDirectoryMenu->addAction(openDirectory);

        }
    }
    else if (dirExt.size() == 1) {
        // if it exists, remove the default directory extension menu (that has only one submenu)
        // from the file "open" action list
        bool updateAction = false;
        QList<QAction*> menuActions = fileMenu->actions();
        int indexAction = 0;

        while (indexAction < menuActions.size() && !updateAction) {
            if (menuActions.at(indexAction)->text().contains(" Directory")) {
                emit menuActions.at(indexAction)->destroyed();
                updateAction = true;
            }

            indexAction++;
        }

        // create the single open directory action
        QString dirExtName = dirExt.at(0);
        QAction* openDirectoryAction = new QAction("Open " + dirExtName + " Directory", this);

        if (!updateAction) {
            // there was no specific menu, just add the single "open directory" action in the file menu
            fileMenu->addAction(openDirectoryAction);
        }

        openDirectoryAction->setStatusTip(tr(QString("Opens data directory for " + dirExtName).toStdString().c_str()));
        openDirectoryAction->setWhatsThis(tr(QString("Opens data directory for " + dirExtName).toStdString().c_str()));

        connect(openDirectoryAction, &QAction::triggered, this, [ this, dirExtName ]() {
            this->openDataDirectory(dirExtName);
        });

        openDirectoryAction->setIcon(QPixmap(":/fileOpen"));
    }
    else {
        fileOpenDataDirectoryMenu = new QMenu("No plugins loaded to Open Data Directory");
        fileOpenDataDirectoryMenu->setIcon(QPixmap(":/fileOpen"));
        fileOpenDataDirectoryMenu->setEnabled(false);
        fileMenu->addMenu(fileOpenDataDirectoryMenu);
    }
}
