//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/View/InstrumentView.cpp
//! @brief     Implements class InstrumentView.
//!
//! @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/InstrumentView.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/File/DatafileItem.h"
#include "GUI/Model/File/DatafilesSet.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/Model/Sim/InstrumentItems.h"
#include "GUI/Model/Sim/InstrumentsSet.h"
#include "GUI/View/IO/ComponentRW.h"
#include "GUI/View/Info/MessageBox.h"
#include "GUI/View/Instrument/DepthprobeInstrumentEditor.h"
#include "GUI/View/Instrument/OffspecInstrumentEditor.h"
#include "GUI/View/Instrument/Scatter2DInstrumentEditor.h"
#include "GUI/View/Instrument/SpecularInstrumentEditor.h"
#include "GUI/View/Modelview/SetView.h"
#include "GUI/View/Setup/ActionFactory.h"
#include "GUI/View/Widget/AppConfig.h"
#include "GUI/View/Widget/GroupBoxes.h"
#include "GUI/View/Widget/StyledToolbar.h"
#include <QFormLayout>
#include <QLineEdit>
#include <QSplitter>
#include <QTextEdit>

InstrumentView::InstrumentView()
    : m_set(gDoc->instrumentsRW())
    , m_qlistview(new SetView(m_set))
    , m_scroll_area(new QScrollArea)
{
    auto* layout = new QVBoxLayout(this);

    //... Top toolbar with action buttons

    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: instrument list

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

    //... Large block on the right: current instrument

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

    m_scroll_area->setWidgetResizable(true);
    m_scroll_area->setFixedWidth(950);
    m_scroll_area->setWidget(new QWidget(m_scroll_area)); // initial state: blank widget

    hListSplitter->addWidget(new QWidget);
    hListSplitter->setStretchFactor(2, 1);

    //... Finalize

    connect(gDoc->instruments(), &InstrumentsSet::setChanged, [this] {
        updateEditor();
        updateActions();
    });
    connect(gDoc->datafiles(), &DatafilesSet::setChanged, [this] { updateActions(); });

    updateActions();
}

