//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/SampleDesigner/SampleListView.cpp
//! @brief     Implements class SampleListView
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2021
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/View/SampleDesigner/SampleListView.h"
#include "Base/Util/SysUtil.h"
#include "GUI/Application/ApplicationSettings.h"
#include "GUI/Model/FromCore/GUIExamplesFactory.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/Model/Sample/SampleItem.h"
#include "GUI/View/Item/ItemViewOverlayButtons.h"
#include "GUI/View/SampleDesigner/SampleListModel.h"
#include "GUI/View/Tool/ItemDelegateForHTML.h"
#include <QAction>
#include <QMenu>
#include <QPainter>

namespace {

class ItemDelegateForSampleTree : public ItemDelegateForHTML {
public:
    ItemDelegateForSampleTree(QObject* parent)
        : ItemDelegateForHTML(parent)
    {
    }

protected:
    void paint(QPainter* painter, const QStyleOptionViewItem& option,
               const QModelIndex& index) const override
    {
        ItemDelegateForHTML::paint(painter, option, index);

        QStyleOptionViewItem options = option;
        initStyleOption(&options, index);

        painter->save();

        painter->setPen(QPen(Qt::lightGray, 1));
        painter->drawLine(options.rect.left(), options.rect.bottom(), options.rect.right(),
                          options.rect.bottom());
        painter->restore();
    }

    QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override
    {
        auto s = ItemDelegateForHTML::sizeHint(option, index);
        s.setHeight(std::max(s.height(), 32));
        return s;
    }
};

} // namespace

SampleListView::SampleListView(QWidget* parent, ProjectDocument* document)
    : QListView(parent)
    , m_document(document)
{
    m_model = new SampleListModel(this, document->sampleModel());

    setContextMenuPolicy(Qt::CustomContextMenu);
    setModel(m_model);
    setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);

    ItemViewOverlayButtons::install(
        this, [this](const QModelIndex& i, bool h) { return getOverlayActions(i, h); });
    setItemDelegate(new ItemDelegateForSampleTree(this));

    connect(selectionModel(), &QItemSelectionModel::currentChanged, this,
            &SampleListView::onCurrentChanged);

    connect(this, &QWidget::customContextMenuRequested, this, &SampleListView::showContextMenu);

    m_newSampleAction = new QAction(this);
    m_newSampleAction->setText("Create new sample");
    m_newSampleAction->setIcon(QIcon(":/images/shape-square-plus.svg"));
    m_newSampleAction->setIconText("New");
    m_newSampleAction->setToolTip("Create new sample");
    connect(m_newSampleAction, &QAction::triggered, this, &SampleListView::createNewSample);

    m_importSampleAction = new QAction(this);
    m_importSampleAction->setText("Import sample from python script (experimental)");
    m_importSampleAction->setIcon(QIcon(":/images/import.svg"));
    m_importSampleAction->setIconText("Import");
    m_importSampleAction->setToolTip(
        "Import sample from Python script.\n The script should contain a function "
        "returning a valid multi-layer.");

#ifdef BORNAGAIN_PYTHON
    connect(m_importSampleAction, &QAction::triggered, this,
            &SampleListView::importSampleFromPython);
#else
    m_importSampleAction->setVisible(false);
#endif

    m_chooseFromLibraryAction = new QAction(this);
    m_chooseFromLibraryAction->setText("Choose from sample examples");
    m_chooseFromLibraryAction->setIcon(QIcon(":/images/library.svg"));
    m_chooseFromLibraryAction->setIconText("Examples");
    m_chooseFromLibraryAction->setToolTip("Choose from sample examples");

    auto* menu = new QMenu(this);
    m_chooseFromLibraryAction->setMenu(menu);

    for (const auto& exampleName : GUI::ExamplesFactory::exampleNames()) {
        QString title, description;
        std::tie(title, description) = GUI::ExamplesFactory::exampleInfo(exampleName);
        auto icon = QIcon(":/SampleDesignerToolbox/images/sample_layers2.png");
        auto* action = menu->addAction(icon, title);
        action->setToolTip(description);
        connect(action, &QAction::triggered, [this, exampleName, title, description]() {
            createSampleFromLibrary(exampleName, title, description);
        });
    }
}

