//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Fit/ParameterTuningWidget.cpp
//! @brief     Implements class ParameterTuningWidget
//!
//! @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/Fit/ParameterTuningWidget.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Data/IntensityDataItem.h"
#include "GUI/Model/Job/ParameterTreeItems.h"
#include "GUI/Model/Model/ParameterTuningModel.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/View/Fit/ParameterTuningDelegate.h"
#include "GUI/View/Fit/SliderSettingsWidget.h"
#include "GUI/View/Info/CautionSign.h"
#include "GUI/View/Widget/StyledToolbar.h"
#include <QAction>
#include <QTreeView>
#include <QVBoxLayout>

ParameterTuningWidget::ParameterTuningWidget(QWidget* parent)
    : DataAccessWidget(parent)
    , m_jobModel(nullptr)
    , m_parameterTuningModel(nullptr)
    , m_sliderSettingsWidget(new SliderSettingsWidget(this))
    , m_treeView(new QTreeView)
    , m_delegate(new ParameterTuningDelegate(this))
    , m_cautionSign(new CautionSign(m_treeView))
{
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    m_treeView->setItemDelegate(m_delegate);
    m_treeView->setContextMenuPolicy(Qt::CustomContextMenu);
    m_treeView->setDragDropMode(QAbstractItemView::NoDragDrop);
    m_treeView->setAttribute(Qt::WA_MacShowFocusRect, false);

    auto* resetValuesAction = new QAction(QIcon(":/images/undo-variant.svg"), "Reset values", this);
    resetValuesAction->setToolTip("Reset parameter tree to initial values");
    connect(resetValuesAction, &QAction::triggered, this,
            &ParameterTuningWidget::restoreModelsOfCurrentJobItem);

    auto* toolbar = new StyledToolbar(this);
    toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
    toolbar->addAction(resetValuesAction);

    auto* mainLayout = new QVBoxLayout;
    mainLayout->setContentsMargins(0, 0, 0, 0);
    mainLayout->setSpacing(0);
    mainLayout->addWidget(toolbar);
    mainLayout->addWidget(m_sliderSettingsWidget);
    mainLayout->addWidget(m_treeView);
    setLayout(mainLayout);

    connect(m_sliderSettingsWidget, &SliderSettingsWidget::sliderRangeFactorChanged, this,
            &ParameterTuningWidget::onSliderRangeChanged);
    connect(m_sliderSettingsWidget, &SliderSettingsWidget::lockzChanged, this,
            &ParameterTuningWidget::onLockZValueChanged);
    connect(m_delegate, &ParameterTuningDelegate::currentLinkChanged, this,
            &ParameterTuningWidget::onCurrentLinkChanged);
    connect(m_treeView, &QTreeView::customContextMenuRequested, this,
            &ParameterTuningWidget::onCustomContextMenuRequested);
}

void ParameterTuningWidget::setJobOrRealItem(QObject* job_item)
{
    DataAccessWidget::setJobOrRealItem(job_item);
    m_sliderSettingsWidget->setJobOrRealItem(job_item);

    updateParameterModel();
    updateDragAndDropSettings();

    connect(jobItem(), &JobItem::jobStatusChanged, this,
            [this](const JobStatus) { updateJobStatus(); });

    updateJobStatus();
}

void ParameterTuningWidget::setModel(QObject* jobModel)
{
    m_jobModel = dynamic_cast<JobModel*>(jobModel);
    ASSERT(m_jobModel);
}

QItemSelectionModel* ParameterTuningWidget::selectionModel()
{
    ASSERT(m_treeView);
    return m_treeView->selectionModel();
}

//! Returns list of ParameterItem's currently selected in parameter tree

QVector<ParameterItem*> ParameterTuningWidget::selectedParameterItems()
{
    QVector<ParameterItem*> result;
    for (auto index : selectionModel()->selectedIndexes())
        if (ParameterItem* parItem = m_parameterTuningModel->getParameterItem(index))
            result.push_back(parItem);

    return result;
}

