//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Main/MainWindow.cpp
//! @brief     Implements class MainWindow
//!
//! @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/MainWindow.h"
#include "GUI/Application/ApplicationSettings.h"
#include "GUI/Model/Model/JobModel.h"
#include "GUI/Support/Util/Path.h"
#include "GUI/View/Import/ImportDataView.h"
#include "GUI/View/Instrument/InstrumentView.h"
#include "GUI/View/Job/JobView.h"
#include "GUI/View/Main/ActionManager.h"
#include "GUI/View/Project/ProjectManager.h"
#include "GUI/View/SampleDesigner/SampleView.h"
#include "GUI/View/Tool/mainwindow_constants.h"
#include "GUI/View/Toplevel/ProjectsView.h"
#include "GUI/View/Toplevel/SimulationView.h"
#include <QAction>
#include <QApplication>
#include <QBoxLayout>
#include <QButtonGroup>
#include <QCloseEvent>
#include <QDir>
#include <QMessageBox>
#include <QOpenGLWidget>
#include <QProgressBar>
#include <QPushButton>
#include <QSettings>
#include <QStackedLayout>
#include <QStatusBar>
#include <QToolButton>

MainWindow::MainWindow()
    : QMainWindow(nullptr)
    , m_progressBar(new QProgressBar)
    , m_viewSelectionButtons(new QButtonGroup(this))
    , m_viewsStack(new QStackedLayout)
    , m_viewSelectionButtonsLayout(new QVBoxLayout)
    , m_projectManager(new ProjectManager(this))
    , m_actionManager(new ActionManager(this))
    , m_projectsView(nullptr)
    , m_instrumentView(nullptr)
    , m_sampleView(nullptr)
    , m_importDataView(nullptr)
    , m_simulationView(nullptr)
    , m_jobView(nullptr)
{
    auto* centralWidget = new QWidget(this);
    auto* mainLayout = new QHBoxLayout(centralWidget);
    mainLayout->setContentsMargins(0, 0, 0, 0);
    mainLayout->setSpacing(0);

    m_viewSelectionButtonsLayout->setContentsMargins(0, 0, 0, 0);
    m_viewSelectionButtonsLayout->setSpacing(0);

    auto* fillerButton = createViewSelectionButton();
    fillerButton->setMinimumSize(5, 5);
    fillerButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
    fillerButton->setEnabled(false);
    m_viewSelectionButtonsLayout->insertWidget(-1, fillerButton);

    connect(m_viewSelectionButtons, &QButtonGroup::idClicked, this, &MainWindow::raiseView);

    auto* vlayout = new QVBoxLayout;
    vlayout->setContentsMargins(0, 0, 0, 0);
    vlayout->setSpacing(0);
    vlayout->addLayout(m_viewsStack);

    vlayout->addWidget(new QOpenGLWidget()); // quick fix to reset surface format

    mainLayout->addLayout(m_viewSelectionButtonsLayout);
    mainLayout->addLayout(vlayout);

    setCentralWidget(centralWidget);

    initApplication();
    readSettings();
    initProgressBar();
    initButtons();
    initViews();

    connect(m_projectManager, &ProjectManager::documentOpenedOrClosed, this,
            &MainWindow::onDocumentOpenedOrClosed);

    connect(m_projectManager, &ProjectManager::documentModified, this,
            &MainWindow::onDocumentModified);

    connect(m_projectManager, &ProjectManager::aboutToCloseDocument, this,
            &MainWindow::onAboutToCloseDocument);

    ASSERT(m_viewSelectionButtons->button(GUI::ID::ViewId::Projects) != nullptr);
    m_viewSelectionButtons->button(GUI::ID::ViewId::Projects)->setChecked(true);

    updateTitle();
    if (appSettings)
        if (appSettings->createNewProjectOnStartup())
            m_projectManager->newProject();
}

MainWindow::~MainWindow() = default;

QProgressBar* MainWindow::progressBar()
{
    return m_progressBar;
}

ProjectManager* MainWindow::projectManager()
{
    return m_projectManager;
}

QWidget* MainWindow::currentView() const
{
    return m_viewsStack->currentWidget();
}

void MainWindow::setCurrentView(int viewId)
{
    if (auto* btn = m_viewSelectionButtons->button(viewId); btn != nullptr)
        btn->click();
}

void MainWindow::raiseView(int viewId)
{
    if (gProjectDocument.has_value() && viewId != GUI::ID::ViewId::Projects)
        gProjectDocument.value()->setViewId(viewId);
    if (m_viewsStack->currentIndex() != viewId) {
        m_viewsStack->setCurrentIndex(viewId);
        emit currentViewChanged(GUI::ID::ViewId(viewId));
    }
}

