//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Tuning/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/Tuning/ParameterTuningWidget.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Data/Data2DItem.h"
#include "GUI/Model/Job/BatchInfo.h"
#include "GUI/Model/Job/JobItem.h"
#include "GUI/Model/Job/JobStatus.h"
#include "GUI/Model/Job/JobsSet.h"
#include "GUI/Model/Par/ParameterTreeItems.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/View/Base/mainwindow_constants.h"
#include "GUI/View/Info/CautionSign.h"
#include "GUI/View/Job/Simulate.h"
#include "GUI/View/ParEdit/ParameterTuningDelegate.h"
#include "GUI/View/Tuning/ParameterBackupWidget.h"
#include "GUI/View/Tuning/PartunerQModel.h"
#include "GUI/View/Tuning/SliderEditor.h"
#include "GUI/View/Widget/StyledToolbar.h"
#include <QAction>
#include <QHeaderView>
#include <QSettings>
#include <QVBoxLayout>

namespace {
void expandChildren(const QModelIndex& parent, QTreeView* view)
{
    int childCount = view->model()->rowCount(parent);
    for (int i = 0; i < childCount; i++) {
        const QModelIndex& child = view->model()->index(i, 0, parent);
        expandChildren(child, view);
    }

    if (view->model()->data(parent, Qt::UserRole).toBool())
        view->collapse(parent);
    else
        view->expand(parent);
}

int currentColumnWidth = GUI::Style::PARAMETER_TUNING_WIDGET_COLUMN_WIDTH;
} // namespace

ParameterTuningWidget::ParameterTuningWidget()
    : m_jobs(nullptr)
    , m_parameter_tuning_model(nullptr)
    , m_backup_widget(new ParameterBackupWidget)
    , m_slider_settings_widget(new SliderEditor)
    , m_tree_view(new QTreeView)
    , m_delegate(new ParameterTuningDelegate)
    , m_caution_sign(new CautionSign(m_tree_view))
{
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    m_tree_view->setItemDelegate(m_delegate); // the delegate creates view&control for one param
    m_tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
    m_tree_view->setDragDropMode(QAbstractItemView::NoDragDrop);
    m_tree_view->setAttribute(Qt::WA_MacShowFocusRect, false);
    m_tree_view->setEditTriggers(QAbstractItemView::AllEditTriggers);

    auto* mainLayout = new QVBoxLayout;
    mainLayout->setContentsMargins(0, 0, 0, 0);
    mainLayout->setSpacing(0);
    mainLayout->addWidget(m_backup_widget);
    mainLayout->addWidget(m_slider_settings_widget);
    mainLayout->addWidget(m_tree_view);
    setLayout(mainLayout);

    connect(m_slider_settings_widget, &SliderEditor::sliderRangeFactorChanged, this,
            &ParameterTuningWidget::onSliderRangeChanged);
    connect(m_slider_settings_widget, &SliderEditor::lockzChanged, this,
            &ParameterTuningWidget::onLockZValueChanged);
    connect(m_delegate, &ParameterTuningDelegate::currentLinkChanged, this,
            &ParameterTuningWidget::onCurrentLinkChanged);
    connect(m_tree_view, &QTreeView::customContextMenuRequested, this,
            &ParameterTuningWidget::onCustomContextMenuRequested);
    connect(m_backup_widget, &ParameterBackupWidget::backupSwitched, this,
            &ParameterTuningWidget::restoreModelsOfCurrentJobItem);


    applySettings();
}

ParameterTuningWidget::~ParameterTuningWidget()
{
    saveSettings();
}

void ParameterTuningWidget::saveSettings()
{
    QSettings settings;
    settings.beginGroup(GUI::Style::S_PARAMETER_TUNING_WIDGET);
    settings.setValue(GUI::Style::S_PARAMETER_TUNING_WIDGET_COLUMN_WIDTH, ::currentColumnWidth);
    settings.endGroup();
    settings.sync();
}

void ParameterTuningWidget::applySettings()
{
    QSettings settings;
    if (settings.childGroups().contains(GUI::Style::S_PARAMETER_TUNING_WIDGET)) {
        settings.beginGroup(GUI::Style::S_PARAMETER_TUNING_WIDGET);
        ::currentColumnWidth =
            settings.value(GUI::Style::S_PARAMETER_TUNING_WIDGET_COLUMN_WIDTH).toInt();
        settings.endGroup();
    }
}

