//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/FitControl/FitEditor.cpp
//! @brief     Implements class FitEditor.
//!
//! @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/FitControl/FitEditor.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Job/BatchInfo.h"
#include "GUI/Model/Job/JobItem.h"
#include "GUI/Model/Job/JobStatus.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/Model/Tune/FitSuiteItem.h"
#include "GUI/View/Base/Fontsize.h"
#include "GUI/View/Base/mainwindow_constants.h"
#include "GUI/View/Info/CautionSign.h"
#include <QHBoxLayout>

namespace {

const int default_interval = 10;
const QVector<int> slider_to_interval = {1,  2,  3,  4,   5,   10,  15,  20,
                                         25, 30, 50, 100, 200, 500, 1000};
const QString slider_tooltip = "Updates fit progress every Nth iteration";

} // namespace

FitEditor::FitEditor()
    : m_start_button(new QPushButton)
    , m_stop_button(new QPushButton)
    , m_upd_button(new QPushButton)
    , m_interval_slider(new QSlider)
    , m_update_interval_label(new QLabel)
    , m_iterations_count_label(new QLabel)
    , m_caution_sign(new CautionSign(this))
{
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    setFixedHeight(GUI::Style::RUN_FIT_CONTROL_WIDGET_HEIGHT);

    m_start_button->setText("Run");
    m_start_button->setToolTip("Run fitting");
    m_start_button->setMaximumWidth(80);

    m_stop_button->setText("Stop");
    m_stop_button->setToolTip("Interrupt fitting");
    m_stop_button->setMaximumWidth(80);

    m_upd_button->setText("Update start values");
    m_upd_button->setToolTip("Set the starting positions to the values from the parameter tree");
    m_upd_button->setMaximumWidth(130);

    m_interval_slider->setToolTip(slider_tooltip);
    m_interval_slider->setOrientation(Qt::Horizontal);
    m_interval_slider->setRange(0, static_cast<int>(slider_to_interval.size()) - 1);
    m_interval_slider->setMaximumWidth(120);
    m_interval_slider->setMinimumWidth(120);
    m_interval_slider->setFocusPolicy(Qt::NoFocus);
    m_interval_slider->setValue(5);

    QFont font("Monospace", GUI::Style::fontSizeSmall(), QFont::Normal);
    font.setPointSize(GUI::Style::fontSizeSmaller());
    m_update_interval_label->setToolTip(slider_tooltip);
    m_update_interval_label->setFont(font);
    m_update_interval_label->setText(QString::number(sliderUpdateInterval()));

    auto* layout = new QHBoxLayout;
    layout->setSpacing(0);
    layout->addWidget(m_start_button);
    layout->addSpacing(5);
    layout->addWidget(m_stop_button);
    layout->addSpacing(5);
    layout->addWidget(m_upd_button);
    layout->addSpacing(15);
    layout->addWidget(m_interval_slider);
    layout->addSpacing(2);
    layout->addWidget(m_update_interval_label);
    layout->addSpacing(5);
    layout->addStretch();
    layout->addWidget(m_iterations_count_label);
    setLayout(layout);

    connect(m_start_button, &QPushButton::clicked, [&] { emit startFittingPushed(); });
    connect(m_stop_button, &QPushButton::clicked, [&] { emit stopFittingPushed(); });
    connect(m_upd_button, &QPushButton::clicked, [&] { emit updFromTreePushed(); });
    connect(m_interval_slider, &QSlider::valueChanged, [&](int i) { onSliderValueChanged(i); });

    setEnabled(false);
}

void FitEditor::setJobItem(JobItem* job_item)
{
    ASSERT(job_item);
    m_job_item = job_item;

    updateControlElements(m_job_item->batchInfo()->status());
    updateIterationsCountLabel(fitSuiteItem()->iterationCount());

    initializeSlider();

    connect(fitSuiteItem(), &FitSuiteItem::iterationCountChanged, this,
            &FitEditor::updateIterationsCountLabel, Qt::UniqueConnection);

    connect(m_job_item, &QObject::destroyed, this, &FitEditor::onJobDestroyed,
            Qt::UniqueConnection);
    connect(m_job_item->batchInfo(), &BatchInfo::jobStatusChanged, this,
            &FitEditor::updateControlElements, Qt::UniqueConnection);
}

void FitEditor::onFittingError(const QString& what)
{
    m_caution_sign->clear();
    m_iterations_count_label->setText("");
    m_caution_sign->setCautionMessage(what);
}

void FitEditor::onSliderValueChanged(int value)
{
    int interval = sliderValueToUpdateInterval(value);
    m_update_interval_label->setText(QString::number(interval));
    if (fitSuiteItem())
        fitSuiteItem()->setUpdateInterval(interval);
    gDoc->setModified();
}

int FitEditor::sliderUpdateInterval()
{
    return sliderValueToUpdateInterval(m_interval_slider->value());
}

//! converts slider value (1-15) to update interval to be propagated to FitSuiteWidget

int FitEditor::sliderValueToUpdateInterval(int value)
{
    auto svalue = static_cast<qsizetype>(value);
    return svalue < slider_to_interval.size() ? slider_to_interval[svalue] : default_interval;
}

int FitEditor::updateIntervalToSliderValue(int updInterval)
{
    if (slider_to_interval.contains(updInterval))
        return slider_to_interval.indexOf(updInterval);
    else
        return slider_to_interval.indexOf(default_interval);
}

void FitEditor::initializeSlider()
{
    if (fitSuiteItem()) {
        int updInterval = fitSuiteItem()->updateInterval();
        int sliderValue = updateIntervalToSliderValue(updInterval);
        QSignalBlocker b(m_interval_slider);
        m_interval_slider->setValue(sliderValue);
        m_update_interval_label->setText(QString::number(sliderUpdateInterval()));
    }
}

//! Updates button "enabled" status and caution status depending on current job conditions.

void FitEditor::updateControlElements(JobStatus status)
{
    setEnabled(isValidJobItem());

    if (isFitting(status)) {
        m_start_button->setEnabled(false);
        m_stop_button->setEnabled(true);
        m_caution_sign->clear();
    } else {
        m_start_button->setEnabled(true);
        m_stop_button->setEnabled(false);
    }
}

FitSuiteItem* FitEditor::fitSuiteItem()
{
    return m_job_item ? m_job_item->fitSuiteItem() : nullptr;
}

bool FitEditor::isValidJobItem()
{
    return m_job_item ? m_job_item->isValidForFitting() : false;
}

void FitEditor::updateIterationsCountLabel(int iter)
{
    m_iterations_count_label->setText(QString::number(iter));
}

void FitEditor::onJobDestroyed()
{
    m_job_item = nullptr;
}