void ParameterTuningWidget::onCurrentLinkChanged(ParameterItem* item)
{
    ASSERT(jobItem());

    if (jobItem()->isRunning())
        return;

    if (item)
        m_jobModel->runJob(jobItem());
}

void ParameterTuningWidget::onSliderRangeChanged(int value)
{
    m_delegate->setSliderRangeFactor(value);
}

void ParameterTuningWidget::onLockZValueChanged(bool value)
{
    if (!jobItem())
        return;
    if (IntensityDataItem* intensityDataItem = jobItem()->intensityDataItem())
        intensityDataItem->setZaxisLocked(value);
}

void ParameterTuningWidget::updateParameterModel()
{
    ASSERT(m_jobModel);

    if (!jobItem())
        return;

    if (!jobItem()->sampleItem() || !jobItem()->instrumentItem())
        throw std::runtime_error("JobItem is missing sample or instrument model");

    delete m_parameterTuningModel;
    m_parameterTuningModel =
        new ParameterTuningModel(jobItem()->parameterContainerItem()->parameterTreeRoot(), this);

    m_treeView->setModel(m_parameterTuningModel);
    if (m_treeView->columnWidth(0) < 170)
        m_treeView->setColumnWidth(0, 170);
    m_treeView->expandAll();
}

void ParameterTuningWidget::onCustomContextMenuRequested(const QPoint& point)
{
    emit itemContextMenuRequest(m_treeView->mapToGlobal(point + QPoint(2, 22)));
}

void ParameterTuningWidget::restoreModelsOfCurrentJobItem()
{
    ASSERT(m_jobModel);
    ASSERT(jobItem());

    if (jobItem()->isRunning())
        return;

    closeActiveEditors();

    m_jobModel->restore(jobItem());
    m_jobModel->runJob(jobItem());
    gProjectDocument.value()->setModified();
}

void ParameterTuningWidget::makeSelected(ParameterItem* item)
{
    QModelIndex index = m_parameterTuningModel->indexForItem(item);
    if (index.isValid())
        selectionModel()->select(index, QItemSelectionModel::Select);
}

void ParameterTuningWidget::contextMenuEvent(QContextMenuEvent*)
{
    // reimplemented to suppress context menu from QMainWindow
}

//! Disable drag-and-drop abilities, if job is in fit running state.

void ParameterTuningWidget::updateDragAndDropSettings()
{
    ASSERT(jobItem());
    if (jobItem()->status() == JobStatus::Fitting) {
        setTuningDelegateEnabled(false);
        m_treeView->setDragDropMode(QAbstractItemView::NoDragDrop);
    } else {
        setTuningDelegateEnabled(true);
        if (jobItem()->isValidForFitting())
            m_treeView->setDragDropMode(QAbstractItemView::DragOnly);
    }
}

//! Sets delegate to enabled/disabled state.
//! In 'disabled' state the delegate is in ReadOnlyMode, if it was containing already some
//! editing widget, it will be forced to close.
void ParameterTuningWidget::setTuningDelegateEnabled(bool enabled)
{
    if (enabled)
        m_delegate->setReadOnly(false);
    else {
        m_delegate->setReadOnly(true);
        closeActiveEditors();
    }
}

void ParameterTuningWidget::closeActiveEditors()
{
    QModelIndex index = m_treeView->currentIndex();
    QWidget* editor = m_treeView->indexWidget(index);
    if (editor) {
        // m_delegate->commitData(editor);
        m_delegate->closeEditor(editor, QAbstractItemDelegate::NoHint);
    }
    m_treeView->selectionModel()->clearSelection();
}

void ParameterTuningWidget::updateJobStatus()
{
    m_cautionSign->clear();

    if (jobItem()->isFailed()) {
        QString message;
        message.append("Current parameter values cause simulation failure.\n\n");
        message.append(jobItem()->comments());
        m_cautionSign->setCautionMessage(message);
    }

    updateDragAndDropSettings();
}
