//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/View/SampleView.cpp
//! @brief     Implements class SampleView.
//!
//! @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/View/SampleView.h"
#include "Base/Util/Vec.h"
#include "GUI/Model/FromCore/GUIExamplesFactory.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/Model/Sample/ItemWithParticles.h"
#include "GUI/Model/Sample/LayerItem.h"
#include "GUI/Model/Sample/LayerStackItem.h"
#include "GUI/Model/Sample/ParticleLayoutItem.h"
#include "GUI/Model/Sample/SamplesSet.h"
#include "GUI/View/Base/mainwindow_constants.h"
#include "GUI/View/IO/PythonImport.h"
#include "GUI/View/Info/MessageBox.h"
#include "GUI/View/Modelview/SetView.h"
#include "GUI/View/Realspace/RealspacePanel.h"
#include "GUI/View/Realspace/RealspaceWidget.h"
#include "GUI/View/Sample/SampleEditor.h"
#include "GUI/View/Sample/ScriptPanel.h"
#include "GUI/View/Setup/ActionFactory.h"
#include "GUI/View/Widget/StyledToolbar.h"
#include <QBoxLayout>
#include <QMenu>
#include <QSettings>
#include <QSplitter>
#include <QToolButton>

SampleView::SampleView()
    : m_set(gDoc->samplesRW())
    , m_qlistview(new SetView(m_set))
    , m_realspace_panel(new RealspacePanel(this))
{
    auto* layout = new QVBoxLayout(this);

    //... Top toolbar with action buttons "new", "import", "example"
    auto* toolbar = new StyledToolbar;
    layout->addWidget(toolbar);
    toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
    createActions(toolbar);

    //... Everything below top toolbar
    auto* hListSplitter = new QSplitter;
    layout->addWidget(hListSplitter);

    //... Left margin: sample listing

    hListSplitter->addWidget(m_qlistview);
    hListSplitter->setCollapsible(0, false);
    hListSplitter->setStretchFactor(0, 0);

    //... Large block on the right: sample editor and bottom panels

    auto* vSplitter = new QSplitter;
    vSplitter->setOrientation(Qt::Vertical);
    hListSplitter->addWidget(vSplitter);
    hListSplitter->setCollapsible(1, false);
    hListSplitter->setStretchFactor(1, 1);

    auto* editor = new SampleEditor;
    vSplitter->addWidget(editor);
    vSplitter->setCollapsible(0, false);
    vSplitter->setStretchFactor(0, 1);

    //... Below central widget: realspace and script panels

    auto* hSplitter = new QSplitter;
    hSplitter->setOrientation(Qt::Horizontal);
    vSplitter->addWidget(hSplitter);
    vSplitter->setCollapsible(1, false);
    vSplitter->setStretchFactor(1, 0);

    hSplitter->addWidget(m_realspace_panel);
    hSplitter->setCollapsible(0, false);
    hSplitter->setStretchFactor(0, 0);

    auto* script_panel = new ScriptPanel(this);
    hSplitter->addWidget(script_panel);
    hSplitter->setCollapsible(1, false);
    hSplitter->setStretchFactor(1, 1);

    applySplitterPos();

    // unfix RealspacePanel size only on manual resize
    connect(hSplitter, &QSplitter::splitterMoved, [this] {
        m_realspace_panel->setMinimumWidth(1); // somehow with min width >0 it behaves better
        m_realspace_panel->setMaximumWidth(QWIDGETSIZE_MAX);
    });
    connect(vSplitter, &QSplitter::splitterMoved, [this] {
        m_realspace_panel->setMinimumHeight(0);
        m_realspace_panel->setMaximumHeight(QWIDGETSIZE_MAX);
    });

    //... Finish

    connect(gDoc->samples(), &SamplesSet::setChanged, [this, editor, script_panel] {
        SampleItem* t = gDoc->samplesRW()->currentItem();
        editor->setCurrentSample(t);
        onRequestViewInRealspace(t);
        script_panel->setCurrentSample(t);
        updateActions();
    });

    connect(gDoc.get(), &ProjectDocument::sampleChanged, script_panel,
            &ScriptPanel::onSampleModified);

    connect(editor, &SampleEditor::requestViewInRealspace, this,
            &SampleView::onRequestViewInRealspace);

    connect(editor, &SampleEditor::aboutToRemoveItem, this, &SampleView::onAboutToRemoveItem);

    connect(gDoc.get(), &ProjectDocument::sampleChanged, m_realspace_panel->widget(),
            &RealspaceWidget::updateRealScene);

    connect(gDoc.get(), &ProjectDocument::sampleChanged, gDoc.get(), &ProjectDocument::setModified);

    updateActions();
}

SampleView::~SampleView()
{
    saveSplitterPos();
}

