//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/SampleView/RealspaceWidget.cpp
//! @brief     Implements class RealspaceScene
//!
//! @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/SampleView/RealspaceWidget.h"
#include "Base/Util/Assert.h"
#include "GUI/Application/ApplicationSettings.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/Model/Sample/MaterialItem.h"
#include "GUI/Model/Sample/SampleItem.h"
#include "GUI/View/Info/CautionSign.h"
#include "GUI/View/SampleView/RealspaceBuilder.h"
#include "Img3D/Model/Model.h"
#include "Img3D/View/Canvas.h"
#include <QApplication>
#include <QFileDialog>
#include <QMessageBox>
#include <QVBoxLayout>
#include <memory>

RealspaceWidget::RealspaceWidget(QWidget* parent)
    : QWidget(parent)
    , m_canvas(new Img3D::Canvas)
    , m_cautionSign(new CautionSign(this))
    , m_cautionLabel(new QLabel(this))
    , m_displayedItem(nullptr)
    , m_containingSample(nullptr)
    , m_firstView(true)
{
    m_canvas->layout()->addWidget(m_cautionLabel);

    auto* layout = new QVBoxLayout;
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);
    layout->addWidget(m_canvas);
    setLayout(layout);
}

RealspaceWidget::~RealspaceWidget() = default;

void RealspaceWidget::setDisplayedItem(SampleItem* containingSample, Item3D* item)
{
    if (!item) {
        resetScene();
        return;
    }
    m_containingSample = containingSample;
    m_displayedItem = item;
    updateScene();
    defaultView(); // Enforces default view and also sets the zoomLevel to default i.e. 0
}

void RealspaceWidget::changeLayerSize(double layerSizeChangeScale)
{
    // when no object is selected --> take no action
    if (!m_displayedItem)
        return;

    m_sceneGeometry.layerSize = m_sceneGeometry.layerSize * layerSizeChangeScale;
    updateScene();
}

void RealspaceWidget::savePicture()
{
    QPixmap pixmap(this->size());
    render(&pixmap, QPoint(), childrenRegion());
    savePicture(pixmap);
}

void RealspaceWidget::showEvent(QShowEvent*)
{
    updateScene();
    if (m_firstView)
        m_canvas->defaultView();
    m_firstView = false;
}

void RealspaceWidget::savePicture(const QPixmap& pixmap)
{
    ASSERT(gProjectDocument.has_value());
    QString dirname = gProjectDocument.value()->userExportDir();
    QString defaultExtension = ".png";
    QString selectedFilter("*" + defaultExtension);
    QString defaultName = dirname + "/untitled";
    QString fileName = QFileDialog::getSaveFileName(
        nullptr, "Save 3D real space view", defaultName, selectedFilter, nullptr,
        appSettings->useNativeFileDialog() ? QFileDialog::Options()
                                           : QFileDialog::DontUseNativeDialog);
    QString nameToSave =
        fileName.endsWith(defaultExtension) ? fileName : fileName + defaultExtension;

    if (!nameToSave.isEmpty()) {
        try {
            pixmap.save(nameToSave);
        } catch (const std::exception& ex) {
            QString message = "Attempt to save file with the name '";
            message.append(nameToSave);
            message.append("' has failed with following error message\n\n");
            message.append(QString::fromStdString(ex.what()));
            QMessageBox::warning(nullptr, "Houston, we have a problem.", message);
        }
    }
}

void RealspaceWidget::updateScene()
{
    if (!isVisible()) // to improve performance
        return;

    if (!m_canvas->isValid())
        // GL not initialized (widget not shown so far) => no updating possible
        return;

    QApplication::setOverrideCursor(Qt::WaitCursor);

    m_realspaceModel = std::make_unique<Img3D::Model>();

    const auto colorForMaterialName = [&](const QString& materialName) {
        return m_containingSample->materialModel().materialItemFromName(materialName)->color();
    };

    RealspaceBuilder builder3D(colorForMaterialName);

    try {
        m_cautionSign->clear();
        m_cautionLabel->hide();
        m_realspaceModel->defaultCameraPosition = m_canvas->camera()->getPos();
        unsigned numParticles = 0;
        if (m_displayedItem)
            builder3D.populate(m_realspaceModel.get(), m_displayedItem, m_sceneGeometry,
                               numParticles);
        if (m_sceneGeometry.maxNumberOfParticlesToShow < numParticles)
            throw std::runtime_error("The number of particles to display is too large");
        m_canvas->setModel(m_realspaceModel.get());
    } catch (const std::exception& ex) {
        m_canvas->setModel(nullptr);
        m_cautionLabel->show();
        m_cautionLabel->setText(ex.what());
        m_cautionSign->setCautionMessage(ex.what());
    } catch (...) {
        ASSERT(false); // don't tolerate other exceptions
    }

    QApplication::restoreOverrideCursor();
}

void RealspaceWidget::resetScene()
{
    m_realspaceModel.reset();
    m_canvas->setModel(nullptr);
    m_displayedItem = nullptr;
    m_containingSample = nullptr;
}

void RealspaceWidget::defaultView()
{
    m_canvas->defaultView();
}

void RealspaceWidget::sideView()
{
    m_canvas->sideView();
}

void RealspaceWidget::topView()
{
    m_canvas->topView();
}
