//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Realspace/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/Realspace/RealspaceWidget.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Material/MaterialItem.h"
#include "GUI/Model/Sample/SampleItem.h"
#include "GUI/View/Info/CautionSign.h"
#include "GUI/View/Realspace/RealspaceBuilder.h"
#include "GUI/View/Widget/AppConfig.h"
#include "GUI/View/Widget/FileDialog.h"
#include "Img3D/Model/Model.h"
#include "Img3D/View/Canvas.h"
#include <QApplication>
#include <QMessageBox>
#include <QVBoxLayout>

RealspaceWidget::RealspaceWidget(QWidget* parent)
    : QWidget(parent)
    , m_canvas(new Img3D::Canvas)
    , m_caution_sign(new CautionSign(this))
    , m_caution_label(new QLabel(this))
    , m_displayed_item(nullptr)
    , m_containing_sample(nullptr)
    , m_first_view(true)
{
    m_canvas->layout()->addWidget(m_caution_label);

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

RealspaceWidget::~RealspaceWidget() = default;

void RealspaceWidget::setDisplayedItem(SampleItem* containingSample, Item3D* item)
{
    if (!item) {
        resetRealScene();
        return;
    }
    m_containing_sample = containingSample;
    m_displayed_item = item;
    connectShowRoughnessCheckbox();
    updateRealScene();
    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_displayed_item)
        return;

    m_scene_geometry.layerSize = m_scene_geometry.layerSize * layerSizeChangeScale;
    updateRealScene();
}

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

void RealspaceWidget::savePicture()
{
    static const QString defaultExtension = ".png";
    QString fname = GUI::FileDialog::w1_1f("Save 3D real space view", gApp->artifact_export_dir,
                                           "*" + defaultExtension);
    if (fname.isEmpty())
        return;
    if (!fname.endsWith(defaultExtension))
        fname += defaultExtension;

    QPixmap pixmap(this->size());
    render(&pixmap, QPoint(), childrenRegion());
    bool ok = pixmap.save(fname);
    if (!ok)
        QMessageBox::warning(nullptr, "Cannot save", "Cannot save picture in file " + fname);
}

void RealspaceWidget::updateRealScene()
{
    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_realspace_model = std::make_unique<Img3D::Model>();

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

    RealspaceBuilder builder3D(colorForMaterialName);

    try {
        m_caution_sign->clear();
        m_caution_label->hide();
        m_realspace_model->defaultCameraPosition = m_canvas->camera()->getPos();
        unsigned numParticles = 0;

        if (m_displayed_item)
            builder3D.populate(m_realspace_model.get(), m_displayed_item, m_containing_sample,
                               m_scene_geometry, numParticles);
        if (m_scene_geometry.maxNumberOfParticlesToShow < numParticles)
            throw std::runtime_error("The number of particles to display is too large");
        m_canvas->setModel(m_realspace_model.get());
    } catch (const std::exception& ex) {
        m_canvas->setModel(nullptr);
        m_caution_label->show();
        m_caution_label->setText(ex.what());
        m_caution_sign->setCautionMessage(ex.what());
    } catch (...) {
        ASSERT_NEVER; // don't tolerate other exceptions
    }

    QApplication::restoreOverrideCursor();
}

void RealspaceWidget::connectShowRoughnessCheckbox()
{
    if (!m_containing_sample || !m_showRoughnessCheckbox)
        return;

    m_showRoughnessCheckbox->disconnect();
    m_showRoughnessCheckbox->setChecked(m_containing_sample->showRoughness);
    connect(m_showRoughnessCheckbox, &QCheckBox::toggled, [this] {
        m_containing_sample->showRoughness = m_showRoughnessCheckbox->isChecked();
        updateRealScene();
    });
}

void RealspaceWidget::resetRealScene()
{
    m_realspace_model.reset();
    m_canvas->setModel(nullptr);
    m_displayed_item = nullptr;
    m_containing_sample = nullptr;
}

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

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

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

void RealspaceWidget::regenerateParticlePositions()
{
    ASSERT(m_containing_sample);
    m_containing_sample->adjustLayoutSeeds();
    updateRealScene();
}

void RealspaceWidget::regenerateRoughness()
{
    ASSERT(m_containing_sample);
    m_containing_sample->adjustLayerSeeds(true);
    updateRealScene();
}