void InstrumentView::createActions(QToolBar* toolbar)
{
    //... New-instrument actions

    auto new_action = [this, toolbar](const QString& name, const QString& description) {
        auto* a = new QAction("New " + name, this);
        a->setIcon(QIcon(":/images/shape-square-plus.svg"));
        a->setToolTip("Add new " + description + " instrument with default settings");
        toolbar->addAction(a);
        return a;
    };

    connect(new_action("GISAS", "2D scattering"), &QAction::triggered,
            [this] { m_set->add_item(new Scatter2DInstrumentItem); });

    connect(new_action("offspec", "off-specular"), &QAction::triggered,
            [this] { m_set->add_item(new OffspecInstrumentItem); });

    connect(new_action("specular", "specular reflectivity"), &QAction::triggered,
            [this] { m_set->add_item(new SpecularInstrumentItem); });

    connect(new_action("depthprobe", "depth intensity profile"), &QAction::triggered,
            [this] { m_set->add_item(new DepthprobeInstrumentItem); });

    //... Load

    m_load_action = new QAction("Load from XML", this);
    m_load_action->setIcon(QIcon(":/images/library.svg"));
    m_load_action->setToolTip("Load an instrument from the instrument library");
    toolbar->addAction(m_load_action);
    connect(m_load_action, &QAction::triggered, [this] {
        if (InstrumentItem* t = IO::loadComponentFromXML<InstrumentItem>("instrument"))
            m_set->add_item(t);
    });

    //... Actions on selected instrument

    m_adapt_action = new QAction("Adapt to data", this);
    m_adapt_action->setIcon(QIcon(":/images/from_data.svg"));
    m_adapt_action->setToolTip("Take axes from current data file");
    toolbar->addAction(m_adapt_action);
    connect(m_adapt_action, &QAction::triggered, [this] {
        const DatafileItem* current_data = gDoc->datafiles()->currentItem();
        ASSERT(current_data);
        if (!GUI::Message::question("Adapt instrument", "Adapt instrument dimensions to data",
                                    QString("%1?").arg(current_data->name()), "Yes", "No"))
            return;
        m_set->currentItem()->updateToRealData(current_data);
        m_scroll_area->setWidget(createEditor(m_set->currentItem()));
    });

    m_save_action = new QAction("Save as XML", this);
    m_save_action->setIcon(QIcon(":/images/library.svg"));
    m_save_action->setToolTip("Store instrument in library");
    toolbar->addAction(m_save_action);
    connect(m_save_action, &QAction::triggered,
            [this] { IO::saveComponentToXML("instrument", m_set->currentItem()); });

    m_cp_action = ActionFactory::createCopyAction("instrument", 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("instrument", this);
    toolbar->addAction(m_rm_action);
    connect(m_rm_action, &QAction::triggered, m_set, &InstrumentsSet::delete_current);
}

void InstrumentView::updateActions()
{
    ASSERT(m_set);
    bool enabled = m_set->currentIndex() != size_t(-1);
    m_adapt_action->setEnabled(enabled && !m_set->currentItem()->is<DepthprobeInstrumentItem>()
                               && !gDoc->datafiles()->empty()
                               && m_set->currentItem()->detectorRank()
                                      == gDoc->datafiles()->currentItem()->rank());
    m_rm_action->setEnabled(enabled);
    m_cp_action->setEnabled(enabled);
    m_save_action->setEnabled(enabled);
}

void InstrumentView::updateEditor()
{
    ASSERT(m_set);
    InstrumentItem* t = m_set->currentItem();
    if (t == m_displayed_item)
        return;
    m_displayed_item = t;
    if (!t)
        delete m_scroll_area->takeWidget();
    else
        m_scroll_area->setWidget(createEditor(t));
}

QWidget* InstrumentView::createEditor(InstrumentItem* t)
{
    auto* result = new QWidget(m_scroll_area);

    if (!t)
        return result; // blank widget

    auto* layout = new QVBoxLayout(result);

    //... Groupbox with instrument name and description (same layout for all instrument types)

    auto title = QString("Summary (%1 instrument)").arg(t->instrumentType());
    auto* g = new CollapsibleGroupBox(title, m_scroll_area, t->expandInfo);
    g->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    auto* formLayout = new QFormLayout;
    g->body()->setLayout(formLayout);
    layout->addWidget(g);

    auto* nameEdit = new QLineEdit(t->name(), g);
    connect(nameEdit, &QLineEdit::textEdited,
            [this](const QString& text) { m_set->setCurrentName(text); });
    formLayout->addRow("Name:", nameEdit);

    auto* descriptionEdit = new QTextEdit(g);
    descriptionEdit->setMinimumWidth(300);
    descriptionEdit->setFixedHeight(60); // TODO replace by 2*line_height
    descriptionEdit->setAcceptRichText(false);
    descriptionEdit->setTabChangesFocus(true);
    descriptionEdit->setPlainText(t->description());
    connect(descriptionEdit, &QTextEdit::textChanged, [this, descriptionEdit] {
        m_set->setCurrentDescription(descriptionEdit->toPlainText());
    });
    formLayout->addRow("Description:", descriptionEdit);

    //... All remaining content depends on instrument type

    IComponentEditor* editor;
    if (auto* ii = dynamic_cast<SpecularInstrumentItem*>(t))
        editor = new SpecularInstrumentEditor(ii);
    else if (auto* ii = dynamic_cast<OffspecInstrumentItem*>(t))
        editor = new OffspecInstrumentEditor(ii);
    else if (auto* ii = dynamic_cast<Scatter2DInstrumentItem*>(t))
        editor = new Scatter2DInstrumentEditor(ii);
    else if (auto* ii = dynamic_cast<DepthprobeInstrumentItem*>(t))
        editor = new DepthprobeInstrumentEditor(ii);
    else
        ASSERT_NEVER;

    layout->addWidget(editor);

    return result;
}