void MainWindow::updateTitle()
{
    auto const doc = gProjectDocument;
    QString location = "not saved yet";
    if (doc.has_value() && doc.value()->hasValidNameAndPath())
        location = GUI::Base::Path::withTildeHomePath(
            QDir::toNativeSeparators(doc.value()->projectFullPath()));
    if (!doc.has_value())
        setWindowTitle("BornAgain");
    else if (doc.value()->isModified())
        setWindowTitle("BornAgain - *" + doc.value()->projectName() + " [" + location + "]");
    else
        setWindowTitle("BornAgain - " + doc.value()->projectName() + " [" + location + "]");
}

void MainWindow::onFocusRequest(int index)
{
    m_viewSelectionButtons->button(index)->click();
}

void MainWindow::openRecentProject()
{
    if (const auto* action = qobject_cast<const QAction*>(sender())) {
        auto file = action->data().value<QString>();
        m_projectManager->openProject(file);
    }
}

void MainWindow::onRunSimulationShortcut()
{
    // This clearFocus is needed for the propagation of the current editor value,
    // since the simulate method will only change focus after finishing the simulation
    if (auto* widget = QApplication::focusWidget())
        widget->clearFocus();
    m_simulationView->simulate();
}

void MainWindow::closeEvent(QCloseEvent* event)
{
    if (gProjectDocument.has_value() && gProjectDocument.value()->jobModel()->hasUnfinishedJobs()) {
        QMessageBox::warning(this, "Cannot quit the application.",
                             "Cannot quit the application while jobs are running.\n"
                             "Cancel running jobs or wait until they are completed.");
        event->ignore();
        return;
    }
    if (m_projectManager->closeCurrentProject()) {
        writeSettings();
        event->accept();
    } else {
        event->ignore();
    }
}

void MainWindow::initApplication()
{
    setDockNestingEnabled(true);
    setAcceptDrops(true);

    setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
    setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea);
}

void MainWindow::initProgressBar()
{
    m_progressBar->hide();
    m_progressBar->setTextVisible(false);
    m_progressBar->setFixedHeight(QApplication::fontMetrics().boundingRect("M").height());
    m_progressBar->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
    m_viewSelectionButtonsLayout->addWidget(m_progressBar);
}

void MainWindow::initButtons()
{
    addButton(GUI::ID::ViewId::Projects, QIcon(":/images/main_welcomeview.svg"), "Projects",
              "Switch to Projects View");
    addButton(GUI::ID::ViewId::Instrument, QIcon(":/images/main_instrumentview.svg"), "Instrument",
              "Define the beam and the  detector");

    addButton(GUI::ID::ViewId::Sample, QIcon(":/images/main_sampleview.svg"), "Sample",
              "Build the sample");

    addButton(GUI::ID::ViewId::Import, QIcon(":/images/main_importview.svg"), "Data",
              "Import intensity data to fit");

    addButton(GUI::ID::ViewId::Simulation, QIcon(":/images/main_simulationview.svg"), "Simulation",
              "Run simulation");

    addButton(GUI::ID::ViewId::Job, QIcon(":/images/main_jobview.svg"), "Jobs",
              "Switch to see job results, tune parameters real time,\nfit the data");

    for (auto* button : m_viewSelectionButtons->buttons()) {
        if (button == m_viewSelectionButtons->button(GUI::ID::ViewId::Projects))
            continue;
        button->setEnabled(false);
    }
}

void MainWindow::initViews()
{
    m_projectsView = new ProjectsView(this);
    resetView(GUI::ID::ViewId::Projects, m_projectsView);

    if (gProjectDocument.has_value()) {
        auto* doc = gProjectDocument.value();
        m_instrumentView = new InstrumentView(this, doc);
        m_sampleView = new SampleView(this, doc);
        m_importDataView = new ImportDataView(this, doc);
        m_simulationView = new SimulationView(this, doc);
        m_jobView = new JobView(progressBar(), doc);

        resetView(GUI::ID::ViewId::Instrument, m_instrumentView);

        resetView(GUI::ID::ViewId::Sample, m_sampleView);

        resetView(GUI::ID::ViewId::Import, m_importDataView);

        resetView(GUI::ID::ViewId::Simulation, m_simulationView);

        resetView(GUI::ID::ViewId::Job, m_jobView);

        connect(m_jobView, &JobView::focusRequest, this, &MainWindow::onFocusRequest);

        m_jobView->onSelectionChanged();

        if (gProjectDocument.has_value())
            setCurrentView(gProjectDocument.value()->viewId());
        else
            raiseView(GUI::ID::ViewId::Projects);
    }
}