void SampleView::createActions(QToolBar* toolbar)
{
    //... New sample action

    auto* new_sample_action = new QAction("New", this);
    new_sample_action->setIcon(QIcon(":/images/shape-square-plus.svg"));
    new_sample_action->setToolTip("Create new sample");
    toolbar->addAction(new_sample_action);

    connect(new_sample_action, &QAction::triggered, [this] {
        auto t = new SampleItem;
        t->addStandardMaterials();
        m_set->add_item(t);
    });

    //... Load and save actions

#ifdef BORNAGAIN_PYTHON
    auto* import_sample_action = new QAction("Import Py", this);
    import_sample_action->setIcon(QIcon(":/images/import.svg"));
    import_sample_action->setToolTip(
        "Import sample from a Python script that contains a function that returns a multi-layer");
    toolbar->addAction(import_sample_action);
    connect(import_sample_action, &QAction::triggered, [this] {
        SampleItem* sample_item = IO::Py::importSample();
        if (sample_item)
            m_set->add_item(sample_item);
    });
#endif

    auto* choose_from_library_action = new QAction("Examples", this);
    toolbar->addAction(choose_from_library_action);
    if (auto* btn =
            dynamic_cast<QToolButton*>(toolbar->widgetForAction(choose_from_library_action)))
        btn->setPopupMode(QToolButton::InstantPopup);
    choose_from_library_action->setIcon(QIcon(":/images/library.svg"));
    choose_from_library_action->setToolTip("Choose from sample examples");
    auto* import_menu = new QMenu(this);
    choose_from_library_action->setMenu(import_menu);
    for (const auto& example_name : GUI::ExamplesFactory::exampleNames()) {
        const auto [title, description] = GUI::ExamplesFactory::exampleInfo(example_name);
        auto icon = QIcon(":/images/sample_layers2.png");
        auto* action = import_menu->addAction(icon, title);
        action->setToolTip(description);
        connect(action, &QAction::triggered, [this, example_name] { loadExample(example_name); });
    }

    //... Copy and remove actions

    m_cp_action = ActionFactory::createCopyAction("sample", this);
    toolbar->addAction(m_cp_action);
    connect(m_cp_action, &QAction::triggered,
            [this] { m_set->add_item(m_set->currentItem()->clone()); });

    m_rm_action = ActionFactory::createRemoveAction("sample", this);
    toolbar->addAction(m_rm_action);
    connect(m_rm_action, &QAction::triggered, m_set, &SamplesSet::delete_current);
}

void SampleView::updateActions()
{
    bool enabled = m_set->currentIndex() != size_t(-1);
    m_rm_action->setEnabled(enabled);
    m_cp_action->setEnabled(enabled);
}

void SampleView::applySplitterPos()
{
    QSettings settings;
    if (settings.childGroups().contains(GUI::Style::S_SAMPLE_3DVIEW_WIDGET)) {
        settings.beginGroup(GUI::Style::S_SAMPLE_3DVIEW_WIDGET);
        m_realspace_panel->setFixedSize(
            settings.value(GUI::Style::S_SAMPLE_3DVIEW_WIDGET_SIZE).toSize());
        settings.endGroup();
    } else
        m_realspace_panel->setFixedSize(GUI::Style::SAMPLE_3DVIEW_WIDGET_WIDTH,
                                        GUI::Style::SAMPLE_3DVIEW_WIDGET_HEIGHT);
}

void SampleView::saveSplitterPos()
{
    QSettings settings;
    settings.beginGroup(GUI::Style::S_SAMPLE_3DVIEW_WIDGET);
    settings.setValue(GUI::Style::S_SAMPLE_3DVIEW_WIDGET_SIZE, m_realspace_panel->size());
    settings.endGroup();
    settings.sync();
}

void SampleView::onRequestViewInRealspace(Item3D* item)
{
    if (!item) {
        m_realspace_panel->widget()->clearDisplay();
        return;
    }

    m_realspace_panel->widget()->setDisplayedItem(gDoc->samplesRW()->currentItem(), item);
}

void SampleView::loadExample(const QString& example_name)
{
    try {
        SampleItem* t = GUI::ExamplesFactory::itemizeSample(example_name);
        ASSERT(t);
        m_set->add_item(t);
    } catch (const std::exception& ex) {
        GUI::Message::warning("Cannot load exemplary sample", ex.what());
    }
}

void SampleView::onAboutToRemoveItem(Item3D* item)
{
    // If an item shall be deleted from the sample, check whether Realspace is affected.
    // Its current item could be item itself, or it could be a child of item.

    // -- for convenience access later on
    auto* widget = m_realspace_panel->widget();
    const Item3D* displayed = widget->displayedItem();

    auto layerContainsDisplayerItem = [displayed](const LayerItem* layerItem) {
        return Vec::containsPtr(displayed, layerItem->layoutItems())
               || Vec::containsPtr(displayed, layerItem->itemsWithParticles());
    };

    if (dynamic_cast<SampleItem*>(item)) {
        // It is a sample. In this case: always reset
        widget->resetRealScene();

    } else if (const auto* i = dynamic_cast<const LayerStackItem*>(item)) {
        bool hasLayerWithDisplayedItem = false;
        const auto layerItems = i->uniqueLayerItems();
        for (const auto& li : layerItems)
            if (layerContainsDisplayerItem(li)) {
                hasLayerWithDisplayedItem = true;
                break;
            }
        if (item == widget->displayedItem() || hasLayerWithDisplayedItem)
            widget->resetRealScene();

    } else if (const auto* i = dynamic_cast<const LayerItem*>(item)) {
        if (item == widget->displayedItem() || layerContainsDisplayerItem(i))
            widget->resetRealScene();

    } else if (const auto* i = dynamic_cast<const ParticleLayoutItem*>(item)) {
        if (item == widget->displayedItem()
            || Vec::containsPtr(displayed, i->containedItemsWithParticles()))
            widget->resetRealScene();

    } else if (const auto* i = dynamic_cast<const ItemWithParticles*>(item)) {
        if (item == displayed || Vec::containsPtr(displayed, i->containedItemsWithParticles()))
            widget->resetRealScene();
    }
}
