//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Main/ProjectManager.cpp
//! @brief     Implements class ProjectManager.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/View/Main/ProjectManager.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Project/AutosaveController.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/Model/Project/ProjectUtil.h"
#include "GUI/View/Info/MessageBox.h"
#include "GUI/View/Widget/AppConfig.h"
#include "GUI/View/Widget/FileDialog4Project.h"
#include <QApplication>
#include <QDateTime>
#include <QFileDialog>
#include <QMessageBox>
#include <QSettings>

namespace {

const QString S_PROJECTMANAGER = "ProjectManager";
const QString S_DEFAULTPROJECTPATH = "DefaultProjectPath";
const QString S_RECENTPROJECTS = "RecentProjects";
const QString S_LASTUSEDIMPORTDIR = "LastUsedImportDir";

} // namespace


ProjectManager::ProjectManager(QObject* parent)
    : QObject(parent)
    , m_autosaver(std::make_unique<AutosaveController>())
{
}

ProjectManager::~ProjectManager() = default;

//! Reads settings of ProjectManager from global settings.

void ProjectManager::loadSettings()
{
    QSettings s;
    m_working_directory = QDir::homePath();
    if (s.childGroups().contains(S_PROJECTMANAGER)) {
        s.beginGroup(S_PROJECTMANAGER);
        m_working_directory = s.value(S_DEFAULTPROJECTPATH).toString();
        m_recent_projects = s.value(S_RECENTPROJECTS).toStringList();
        s.endGroup();
    }
}

//! Saves settings of ProjectManager in global settings.

void ProjectManager::saveSettings()
{
    QSettings s;
    s.beginGroup(S_PROJECTMANAGER);
    s.setValue(S_DEFAULTPROJECTPATH, m_working_directory);
    s.setValue(S_RECENTPROJECTS, m_recent_projects);
    s.endGroup();
}

//! Returns list of recent projects, validates if projects still exists on disk.

QStringList ProjectManager::recentProjects()
{
    QStringList updatedList;
    for (const QString& fname : m_recent_projects)
        if (QFile fin(fname); fin.exists())
            updatedList.append(fname);
    m_recent_projects = updatedList;
    return m_recent_projects;
}

//! Calls save/discard/cancel dialog, if necessary.
//! Returns false if saving was canceled.

bool ProjectManager::saveOnQuit()
{
    if (gDoc->isModified()) {
        QMessageBox msgBox;
        msgBox.setText("The project has been modified.");
        msgBox.setInformativeText("Do you want to save your changes?");
        msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
        msgBox.setDefaultButton(QMessageBox::Save);

        switch (msgBox.exec()) {
        case QMessageBox::Save:
            if (!saveProject())
                return false;
            break;
        case QMessageBox::Discard:
            break;
        case QMessageBox::Cancel:
            return false;
        default:
            break;
        }
    }
    return true;
}

//! Clears list of recent projects.

void ProjectManager::clearRecentProjects()
{
    m_recent_projects.clear();
    emit recentListModified();
}

//! Processes new project request (close old project, rise dialog for project name, create project).

void ProjectManager::newProject()
{
    createNewProject();
    emit documentOpenedOrClosed(true);
}

//! Processes save project request.

bool ProjectManager::saveProject(QString projectPullPath)
{
    if (projectPullPath.isEmpty()) {
        if (gDoc->hasValidNameAndPath())
            projectPullPath = gDoc->projectFullPath();
        else
            projectPullPath = acquireProjectPullPath();
    }

    if (projectPullPath.isEmpty())
        return false;

    gDoc->setProjectName(GUI::Util::Project::projectName(projectPullPath));
    gDoc->setProjectDir(GUI::Util::Project::projectDir(projectPullPath));

    try {
        gDoc->saveProjectFileWithData(projectPullPath);
    } catch (const std::exception& ex) {
        QString message = QString("Failed to save project under '%1'. \n\n").arg(projectPullPath);
        message.append("Exception was thrown.\n\n");
        message.append(ex.what());

        QMessageBox::warning(gApp->mainWindow, "Error while saving project", message);
        return false;
    }
    addToRecentProjects();
    return true;
}

//! Processes 'save project as' request.

