//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Job/JobListView.cpp
//! @brief     Implements class JobListView
//!
//! @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/Job/JobListView.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Data/IntensityDataItem.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/View/Job/JobListModel.h"
#include "GUI/View/Job/JobListViewDelegate.h"
#include "GUI/View/Widget/StyledToolbar.h"
#include <QAction>
#include <QVBoxLayout>

namespace {

//! compare function for sorting indexes according to row descending
bool row_descending(const QModelIndex& idx1, const QModelIndex& idx2)
{
    return idx1.row() > idx2.row();
}

//! compare function for sorting indexes according to row asscending
bool row_ascending(const QModelIndex& idx1, const QModelIndex& idx2)
{
    return idx1.row() < idx2.row();
}

} // namespace

//==================================================================================================
// JobListView
//==================================================================================================

//--------------------------------------------------------------------------------------------------
// public member functions
//--------------------------------------------------------------------------------------------------

JobListView::JobListView(JobModel* jobs, QWidget* parent, Qt::WindowFlags f)
    : QWidget(parent, f)
{
    auto* layout = new QVBoxLayout(this);
    layout->setContentsMargins(0, 0, 0, 0);

    m_runAction = new QAction("Run", this);
    m_runAction->setIcon(QIcon(":/images/play.svg"));
    m_runAction->setToolTip("Run currently selected jobs");
    connect(m_runAction, &QAction::triggered, this, &JobListView::onRun);
    addAction(m_runAction);

    m_cancelAction = new QAction("Stop", this);
    m_cancelAction->setIcon(QIcon(":/images/stop.svg"));
    m_cancelAction->setToolTip("Stop currently selected jobs");
    connect(m_cancelAction, &QAction::triggered, this, &JobListView::onCancel);
    addAction(m_cancelAction);

    m_removeAction = new QAction("Remove", this);
    m_removeAction->setIcon(QIcon(":/images/delete.svg"));
    m_removeAction->setToolTip("Remove currently selected jobs");
    connect(m_removeAction, &QAction::triggered, this, &JobListView::onRemove);
    addAction(m_removeAction);

    m_equalizeMenu = new QMenu("Equalize selected plots", this);

    QToolBar* toolbar = new StyledToolbar(this);
    toolbar->setMinimumSize(toolbar->minimumHeight(), toolbar->minimumHeight());
    toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
    toolbar->addAction(m_runAction);
    toolbar->addAction(m_cancelAction);
    toolbar->addAction(m_removeAction);
    layout->addWidget(toolbar);

    m_listView = new QListView(this);
    m_listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
    m_listViewDelegate = new JobListViewDelegate(this);
    connect(m_listViewDelegate, &JobListViewDelegate::cancelButtonClicked, this,
            &JobListView::onCancel);
    m_listView->setItemDelegate(m_listViewDelegate);
    layout->addWidget(m_listView);

    m_model = new JobListModel(jobs, this);
    m_listView->setModel(m_model);

    setContextMenuPolicy(Qt::CustomContextMenu);
    connect(this, &QWidget::customContextMenuRequested, this, &JobListView::showContextMenu);

    connect(m_listView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
            &JobListView::onItemSelectionChanged);
    connect(m_model, &QAbstractListModel::dataChanged, this,
            &JobListView::onJobListModelDataChanged);

    updateActions();
    restoreSelection();

    setMinimumWidth(10);
}

QVector<JobItem*> JobListView::selectedJobItems() const
{
    QVector<JobItem*> jobs;
    for (const QModelIndex& index : m_listView->selectionModel()->selectedIndexes())
        jobs.push_back(m_model->jobItemForIndex(index));
    return jobs;
}

void JobListView::selectJob(JobItem* job)
{
    QModelIndex idx = m_model->indexForJob(job);
    QModelIndexList selected = m_listView->selectionModel()->selectedIndexes();

    // Already selected, but we still will emit the signal to notify widgets.
    // To handle the case, when the job was selected before it completed (and some stack widgets
    // were refusing to show the content for non-complete job).
    if (selected.size() == 1 && selected.front() == idx) {
        emit selectedJobsChanged({job});
        return;
    }

    m_listView->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect);
}

//--------------------------------------------------------------------------------------------------
// private slots
//--------------------------------------------------------------------------------------------------
void JobListView::onItemSelectionChanged()
{
    updateActions();

    QModelIndexList selected = m_listView->selectionModel()->selectedIndexes();
    if (selected.size() == 1)
        gProjectDocument.value()->jobModel()->setSelectedIndex(selected.first().row());

    emit selectedJobsChanged(selectedJobItems());
}

void JobListView::onJobListModelDataChanged(const QModelIndex& topLeft,
                                            const QModelIndex& bottomRight)
{
    // currently only single items change, not ranges; thus ranges are not supported
    ASSERT(topLeft == bottomRight);

    if (m_listView->selectionModel()->isSelected(topLeft))
        updateActions();
}

