/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2016 Univ. Grenoble Alpes, CNRS, TIMC-IMAG UMR 5525 (GMCAO)
 *
 * 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 "Application.h"
#include "Core.h"
#include "Log.h"
#include "MainWindow.h"
#include "ExtensionManager.h"
#include "Action.h"
#include "HistoryComponent.h"
#include "ImageComponent.h"
#include "Property.h"
#include "PropertyObject.h"

// -- QT stuff
#include <QSettings>
#include <QMessageBox>
#include <QFileDialog>
#include <QDomDocument>
#include <QTextStream>

// -- VTK stuff (only for updating progress bar via vtk filters)
#include <vtkAlgorithm.h>

// see http://doc.qt.nokia.com/latest/resources.html (bottom)
inline void initIcons() {
    Q_INIT_RESOURCE(CamiTKIcons);
}

namespace camitk {

// the main window (static, unique instance, verifies singleton)
MainWindow * Application::mainWindow = NULL;
QSettings Application::settings(QSettings::IniFormat, QSettings::UserScope, "CamiTK", QString(Core::version).remove(QChar(' ')));
QList<QFileInfo> Application::recentDocuments;
QDir Application::lastUsedDirectory;
int Application::maxRecentDocuments = 0;
QString Application::name = Core::version;

// ----------------- constructor --------------------
Application::Application(QString name, int & theArgc, char ** theArgv, bool autoloadExtensions, bool registerFileExtension) : QApplication(theArgc, theArgv) {
    this->name = name;
    QApplication::setApplicationName(name);    // associate the QProperty to the Qt meta-object

    argc = theArgc;
    argv = theArgv;
    mainWindow = NULL;

    initIcons(); // sometimes needed to get the icon when compiled in static mode

    // recommended (see exec() comment in QApplication)
    connect(this, SIGNAL(aboutToQuit()), SLOT(quitting()));
    connect(this, SIGNAL(lastWindowClosed()), SLOT(quitting()));

    // init log file if needed
#ifdef LOG_IN_FILE
    Log::openLogFile();
#endif

    // once the log is setup, only now one can try to load the extensions
    if (autoloadExtensions) {
        ExtensionManager::autoload();
    }

    // initialize recent/lastUsedDirectory documents from the settings
    settings.beginGroup(name + ".Application");

    // max memorized recent documents
    maxRecentDocuments = settings.value("maxRecentDocuments", 10).toInt();

    // the recent documents
    QStringList recentDoc = settings.value("recentDocuments").toStringList();
    recentDocuments.clear();

    foreach (QString fileName, recentDoc) {
        recentDocuments.append(fileName);
    }

    // the last used directory
    QDir defaultDir(Core::getTestDataDir());
    lastUsedDirectory = settings.value("lastUsedDirectory", defaultDir.absolutePath()).toString();


    // register file association with this application for opening
    if (registerFileExtension) {
        // TODO : remove ifdef WIN32 as soon as we handle file association for other platform than Windows
        // see ExtensionManager::registerFileExtension method.
#ifdef WIN32
        // File types association with the application for opening
        QStringList newFileExtensions;
        QStringList fileExtensionsAlreadyRegistered = settings.value("fileExtensionsRegistered").toStringList();
        // Forbidden list, to avoid user to have common image file type associated with the application at opening
        // TODO : uses a better prompt for user to select each component he wants to register with the application
        QStringList fileExtensionForbidden;
        fileExtensionForbidden.append("jpg");
        fileExtensionForbidden.append("png");
        fileExtensionForbidden.append("bmp");
        fileExtensionForbidden.append("tif");
        fileExtensionForbidden.append("tiff");

        foreach (QString extensionFile, ExtensionManager::getFileExtensions()) {
            // check the application can handle new file type
            if (!fileExtensionsAlreadyRegistered.contains(extensionFile) && !fileExtensionForbidden.contains(extensionFile)) {
                newFileExtensions.append(extensionFile);
            }
        }

        // check if the application handles new file types
        if (!newFileExtensions.isEmpty()) {
            // new component extension(s) allow(s) the application to handle new file types
            // prompt the user if he wants them to be associated with this application for opening
            if (ExtensionManager::promptRegisterFileExtensions(newFileExtensions)) {
                // user agrees : register each new file type
                foreach (QString fileExtensionToRegister, newFileExtensions) {
                    ExtensionManager::registerFileExtension(fileExtensionToRegister);
                }
            }

            // save the file types in the application's settings in order not to be prompt again
            fileExtensionsAlreadyRegistered.append(newFileExtensions);
            settings.setValue("fileExtensionsRegistered", fileExtensionsAlreadyRegistered);
        }

#endif
    }

    settings.endGroup();

    // Load the resources of the application (mainly its translation file)
    initResources();

    // Add application properties
    createProperties();
}

// ----------------- destructor --------------------
Application::~Application() {
    // do not use the destructor to clean or free resources, but quitting()

    if(translator)
        delete translator; //delete instance of internationalization support

    // delete property object and all its properties !
    if(propertyObject)
        delete propertyObject;

}

// ----------------- destructor --------------------
QString Application::getName() {
    return name;
}

// ----------------- quitting --------------------
void Application::quitting() {
    // this is connect to the aboutToQuit signal from QApplication
    // it should contain all the code that frees the resources

    // delete all actions (they are instanciated when the extension is loaded)
    ExtensionManager::unloadAllActionExtensions();

#ifdef LOG_IN_FILE
    Log::closeLogFile();
#endif
}

// ----------------- setMainWindow --------------------
void Application::setMainWindow(MainWindow * mw) {
    if (mw == NULL) {
        mainWindow = new MainWindow(name);
    } else {
        mainWindow = mw;
    }

    // by default redirect to console
    mainWindow->redirectToConsole(true);

    // Set the locale to C for using dot as decimal point dispite locale
    // Set utf8 for output to enforce using utf8 strings.
    //
    // see http://doc.qt.io/qt-5/qcoreapplication.html#locale-settings
    // and various threads or forum discussions such as http://stackoverflow.com/questions/25661295/why-does-qcoreapplication-call-setlocalelc-all-by-default-on-unix-linux
    char * statusOk = setlocale(LC_CTYPE, "C.UTF-8");

    if (statusOk != NULL) {
        statusOk = setlocale(LC_NUMERIC, "C.UTF-8");
    }

    if (statusOk != NULL) {
        statusOk = setlocale(LC_TIME, "C.UTF-8");
    }

    // try without UTF-8
    if (!statusOk) {
        statusOk = setlocale(LC_CTYPE, "C");

        if (statusOk != NULL) {
            statusOk = setlocale(LC_NUMERIC, "C");
        }

        if (statusOk != NULL) {
            statusOk = setlocale(LC_TIME, "C");
        }

        if (statusOk == NULL) {
            CAMITK_ERROR("Application", "setMainWindow (initialization)", "Could not set the locale to C. This is mandatory to enforce using dot as decimal separator (platform independency)." << std::endl
                         << "This can cause a lot of trouble for numerics I/O... Beware of decimal dots..." << std::endl);
        }
    }
}


// ----------------- getMainWindow --------------------
MainWindow * Application::getMainWindow() {
    if (!mainWindow) {
        dynamic_cast<Application *>(qApp)->setMainWindow(NULL);
    }

    return mainWindow;
}

// ----------------- getSettings --------------------
QSettings & Application::getSettings() {
    return settings;
}

// ----------------- exec --------------------
int Application::exec() {
    if (!mainWindow) {
        dynamic_cast<Application *>(qApp)->setMainWindow(NULL);
    }

    mainWindow->aboutToShow();
    mainWindow->show();

    return QApplication::exec();
}

// ----------------- refresh --------------------
void Application::refresh() {
    mainWindow->refresh();
}

// ----------------- showStatusBarMessage --------------------
void Application::showStatusBarMessage(QString msg, int timeout) {
    QStatusBar * statusBar = mainWindow->statusBar();

    if (statusBar) {
        statusBar->showMessage(msg, timeout);
    } else {
        CAMITK_INFO("Application", "showStatusBarMessage", msg.toStdString() << std::endl);
    }
}

// ----------------- resetProgressBar --------------------
void Application::resetProgressBar() {
    QProgressBar * progress = mainWindow->getProgressBar();

    if (progress) {
        progress->setValue(0);
    }
}

// ----------------- setProgressBarValue --------------------
void Application::setProgressBarValue(int value) {
    QProgressBar * progress = mainWindow->getProgressBar();

    if (progress) {
        progress->setValue(value);
    }

}

// ----------------- vtkProgressFunction --------------------
void Application::vtkProgressFunction(vtkObject * caller, long unsigned int eventId, void * clientData, void * callData) {
    QProgressBar * progress = mainWindow->getProgressBar();
    vtkAlgorithm * filter = static_cast<vtkAlgorithm *>(caller);
    int progressVal = filter->GetProgress() * 100;

    if (progress) {
        progress->setValue(progressVal);
    }
}

// ----------------- addRecentDocument --------------------
void Application::addRecentDocument(QFileInfo filename) {
    recentDocuments.removeOne(filename);
    recentDocuments.append(filename);

    // update the last used dir
    lastUsedDirectory = recentDocuments.last().absoluteDir();

    // save settings (the last 10 recent files by default)
    settings.beginGroup(name + ".Application");

    // max memorized recent documents
    settings.setValue("maxRecentDocuments", maxRecentDocuments);

    // save all up to maxRecentDocuments
    int firstOpened = recentDocuments.size() - maxRecentDocuments;

    if (firstOpened < 0) {
        firstOpened = 0;
    }

    QStringList recentDoc;

    for (int i = firstOpened; i < recentDocuments.size(); i++) {
        recentDoc.append(recentDocuments[i].absoluteFilePath());
    }

    settings.setValue("recentDocuments", recentDoc);

    // last used directory
    settings.setValue("lastUsedDirectory", lastUsedDirectory.absolutePath());
    settings.endGroup();
}

// ----------------- getLastUsedDirectory --------------------
const QDir Application::getLastUsedDirectory() {
    return lastUsedDirectory;
}

// ----------------- setLastUsedDirectory --------------------
void Application::setLastUsedDirectory(QDir last) {
    lastUsedDirectory = last;
}

// ----------------- getRecentDocuments --------------------
const QList< QFileInfo > Application::getRecentDocuments() {
    return recentDocuments;
}

// ----------------- getMaxRecentDocuments --------------------
const int Application::getMaxRecentDocuments() {
    return maxRecentDocuments;
}

// -------------------- open --------------------
Component * Application::open(const QString & fileName) {
    // set waiting cursor
    setOverrideCursor(QCursor(Qt::WaitCursor));

    Component * comp = NULL;

    // -- Get the corresponding extension... (compatible format)
    QString extension = QFileInfo(fileName).suffix();

    // -- ask the plugin instance to create the Component instance
    ComponentExtension * cp = ExtensionManager::getComponentExtensions().value(extension);
    QString ext;

    // -- file may be compressed so we check the complete extension
    if (!cp) {
        // the complete base name consists of all characters in the file up to (but not including) the last '.' character.
        QString baseName = QFileInfo(fileName).completeBaseName();
        ext = QFileInfo(baseName).suffix();
        extension = ext + "." + extension;
        cp = ExtensionManager::getComponentExtensions().value(extension);
    }

    // -- check the validity of the plugin
    if (!cp) {
        // restore the normal cursor/progress bar
        restoreOverrideCursor();
        resetProgressBar();

        QMessageBox::warning(NULL, "ComponentExtension Opening Error...", tr("Cannot find the appropriate component plugin for opening:<br>") + fileName + tr(" (extension ") + extension + tr(")<p>To solve this problem, make sure that: <ul><li>A corresponding valid plugin is present in ") + Core::getComponentDir() + tr("</li><li>Your application called <tt>ComponentExtension::autoloadExtensions()</tt> before calling <tt>ComponentExtension::open(...)</tt></li></ul>"));
    } else {

        // -- ask the plugin to create the top level component
        try {
            // open using transformed path so that anything looking like C:\\Dir1\\Dir2\\foo.zzz is unified to a C:/Dir1/Dir2/foo.zz
            comp = cp->open(QFileInfo(fileName).absoluteFilePath());

            // -- notify the application
            if (comp == NULL) {
                showStatusBarMessage(tr("Error loading file:") + fileName);
            } else {
                // add the document to the recent list
                addRecentDocument(fileName);
                showStatusBarMessage(tr(QString("File " + fileName + " successfully loaded").toStdString().c_str()));

                if (!comp->isTopLevel()) {
                    CAMITK_WARNING("Application", "Open", "Instanciating a NON top level component.")
                }

                // refresh all viewers
                refresh();
            }

        } catch
            (AbortException & e) {
            // restore the normal cursor/progress bar
            restoreOverrideCursor();
            resetProgressBar();
            QMessageBox::warning(NULL, "Opening aborted...", tr("Extension: ") + cp->getName() + tr("<br>Error: cannot open file ") + fileName + tr("<br>Reason:<br>") + e.what());
            comp = NULL;
        } catch
            (std::exception & e) {
            // restore the normal cursor/progress bar
            restoreOverrideCursor();
            resetProgressBar();
            QMessageBox::warning(NULL, "Opening aborted...", tr("Extension ") + cp->getName() + tr("<br>Error: cannot open file ") + fileName + tr("<br>External:<br>") + tr("This exception was not generated directly by the extension,<br>but by one of its dependency.<br>Reason:<br>") + e.what());
            comp = NULL;
        } catch
            (...) {
            // restore the normal cursor/progress bar
            restoreOverrideCursor();
            resetProgressBar();
            QMessageBox::warning(NULL, "Opening aborted...", tr("Extension ") + cp->getName() + tr("<br>Error: cannot open file ") + fileName + tr("<br>Unknown Reason:<br>") + tr("This exception was not generated directly by the extension,<br>but by one of its dependency."));
            comp = NULL;
        }
    }

    // restore the normal cursor/progress bar
    restoreOverrideCursor();
    resetProgressBar();

    return comp;
}

// -------------------- openDirectory --------------------
Component * Application::openDirectory(const QString & dirName, const QString & pluginName) {
    // set waiting cursor
    setOverrideCursor(QCursor(Qt::WaitCursor));

    Component * comp = NULL;

    ComponentExtension * cp = ExtensionManager::getDataDirectoryComponents().value(pluginName);

    if (cp != NULL) {
        // Ask the plugin instance to create the Component instance
        try {
            comp = cp->open(QDir(dirName).absolutePath());

            // -- notify the application
            if (comp == NULL) {
                showStatusBarMessage(tr("Error loading directory:") + dirName);
            } else {
                showStatusBarMessage(tr(QString("Directory " + dirName + " successfully loaded").toStdString().c_str()));
                // refresh all viewers
                refresh();
            }
        } catch
            (AbortException & e) {
            // restore the normal cursor/progress bar
            restoreOverrideCursor();
            resetProgressBar();
            QMessageBox::warning(NULL, "Opening aborted...", tr("Extension: ") + cp->getName() + tr("<br>Error: cannot open file ") + dirName + tr("<br>Reason:<br>") + e.what());
            comp = NULL;
        } catch
            (...) {
            // restore the normal cursor/progress bar
            restoreOverrideCursor();
            resetProgressBar();
            QMessageBox::warning(NULL, "Opening aborted...", tr("Extension ") + cp->getName() + tr(" Error: cannot open directory ") + dirName + tr("<br>Unknown Reason:<br>") + tr("This exception was not generated directly by the extension,<br>but by one of its dependency."));
            delete comp;
            comp = NULL;
        }
    } else {
        // restore the normal cursor/progress bar
        restoreOverrideCursor();
        resetProgressBar();
        QMessageBox::warning(NULL, "ComponentExtension Opening Error...", tr("Cannot find the appropriate component plugin for opening directory:<br>") + dirName
                             + tr("<p>To solve this problem, make sure that:<ul>")
                             + tr(" <li>A corresponding valid plugin is present in one of the component directories: <tt>") + Core::getComponentDirectories().join(", ") + "</tt></li>"
                             + tr("<li>And your application is initialized with the autoloadExtensions option;</li>")
                             + tr(" <li>Or your correctly registered your component in the CamiTK settings</li></ul>"));
    }

    // restore the normal cursor/progress bar
    restoreOverrideCursor();
    resetProgressBar();


    return comp;
}

// -------------------------- close ------------------------------
bool Application::close(Component * comp) {
    int keyPressed = QMessageBox::Discard;
    bool saveOk = true;
    QString compName = comp->getName();

    // check if the top-level component needs to be saved
    if (comp->getModified()) {
        // dManager has changed, propose to save it
        keyPressed = QMessageBox::warning(NULL, "Closing...", tr("Component \"") + compName + tr("\" has been modified.\nDo you want to save your change before closing?"), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Save);

        // Do we have to save or not?
        if (keyPressed == QMessageBox::Save) {
            saveOk = save(comp);
        }
    }

    // Do we have to close or not?
    if (saveOk && keyPressed != QMessageBox::Cancel) {
        // delete the data
        delete comp;
        comp = NULL;

        // refresh all viewers
        refresh();

        showStatusBarMessage(compName + tr(" successfully closed..."));
        return true;
    } else {
        // return that the close was cancelled
        showStatusBarMessage(tr("Close cancelled..."));
        return false;
    }

}

// -------------------- save --------------------
bool Application::save(Component * component) {

    // no name -> save as
    if (component->getFileName().isEmpty()) {
        return (getAction("Save As")->apply() == Action::SUCCESS);
    }

    // Get the saving extension...
    QString extension = QFileInfo(component->getFileName()).suffix();

    // Get the extension instance that can save the component instance (compatible format)
    ComponentExtension * cp = ExtensionManager::getComponentExtensions().value(extension);

    if (!cp) {
        QMessageBox::warning(NULL, "Saving Error...", tr("Cannot find the appropriate component plugin for saving component:<br/>\"") + component->getName() + tr("\" in file:<br/>") + component->getFileName() + tr(" (extension ") + extension + ")"
                             + tr("<p>To solve this problem, make sure that:<ul>")
                             + tr(" <li>A corresponding valid plugin is present in one of the component directories: <tt>") + Core::getComponentDirectories().join(", ") + "</tt></li>"
                             + tr("<li>And your application is initialized with the autoloadExtensions option;</li>")
                             + tr(" <li>Or your correctly registered your component in the CamiTK settings</li></ul>"));
        return false;
    } else { // ready to save

        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

        if (cp->save(component)) {

            // update the last used dir
            setLastUsedDirectory(QFileInfo(component->getFileName()).absoluteDir());
            showStatusBarMessage(component->getName() + tr(" successfully saved..."));

            QApplication::restoreOverrideCursor();
            return true;
        } else {
            QApplication::restoreOverrideCursor();
            return false;
        }
    }
}


// -------------------- getActionMap --------------------
QMap<QString, Action *> & Application::getActionMap() {
    static QMap<QString, Action *> actionMap;

    return actionMap;
}

// -------------------- getActions --------------------
const ActionList Application::getActions() {
    return getActionMap().values();
}

// -------------------- registerAllActions --------------------
int Application::registerAllActions(ActionExtension * ext) {
    int registered = 0;

    foreach (Action * action, ext->getActions()) {
        // check if an action with same name was not already registered
        if (getActionMap().contains(action->getName())) {
            CAMITK_ERROR("Application", "registerAllActions", "Cannot register action: " << action->getName().toStdString()
                         << "(extension: " << action->getExtensionName().toStdString()
                         << ", family: " << action->getFamily().toStdString()
                         << ", description:\"" << action->getDescription().toStdString()
                         << "\"): extension of same name already registered by extension \"" << getAction(action->getName())->getExtensionName().toStdString() << "\"");
        } else {
            getActionMap().insert(action->getName(), action);
            registered++;
        }
    }

    return registered;
}

// -------------------- unregisterAllActions --------------------
int Application::unregisterAllActions(ActionExtension * ext) {
    int registered = 0;

    foreach(Action * action, ext->getActions()) {
        getActionMap().remove(action->getName());
        registered++;
    }

    return registered;
}

// ---------------- actionLessThan ----------------
bool actionLessThan(const camitk::Action * a1, const camitk::Action * a2) {
    // This method is needed by qsort in the sort method to sort action by name
    return a1->getName() < a2->getName();
}

// ---------------- sort ----------------
ActionList Application::sort(ActionSet actionSet) {
    // sort actions by name
    ActionList actionList = actionSet.toList();
    qSort(actionList.begin(), actionList.end(), actionLessThan);

    return actionList;
}

// ---------------- getAction ----------------
Action * Application::getAction(QString name) {
    return getActionMap().value(name);
}

// ---------------- getActions ----------------
ActionList Application::getActions(Component * component) {
    ActionSet actions;

    if (component) {
        QStringList componentHierarchy = component->getHierarchy();

        foreach (Action * currentAct, Application::getActions()) {
            if (componentHierarchy.contains(currentAct->getComponent())) {
                actions.insert(currentAct);
            }
        }
    } else {
        foreach (Action * currentAct, Application::getActions()) {
            if (currentAct->getComponent().isEmpty()) {
                actions.insert(currentAct);
            }
        }
    }

    return sort(actions);
}

// ---------------- getActions ----------------
ActionList Application::getActions(ComponentList cList) {
    // if this is an empty list, return all action not based on a component
    if (cList.size() == 0) {
        return getActions(NULL);
    } else {
        ActionSet actions;

        foreach (Component * currentComp, cList) {
            actions += getActions(currentComp).toSet();
        }

        return sort(actions);
    }
}

// ---------------- getActions ----------------
ActionList Application::getActions(ComponentList selComp, QString tag) {
    // first build the list of possible actions for cList
    ActionList possibleActions = getActions(selComp);

    // now check possibleActions considering the tag value
    ActionList actions;

    foreach (Action * action, possibleActions) {
        if (action->getTag().contains(tag)) {
            actions.append(action);
        }
    }

    // sort and return
    qSort(actions.begin(), actions.end(), actionLessThan);
    return actions;

}

// ---------- isAlive ----------
bool Application::isAlive(Component * comp) {
    return getAllComponents().contains(comp);
}

// -------------------- hasModified --------------------
bool Application::hasModified() {
    // look for a top level component that has been modified
    int i = 0;

    while (i < getTopLevelComponents().size() && !getTopLevelComponents() [i]->getModified()) {
        i++;
    }

    return (i < getTopLevelComponents().size());
}

// -------------------- addComponent --------------------
void Application::addComponent(Component * comp) {
    getAllComponentList().append(comp);

    if (comp->getParentComponent() == NULL)
        // this a top level component
    {
        getTopLevelComponentList().append(comp);
    }
}

// -------------------- removeComponent --------------------
void Application::removeComponent(Component * comp) {
    getAllComponentList().removeAll(comp);
    getTopLevelComponentList().removeAll(comp);
    getSelectedComponentList().removeAll(comp);
}

// -------------------- getTopLevelComponentList --------------------
ComponentList & Application::getTopLevelComponentList() {
    static ComponentList topLevelComponents;

    return topLevelComponents;
}

// -------------------- getAllComponentList --------------------
ComponentList & Application::getAllComponentList() {
    static ComponentList allComponents;

    return allComponents;
}

// -------------------- getSelectedComponentList --------------------
ComponentList & Application::getSelectedComponentList() {
    static ComponentList selectedComponents;

    return selectedComponents;
}

// -------------------- getTopLevelComponents --------------------
const ComponentList & Application::getTopLevelComponents() {
    return getTopLevelComponentList();
}

// -------------------- getAllComponents --------------------
const ComponentList & Application::getAllComponents() {
    return getAllComponentList();
}

// -------------------- getSelectedComponents --------------------
const ComponentList & Application::getSelectedComponents() {
    return getSelectedComponentList();
}

// -------------------- setSelected --------------------
void Application::setSelected(Component * component, bool isSelected) {
    // in case the component is selected again, put it in the end
    // therefore the correct processing is :
    // 1. remove component from the list
    // 2. if isSelected is true, append
    getSelectedComponentList().removeAll(component);

    if (isSelected) {
        getSelectedComponentList().append(component);
    }
}

// -------------------- clearSelectedComponents --------------------
void Application::clearSelectedComponents() {
    foreach (Component * comp, getSelectedComponentList()) {
        comp->setSelected(false);
    }

    getSelectedComponentList().clear();
}

// -------------------- getHistory --------------------
QStack<HistoryItem> & Application::getHistory() {
    // static singleton declaration
    static QStack<HistoryItem> history;
    return history;
}

// -------------------- addHistory ------------------------------
void Application::addHistoryItem(HistoryItem item) {
    getHistory().push(item);
}

// -------------------- removeLastHistoryItem --------------------
HistoryItem Application::removeLastHistoryItem() {
    return getHistory().pop();
}

// -------------------- saveHistoryAsSXML --------------------
void Application::saveHistoryAsSXML() {
    // Empty history => do nothing
    if (Application::getHistory().isEmpty()) {
        cerr << "No history to save, aborting ..." << endl;
        return;
    }

    // Create the XML document
    QDomDocument * doc = new QDomDocument();

    // root element
    QDomElement root = doc->createElement("scxml");
    root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
    root.setAttribute("xmlns", "http://camitk.imag.fr/3/smallScxml");
    root.setAttribute("xmlns:camitk", "http://camitk.imag.fr/3/asm");
    root.setAttribute("xsi:schemaLocation", "http://camitk.imag.fr/3/smallScxml/../resources/smallScxml.xsd");
    root.setAttribute("initial", "Initialize");
    doc->appendChild(root);

    // SXML always contains an Initialize element which describe the pipeline
    QDomElement initializeElt = doc->createElement("state");
    root.appendChild(initializeElt);
    initializeElt.setAttribute("id", "Initialize");
    QDomElement initializeElt_onEntry = doc->createElement("onentry");
    initializeElt.appendChild(initializeElt_onEntry);
    QDomElement initializeElt_onState = doc->createElement("camitk:onState");
    initializeElt_onEntry.appendChild(initializeElt_onState);
    // Description of the pipeline
    QDomElement initializeElt_desc = doc->createElement("camitk:description");
    initializeElt_onState.appendChild(initializeElt_desc);
    // create the pipeline description that will be filled whilel processing the history items.
    QString pipelineDescription = "This pipeline will process several actions on the input component(s): <br /> <ul>";
    // Transition
    QDomElement initializeElt_transition = doc->createElement("transition");
    initializeElt_transition.setAttribute("event", "Next");
    initializeElt_transition.setAttribute("target", "Action 1");
    initializeElt.appendChild(initializeElt_transition);
    QDomElement initializeElt_transition2 = doc->createElement("transition");

    // Create a list of all components created during the pipeline, to know which ones to delete once pipeline is reset
    // by the action state machine
    QList<HistoryComponent> allCreatedComponents;

    // Main loop accross the different actions in the history
    for (int i = 0; i < Application::getHistory().size(); i++) {
        // Get the current history element, i.e. the action of the pipeline with its parameters
        HistoryItem historyItem = Application::getHistory().at(i);
        Action * action = Application::getAction(historyItem.getName());
        // state
        QDomElement stateElement = doc->createElement("state");
        root.appendChild(stateElement);
        stateElement.setAttribute("id", "Action " + QString::number(i + 1));
        // onEntry
        QDomElement onentryElement = doc->createElement("onentry");
        stateElement.appendChild(onentryElement);
        QDomElement onStateElement = doc->createElement("camitk:onState");
        onentryElement.appendChild(onStateElement);

        // action description
        QDomElement descriptionElement = doc->createElement("camitk:description");
        QDomText descriptionText = doc->createTextNode(action->getDescription());
        descriptionElement.appendChild(descriptionText);
        onStateElement.appendChild(descriptionElement);

        // action name & parameters
        QDomElement actionElement = doc->createElement("camitk:action");
        onStateElement.appendChild(actionElement);
        // action name
        QDomElement actionElementName = doc->createElement("camitk:name");
        actionElement.appendChild(actionElementName);
        pipelineDescription.append("<li>" + action->getName() + "</li>");
        QDomText actionElementNameText = doc->createTextNode(action->getName());
        actionElementName.appendChild(actionElementNameText);

        // action parameters
        if (!action->dynamicPropertyNames().isEmpty()) {
            QDomElement parametersElement = doc->createElement("camitk:parameters");
            actionElement.appendChild(parametersElement);

            foreach (QByteArray actionParameter, action->dynamicPropertyNames()) {
                QDomElement parameterElement = doc->createElement("camitk:parameter");
                parametersElement.appendChild(parameterElement);
                parameterElement.setAttribute("name", QString(actionParameter));
                parameterElement.setAttribute("type", QVariant::typeToName(action->property(actionParameter).type()));
                parameterElement.setAttribute("value", action->property(actionParameter.data()).toString());
            }
        }

        QDomElement actionElementParam = doc->createElement("camitk:parameters");
        actionElement.appendChild(actionElementParam);

        // action input components
        if (!historyItem.getInputHistoryComponents().isEmpty()) {
            QDomElement actionElementInputComp = doc->createElement("camitk:inputs");
            actionElement.appendChild(actionElementInputComp);

            for (int j = 0; j < historyItem.getInputHistoryComponents().size(); j++) {
                QDomElement componentElement = doc->createElement("camitk:component");
                // determine the type of each input component (image, meshes or other)
                HistoryComponent inputHistoryComponents = historyItem.getInputHistoryComponents().at(j);

                switch (inputHistoryComponents.getType()) {
                case HistoryComponent::IMAGE_COMPONENT:
                    componentElement.setAttribute("type", "ImageComponent");
                    break;

                case HistoryComponent::MESH_COMPONENT:
                    componentElement.setAttribute("type", "MeshComponent");
                    break;

                case HistoryComponent::OTHER:
                default:
                    componentElement.setAttribute("type", "Other");
                    break;
                }

                // save its name
                componentElement.setAttribute("name", inputHistoryComponents.getName());

                // save the current DOM element in the XML structure
                actionElementInputComp.appendChild(componentElement);
            }
        }

        // action output components
        if (!historyItem.getOutputHistoryComponents().isEmpty()) {
            QDomElement outputsElement = doc->createElement("camitk:outputs");
            actionElement.appendChild(outputsElement);

            for (int j = 0; j < historyItem.getOutputHistoryComponents().size(); j++) {
                QDomElement componentElement = doc->createElement("camitk:component");
                // determine the type of each input component (image, meshes or other)
                HistoryComponent outputHistoryComponents = historyItem.getOutputHistoryComponents().at(j);
                allCreatedComponents.append(outputHistoryComponents);    // note the component created

                switch (outputHistoryComponents.getType()) {
                case HistoryComponent::IMAGE_COMPONENT:
                    componentElement.setAttribute("type", "ImageComponent");
                    break;

                case HistoryComponent::MESH_COMPONENT:
                    componentElement.setAttribute("type", "MeshComponent");
                    break;

                case HistoryComponent::OTHER:
                default:
                    componentElement.setAttribute("type", "Other");
                    break;
                }

                // save its name
                componentElement.setAttribute("name", outputHistoryComponents.getName());

                // save the current DOM element in the XML structure
                outputsElement.appendChild(componentElement);
            }
        }

        // Transitions
        if (Application::getHistory().size() >= 1) {   // else, no transition at all
            // Next transition
            QDomElement nextTransitionElement = doc->createElement("transition");

            if (i == Application::getHistory().size() - 1) {   // Last action element => next element = Bye element.
                nextTransitionElement.setAttribute("target", "Bye");
            } else { // Next generic action state
                nextTransitionElement.setAttribute("target", "Action " + QString::number(i + 2));
            }

            nextTransitionElement.setAttribute("event", "Next");
            stateElement.appendChild(nextTransitionElement);

            // Back transition
            QDomElement backTransitionElement = doc->createElement("transition");
            backTransitionElement.setAttribute("event", "Back");

            if (i == 0) {   // Back to Initialize state
                backTransitionElement.setAttribute("target", "Initialize");
            } else { // Back to previous state and delete optional created component in memory
                backTransitionElement.setAttribute("target", "Action " + QString::number(i));
                // Verify that previsous element has not created components in memory
                HistoryItem previousItem = Application::getHistory().at(i - 1);

                if (!previousItem.getOutputHistoryComponents().isEmpty()) {
                    // Ask for deletion of each component created at the previous element
                    QDomElement onTransitionElement =  doc->createElement("onTransition");
                    backTransitionElement.appendChild(onTransitionElement);
                    QDomElement closeElement = doc->createElement("camitk:close");
                    onTransitionElement.appendChild(closeElement);

                    foreach (HistoryComponent outputHistoryComponent, previousItem.getOutputHistoryComponents()) {
                        QDomElement backTransitionComponentElement = doc->createElement("camitk:component");
                        closeElement.appendChild(backTransitionComponentElement);

                        switch (outputHistoryComponent.getType()) {
                        case HistoryComponent::IMAGE_COMPONENT:
                            backTransitionComponentElement.setAttribute("type", "ImageComponent");
                            break;

                        case HistoryComponent::MESH_COMPONENT:
                            backTransitionComponentElement.setAttribute("type", "MeshComponent");
                            break;

                        case HistoryComponent::OTHER:
                        default:
                            backTransitionComponentElement.setAttribute("type", "Other");
                            break;
                        }

                        backTransitionComponentElement.setAttribute("name", outputHistoryComponent.getName());
                    }
                }
            }

            stateElement.appendChild(backTransitionElement);
        } // End transitions
    }

    // Update description of the pipeline, now that we have filled the it!
    initializeElt_desc.appendChild(doc->createCDATASection(pipelineDescription));

    // Last element : Bye
    QDomElement finalElt = doc->createElement("state");
    root.appendChild(finalElt);
    finalElt.setAttribute("id", "Bye");
    finalElt.setAttribute("final", "true");
    QDomElement finalElt_onEntry = doc->createElement("onentry");
    finalElt.appendChild(finalElt_onEntry);
    QDomElement finalElt_onState = doc->createElement("camitk:onState");
    finalElt_onEntry.appendChild(finalElt_onState);
    // Thanks !
    QDomElement finalElt_desc = doc->createElement("camitk:description");
    finalElt_onState.appendChild(finalElt_desc);
    QDomCDATASection finalEltDesc;
    finalElt_desc.appendChild(doc->createCDATASection("Thanks you for using the CamiTK Action State Machine !"));
    // Transition
    QDomElement finalElt_transition = doc->createElement("transition");
    finalElt_transition.setAttribute("event", "Back to the beginning");
    finalElt_transition.setAttribute("target", "Initialize");
    finalElt.appendChild(finalElt_transition);

    // Ask for deletion of all components created in the pipeline
    if (!allCreatedComponents.isEmpty()) {
        QDomElement finalEltTransition_onTransition =  doc->createElement("onTransition");
        finalElt_transition.appendChild(finalEltTransition_onTransition);
        QDomElement finalEltTransition_close = doc->createElement("camitk:close");
        finalEltTransition_onTransition.appendChild(finalEltTransition_close);

        foreach (HistoryComponent createdComponent, allCreatedComponents) {
            QDomElement finalEltTransition_comp = doc->createElement("camitk:component");
            finalEltTransition_close.appendChild(finalEltTransition_comp);

            switch (createdComponent.getType()) {
            case HistoryComponent::IMAGE_COMPONENT:
                finalEltTransition_comp.setAttribute("type", "ImageComponent");
                break;

            case HistoryComponent::MESH_COMPONENT:
                finalEltTransition_comp.setAttribute("type", "MeshComponent");
                break;

            case HistoryComponent::OTHER:
            default:
                finalEltTransition_comp.setAttribute("type", "Other");
                break;
            }

            finalEltTransition_comp.setAttribute("name", createdComponent.getName());
        }
    }

    // note, final element will automatically have a "Quit" transition, added by the action state machine.


    // Get the xml of the tree node
    QString xml = doc->toString();

    // Write the xml to a file
    QString outputFileName = QFileDialog::getSaveFileName(NULL, tr("Save history of actions ..."), Core::getCurrentWorkingDir() + "/actions_history.xml", tr("XML Files (*.xml)"));

    QFile * outputFile = new QFile(outputFileName);

    if (outputFile->open(QFile::WriteOnly)) {
        QTextStream out(outputFile);
        out << xml;
    } else {
        cerr << "Error opening file: " + outputFileName.toStdString() + " for writing.";
    }
}


// -------------------- getSelectedLanguage --------------------
QString Application::getSelectedLanguage() {
    // Get the path of the Language File in the setting file
    QSettings  & settings(Application::getSettings());
    settings.beginGroup(Application::getName() + ".Application");
    // Get the language an d flag for the current Application instance
    QString language = settings.value("language").toString();
    QString languageAbreviation = settings.value("languageAbreviation").toString();
    QString flagFile = settings.value("flagFilename").toString();
    settings.endGroup();

    return languageAbreviation;
}

// -------------------- initResources --------------------
void Application::initResources() {

    // Get the selected language
    QString selectedLanguage = Application::getSelectedLanguage();

    // Load the application translation
    translator = new QTranslator();
    QString languageFile = ":/translate_" + getName().remove("camitk-") + "/translate/translate_" + selectedLanguage + ".qm";

    if (!translator->load(languageFile))
        CAMITK_WARNING("Application", "initResources", "Cannot load file: " + languageFile.toStdString())
        else {
            QCoreApplication::installTranslator(translator);
        }
}

// -------------------- createProperties --------------------
void Application::createProperties() {
    // instanciate the application property object
    propertyObject = new PropertyObject(getName());

    // Target positionning method
    defaultTargetedComponentProperty = new Property("Default target positionning method", camitk::Application::SAME_TRANSFORMATION, "When an action creates a new component, what should be its frame regarding its parent's one.","");
    defaultTargetedComponentProperty->setEnumTypeName("TargetPositionningMethods");
    QStringList targetPositionningMethodPropertyGUINames;
    targetPositionningMethodPropertyGUINames << "Same transformation" << "No transformation" << "Subframe";
    defaultTargetedComponentProperty->setAttribute("enumNames", targetPositionningMethodPropertyGUINames);
    propertyObject->addProperty(defaultTargetedComponentProperty);

    // Auto load last opened components
    autoloadLastOpenedComponentsProperty = new Property("Auto load last opened components?", false,
            "Do you want to open the last opened components when starting the application?", "");
    propertyObject->addProperty(autoloadLastOpenedComponentsProperty);

}

// -------------------- setDefaultTargetPositionningMethod --------------------
void Application::setDefaultTargetPositionningMethod(TargetPositionningMethods method) {
    dynamic_cast<Application*>(qApp)->getPropertyObject()->setProperty("Default target positionning method", method);
}

// -------------------- getDefaultTargetPositionningMethod --------------------
Application::TargetPositionningMethods Application::getDefaultTargetPositionningMethod() {
    return (TargetPositionningMethods) dynamic_cast<Application*>(qApp)->getPropertyObject()->property("Default target positionning method").toInt();
}

// ---------------------- getPropertyObject ------------------
PropertyObject* Application::getPropertyObject() {
    return propertyObject;
}

}