void ParameterTuningWidget::setJobItem(JobItem* job_item)
{
    m_job_item = job_item;
    m_slider_settings_widget->setJobItem(job_item);

    if (!m_job_item)
        return;

    m_backup_widget->setParameterContainer(m_job_item->parameterContainerItem());

    updateParameterModel();
    updateDragAndDropSettings();

    connect(m_job_item, &QObject::destroyed, this, &ParameterTuningWidget::onJobDestroyed,
            Qt::UniqueConnection);
    connect(m_job_item->batchInfo(), &BatchInfo::jobStatusChanged, this,
            &ParameterTuningWidget::updateJobStatus, Qt::UniqueConnection);

    updateJobStatus(m_job_item->batchInfo()->status());
}

void ParameterTuningWidget::setModel(QObject* jobs)
{
    m_jobs = dynamic_cast<JobsSet*>(jobs);
    ASSERT(m_jobs);
}

QItemSelectionModel* ParameterTuningWidget::selectionModel()
{
    ASSERT(m_tree_view);
    return m_tree_view->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_parameter_tuning_model->getParameterItem(index))
            result.push_back(parItem);

    return result;
}

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

    if (isRunning(m_job_item->batchInfo()->status()))
        return;

    if (item)
        GUI::Sim::simulate(m_job_item, m_jobs);
}

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

void ParameterTuningWidget::onLockZValueChanged(bool value)
{
    if (!m_job_item)
        return;
    if (Data2DItem* data_item = m_job_item->data2DItem())
        data_item->setZaxisLocked(value);
}

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

    if (!m_job_item)
        return;

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

    m_parameter_tuning_model.reset(
        new PartunerQModel(m_job_item->parameterContainerItem()->parameterTreeRoot(), this));

    m_tree_view->setModel(m_parameter_tuning_model.get());
    m_tree_view->setColumnWidth(0, ::currentColumnWidth);
    expandChildren(m_tree_view->rootIndex(), m_tree_view);

    connect(m_tree_view, &QTreeView::expanded, m_parameter_tuning_model.get(),
            &PartunerQModel::setExpanded, Qt::UniqueConnection);
    connect(m_tree_view, &QTreeView::collapsed, m_parameter_tuning_model.get(),
            &PartunerQModel::setCollapsed, Qt::UniqueConnection);

    connect(m_tree_view->header(), &QHeaderView::sectionResized, this,
            &ParameterTuningWidget::onSectionResized, Qt::UniqueConnection);
}

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

void ParameterTuningWidget::onJobDestroyed()
{
    m_job_item = nullptr;
    m_parameter_tuning_model.reset(nullptr);
    m_tree_view->setModel(nullptr);
}

void ParameterTuningWidget::onSectionResized(int i, int, int n)
{
    if (i == 0)
        ::currentColumnWidth = n;
}

void ParameterTuningWidget::restoreModelsOfCurrentJobItem(int index)
{
    ASSERT(m_jobs);
    ASSERT(m_job_item);

    if (isRunning(m_job_item->batchInfo()->status()))
        return;

    closeActiveEditors();

    m_jobs->restoreBackupPars(m_job_item, index);
    GUI::Sim::simulate(m_job_item, m_jobs);
    updateView();
    gDoc->setModified();
}

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

void ParameterTuningWidget::updateView()
{
    m_tree_view->update();
}

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(m_job_item);
    if (m_job_item->batchInfo()->status() == JobStatus::Fitting) {
        setTuningDelegateEnabled(false);
        m_tree_view->setDragDropMode(QAbstractItemView::NoDragDrop);
    } else {
        setTuningDelegateEnabled(true);
        if (m_job_item->isValidForFitting())
            m_tree_view->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_tree_view->currentIndex();
    if (QWidget* editor = m_tree_view->indexWidget(index))
        m_delegate->closeEditor(editor, QAbstractItemDelegate::NoHint);
    m_tree_view->selectionModel()->clearSelection();
}

void ParameterTuningWidget::updateJobStatus(const JobStatus status)
{
    ASSERT(m_job_item);
    m_caution_sign->clear();

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

    updateDragAndDropSettings();
}