void SampleListView::setCurrentSample(SampleItem* sample)
{
    setCurrentIndex(m_model->indexForItem(sample));
}

SampleItem* SampleListView::currentSampleItem()
{
    return m_model->itemForIndex(currentIndex());
}

QAction* SampleListView::newSampleAction()
{
    return m_newSampleAction;
}

QAction* SampleListView::importSampleAction()
{
    return m_importSampleAction;
}

QAction* SampleListView::chooseFromLibraryAction()
{
    return m_chooseFromLibraryAction;
}

QSize SampleListView::sizeHint() const
{
    QSize s = QListView::sizeHint();
    s.setWidth(std::max(300, s.width()));
    return s;
}

void SampleListView::onSampleModified()
{
    // if length of description changes height of TreeItem, the position of the overlay buttons
    // has to be updated -> schedule a re-layout
    scheduleDelayedItemsLayout();
}

void SampleListView::createNewSample()
{
    const QModelIndex newIndex = m_model->createSample();
    setCurrentIndex(newIndex);
    m_document->setModified();
}

void SampleListView::createSampleFromLibrary(const QString& classname, const QString& title,
                                             const QString& description)
{
    const QModelIndex newIndex = m_model->createSampleFromExamples(classname, title, description);
    if (newIndex.isValid()) {
        setCurrentIndex(newIndex);
        m_document->setModified();
    }
}

void SampleListView::importSampleFromPython()
{
#ifdef BORNAGAIN_PYTHON
    const QModelIndex newIndex = m_model->createSampleFromPython();
    if (newIndex.isValid()) {
        setCurrentIndex(newIndex);
        m_document->setModified();
    }
#endif
}

QList<QAction*> SampleListView::getOverlayActions(const QModelIndex& index, bool asHover)
{
    if (!asHover)
        return {};

    auto* item = m_model->itemForIndex(index);
    if (item == nullptr)
        return {};

    return {createRemoveAction(this, item)};
}

void SampleListView::onCurrentChanged(const QModelIndex& index)
{
    m_document->sampleModel()->setSelectedIndex(index.row());
    emit currentSampleChanged(m_model->itemForIndex(index));
}

void SampleListView::restoreSelection()
{
    int selected = m_document->sampleModel()->selectedIndex();
    if (selected < 0 || selected >= m_model->rowCount())
        selected = m_model->rowCount() - 1;

    QModelIndex selectedIndex = m_model->index(selected, 0, QModelIndex());
    if (selectedIndex.isValid()) {
        selectionModel()->select(selectedIndex, QItemSelectionModel::ClearAndSelect);
        setCurrentSample(m_document->sampleModel()->sampleItems()[selected]);
    }
}

void SampleListView::removeSample(SampleItem* item)
{
    m_model->removeSample(item);
    m_document->setModified();
}

QAction* SampleListView::createRemoveAction(QObject* parent, SampleItem* item)
{
    auto* removeAction = new QAction(parent);
    removeAction->setText("Remove");
    removeAction->setIcon(QIcon(":/images/delete.svg"));
    removeAction->setIconText("Remove");
    removeAction->setToolTip("Remove this sample");
    connect(removeAction, &QAction::triggered, [this, item]() { removeSample(item); });

    return removeAction;
}

void SampleListView::showContextMenu(const QPoint& pos)
{
    auto* sampleAtPoint = m_model->itemForIndex(indexAt(pos));

    QMenu menu;
    menu.setToolTipsVisible(true);

    if (sampleAtPoint != nullptr) {
        menu.addAction(createRemoveAction(&menu, sampleAtPoint));
        menu.addSeparator();
    }

    menu.addAction(m_newSampleAction);
    menu.addAction(m_chooseFromLibraryAction);
    menu.exec(mapToGlobal(pos));
}