bool ProjectManager::saveProjectAs()
{
    QString projectFileName = acquireProjectPullPath();

    if (projectFileName.isEmpty())
        return false;

    return saveProject(projectFileName);
}

//! Opens existing project. If fname is empty, will popup file selection dialog.

void ProjectManager::openProject(QString projectPullPath)
{
    if (projectPullPath.isEmpty()) {
        const QString ext = QString(GUI::Util::Project::projectFileExtension);
        projectPullPath = QFileDialog::getOpenFileName(
            gApp->mainWindow, "Open project file", m_working_directory,
            "BornAgain project Files (*" + ext + ")", nullptr,
            gApp->useNativeFileDialog() ? QFileDialog::Options()
                                        : QFileDialog::DontUseNativeDialog);
        if (projectPullPath.isEmpty())
            return;
    }

    createNewProject();
    loadProject(projectPullPath);
    emit documentOpenedOrClosed(true);
}

//! Calls dialog window to define project path and name.

void ProjectManager::createNewProject()
{
    gDoc->clear();

    if (gApp->autosave_enabled)
        m_autosaver->setDocument(gDoc.get());
}

//! Load project data from file name. If autosave info exists, opens dialog for project restore.

void ProjectManager::loadProject(const QString& fullPathAndName)
{
    const bool useAutosave = GUI::Util::Project::hasAutosavedData(fullPathAndName);
    const QString autosaveFullPath = GUI::Util::Project::autosaveFullPath(fullPathAndName);
    if (qApp)
        QApplication::setOverrideCursor(Qt::WaitCursor);
    if (useAutosave && restoreProjectDialog(fullPathAndName, autosaveFullPath)) {
        gDoc->loadProjectFileWithData(autosaveFullPath);
        gDoc->setProjectFullPath(fullPathAndName);
        // restored project should be marked by '*'
        gDoc->setModified();
    } else {
        gDoc->loadProjectFileWithData(fullPathAndName);
    }
    if (qApp)
        QApplication::restoreOverrideCursor();
}

//! Returns project file name from dialog. Returns empty string if dialog was canceled.

QString ProjectManager::acquireProjectPullPath()
{
    FileDialog4Project dialog(gApp->mainWindow, m_working_directory, untitledProjectName());

    if (dialog.exec() != QDialog::Accepted)
        return "";

    m_working_directory = dialog.getWorkingDirectory();

    return dialog.getProjectFileName();
}

//! Add name of the current project to the name of recent projects

void ProjectManager::addToRecentProjects()
{
    QString fname = gDoc->projectFullPath();
    m_recent_projects.removeAll(fname);
    m_recent_projects.prepend(fname);
    while (m_recent_projects.size() > 10)
        m_recent_projects.removeLast();

    emit recentListModified();
}

//! Returns default project path.
//! Will return 'Untitled' if the directory with such name doesn't exist in project
//! path. Otherwise will return Untitled1, Untitled2 etc.

QString ProjectManager::untitledProjectName()
{
    QString result = "Untitled";
    QDir projectDir = m_working_directory + "/" + result;
    if (projectDir.exists()) {
        for (size_t i = 1; i < 99; ++i) {
            result = QString("Untitled") + QString::number(i);
            projectDir.setPath(m_working_directory + "/" + result);
            if (!projectDir.exists())
                break;
        }
    }
    return result;
}

//! Rises dialog if the project should be restored from autosave. Returns true, if yes.

bool ProjectManager::restoreProjectDialog(const QString& projectFileName,
                                          const QString autosaveName)
{
    const QString title("Recover project");
    const QString lmProject =
        QFileInfo(projectFileName).lastModified().toString("hh:mm:ss, MMMM d, yyyy");
    const QString lmAutoSave =
        QFileInfo(autosaveName).lastModified().toString("hh:mm:ss, MMMM d, yyyy");

    QString message = QString("Project '%1' contains autosaved data.\n\n"
                              "Project saved at %2\nAutosave from %3")
                          .arg(GUI::Util::Project::projectName(projectFileName))
                          .arg(lmProject)
                          .arg(lmAutoSave);

    return GUI::Message::question(gApp->mainWindow, title, message,
                                  "\nDo you want to restore from autosave?\n",
                                  "Yes, please restore.", "No, keep loading original");
}