void MainWindow::readSettings()
{
    QSettings settings;
    settings.beginGroup(GUI::Constants::S_MAIN_WINDOW);
    resize(settings.value(GUI::Constants::S_WINDOW_SIZE, QSize(1000, 600)).toSize());
    move(settings.value(GUI::Constants::S_WINDOW_POSITION, QPoint(500, 300)).toPoint());
    settings.endGroup();

    m_projectManager->readSettings();
}

void MainWindow::writeSettings()
{
    QSettings settings;
    settings.beginGroup(GUI::Constants::S_MAIN_WINDOW);
    settings.setValue(GUI::Constants::S_WINDOW_SIZE, size());
    settings.setValue(GUI::Constants::S_WINDOW_POSITION, pos());
    settings.endGroup();
    m_projectManager->writeSettings();
    settings.sync();
}

void MainWindow::addButton(GUI::ID::ViewId id, const QIcon& icon, const QString& title,
                           const QString& tooltip)
{
    QToolButton* btn = createViewSelectionButton();
    m_viewSelectionButtonsLayout->insertWidget(id, btn);

    btn->setText(title);
    btn->setToolTip(tooltip);
    btn->setIcon(icon);
    m_viewSelectionButtons->addButton(btn, id);

    updateViewSelectionButtonsGeometry();
}

void MainWindow::resetView(GUI::ID::ViewId id, QWidget* view)
{
    m_viewsStack->insertWidget(id, view);
}

void MainWindow::updateViewSelectionButtonsGeometry() const
{
    if (m_viewSelectionButtons->buttons().isEmpty())
        return;

    const QFontMetrics fontMetrics = m_viewSelectionButtons->buttons().first()->fontMetrics();

    // Find the maximum text extents
    int maxTextWidth = 0;
    int maxTextHeight = 0;
    for (auto* b : m_viewSelectionButtons->buttons()) {
        const auto r = fontMetrics.boundingRect(b->text());
        maxTextWidth = std::max(maxTextWidth, r.width());
        maxTextHeight = std::max(maxTextHeight, r.height());
    }

    // calculate the button extent by width (width == height!). Ensure an extent of 70 for normal
    // DPI devices (legacy value)
    const int margin = fontMetrics.boundingRect("M").width();
    const int buttonExtent = std::max(70, maxTextWidth + 2 * margin);

    // calculate the icon extent by height (width == height!)
    const int iconExtent = buttonExtent - margin - maxTextHeight;

    // set new values in all buttons
    for (auto* b : m_viewSelectionButtons->buttons()) {
        b->setFixedSize(buttonExtent, buttonExtent);
        b->setIconSize({iconExtent, iconExtent});
    }
    // set fixed width in filler and progress bar
    auto* filler = m_viewSelectionButtonsLayout->itemAt(m_viewSelectionButtons->buttons().size());
    if (filler)
        if (auto* fillerBtn = dynamic_cast<QToolButton*>(filler->widget()); fillerBtn)
            fillerBtn->setFixedWidth(buttonExtent);

    m_progressBar->setFixedWidth(buttonExtent);
}

void MainWindow::onDocumentOpenedOrClosed(bool open)
{
    initViews();
    updateTitle();
    if (open) {
        if (gProjectDocument.has_value()) {
            for (auto* button : m_viewSelectionButtons->buttons())
                button->setEnabled(true);
            auto* filler =
                m_viewSelectionButtonsLayout->itemAt(m_viewSelectionButtons->buttons().size());
            if (filler)
                if (auto* fillerBtn = dynamic_cast<QToolButton*>(filler->widget()); fillerBtn)
                    fillerBtn->setEnabled(true);
            setCurrentView(gProjectDocument.value()->viewId());
        } else
            setCurrentView(GUI::ID::ViewId::Instrument);
    }
}

void MainWindow::onDocumentModified()
{
    updateTitle();
}

void MainWindow::onAboutToCloseDocument()
{
    setCurrentView(GUI::ID::ViewId::Projects);

    updateViewSelectionButtonsGeometry();

    delete m_instrumentView;
    m_instrumentView = nullptr;

    delete m_sampleView;
    m_sampleView = nullptr;

    delete m_importDataView;
    m_importDataView = nullptr;

    delete m_simulationView;
    m_simulationView = nullptr;

    delete m_jobView;
    m_jobView = nullptr;
}

QToolButton* MainWindow::createViewSelectionButton() const
{
    auto* btn = new QToolButton;
    btn->setObjectName("ViewSelectionButton"); // for addressing in style sheet
    btn->setCheckable(true);
    btn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
    return btn;
}

void MainWindow::loadProject(QString projectPath)
{
    m_projectManager->openProject(projectPath);
}