void JobListView::onRun()
{
    for (const QModelIndex& index : m_listView->selectionModel()->selectedIndexes())
        m_model->runJob(index);
    gProjectDocument.value()->setModified();
}

void JobListView::onCancel()
{
    for (const QModelIndex& index : m_listView->selectionModel()->selectedIndexes())
        m_model->cancelJob(index);
    gProjectDocument.value()->setModified();
}

void JobListView::onRemove()
{
    QModelIndexList indexes = m_listView->selectionModel()->selectedIndexes();
    ASSERT(!indexes.isEmpty());
    std::sort(indexes.begin(), indexes.end(), row_descending);
    for (const QModelIndex& index : indexes)
        m_model->removeJob(index);

    int lastSelectedRow = indexes.front().row();
    ensureItemSelected(lastSelectedRow);

    gProjectDocument.value()->setModified();
}

void JobListView::equalizeSelectedToJob(JobItem* srcJob)
{
    QModelIndexList indexes = m_listView->selectionModel()->selectedIndexes();

    IntensityDataItem* srcData = srcJob->intensityDataItem();
    if (!srcData)
        return;

    for (const QModelIndex& index : indexes) {
        JobItem* job = m_model->jobItemForIndex(index);
        if (job != srcJob) {
            IntensityDataItem* data = job->intensityDataItem();
            if (data) {
                data->setLowerX(srcData->lowerX());
                data->setUpperX(srcData->upperX());
                data->setLowerY(srcData->lowerY());
                data->setUpperY(srcData->upperY());
                data->setLowerZ(srcData->lowerZ());
                data->setUpperZ(srcData->upperZ());
            }
        }
    }
    gProjectDocument.value()->setModified();
}

void JobListView::showContextMenu(const QPoint&)
{
    QMenu menu(this);
    menu.addAction(m_runAction);
    menu.addAction(m_cancelAction);
    menu.addAction(m_removeAction);
    menu.addSeparator();

    m_equalizeMenu->clear();
    QModelIndexList indexes = m_listView->selectionModel()->selectedIndexes();
    if (indexes.size() > 1) {
        std::sort(indexes.begin(), indexes.end(), row_ascending);
        for (const QModelIndex& index : indexes) {
            JobItem* job = m_model->jobItemForIndex(index);
            QAction* action = m_equalizeMenu->addAction(QString("to ").append(job->jobName()));
            connect(action, &QAction::triggered, this, [this, job] { equalizeSelectedToJob(job); });
        }
        m_equalizeMenu->setEnabled(true);
    } else
        m_equalizeMenu->setEnabled(false);
    menu.addMenu(m_equalizeMenu);
    menu.exec(QCursor::pos());
}

//--------------------------------------------------------------------------------------------------
// private member functions
//--------------------------------------------------------------------------------------------------

void JobListView::updateActions()
{
    QModelIndexList indexes = m_listView->selectionModel()->selectedIndexes();

    struct IsRunningOrFitting {
        JobListModel* m_model;
        IsRunningOrFitting(JobListModel* model)
            : m_model(model)
        {
        }
        bool operator()(const QModelIndex& i) const
        {
            JobItem* job = m_model->jobItemForIndex(i);
            ASSERT(job);
            return job->isRunning() || job->isFitting();
        }
    };

    bool none_running = std::none_of(indexes.begin(), indexes.end(), IsRunningOrFitting(m_model));
    bool all_running = std::all_of(indexes.begin(), indexes.end(), IsRunningOrFitting(m_model));
    bool nonempty = !indexes.empty();
    m_runAction->setEnabled(nonempty && none_running);
    m_cancelAction->setEnabled(nonempty && all_running);
    m_removeAction->setEnabled(nonempty && none_running);
}

void JobListView::ensureItemSelected(int lastSelectedRow)
{
    if (!m_listView->selectionModel()->hasSelection() && m_model->rowCount()) {
        QModelIndex last = m_model->index(m_model->rowCount() - 1, 0, QModelIndex());
        if (lastSelectedRow >= 0 && lastSelectedRow < m_model->rowCount())
            last = m_model->index(lastSelectedRow, 0, QModelIndex());
        m_listView->selectionModel()->select(last, QItemSelectionModel::ClearAndSelect);
    }
}

void JobListView::restoreSelection()
{
    int lastUsed = gProjectDocument.value()->jobModel()->selectedIndex();
    if (lastUsed >= 0 && lastUsed < m_model->rowCount()) {
        QModelIndex lastUsedIndex = m_model->index(lastUsed, 0, QModelIndex());
        m_listView->selectionModel()->select(lastUsedIndex, QItemSelectionModel::ClearAndSelect);
    } else
        ensureItemSelected();
}
