//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Fit/RunFitControlWidget.cpp
//! @brief     Implements class RunFitControlWidget
//!
//! @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/RunFitControlWidget.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Job/FitSuiteItem.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/Support/Util/DesignerHelper.h"
#include "GUI/View/Info/CautionSign.h"
#include "GUI/View/Tool/mainwindow_constants.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QSlider>

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

RunFitControlWidget::RunFitControlWidget(QWidget* parent)
    : DataAccessWidget(parent)
    , m_startButton(new QPushButton)
    , m_stopButton(new QPushButton)
    , m_intervalSlider(new QSlider)
    , m_updateIntervalLabel(new QLabel)
    , m_iterationsCountLabel(new QLabel)
    , m_cautionSign(new CautionSign(this))
{
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    setFixedHeight(GUI::Constants::RUN_FIT_CONTROL_WIDGET_HEIGHT);

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

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

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

    QFont font("Monospace", DesignerHelper::getLabelFontSize(), QFont::Normal);
    font.setPointSize(DesignerHelper::getPortFontSize());
    m_updateIntervalLabel->setToolTip(slider_tooltip);
    m_updateIntervalLabel->setFont(font);
    m_updateIntervalLabel->setText(QString::number(sliderUpdateInterval()));

    auto* layout = new QHBoxLayout;
    layout->setSpacing(0);
    layout->addWidget(m_startButton);
    layout->addSpacing(5);
    layout->addWidget(m_stopButton);
    layout->addSpacing(5);
    layout->addWidget(m_intervalSlider);
    layout->addSpacing(2);
    layout->addWidget(m_updateIntervalLabel);
    layout->addSpacing(5);
    layout->addStretch();
    layout->addWidget(m_iterationsCountLabel);
    setLayout(layout);

    connect(m_startButton, &QPushButton::clicked, [&]() { startFittingPushed(); });
    connect(m_stopButton, &QPushButton::clicked, this, [&]() { stopFittingPushed(); });
    connect(m_intervalSlider, &QSlider::valueChanged, this,
            &RunFitControlWidget::onSliderValueChanged);

    setEnabled(false);
}

void RunFitControlWidget::setJobOrRealItem(QObject* job_item)
{
    DataAccessWidget::setJobOrRealItem(job_item);
    ASSERT(jobItem());

    updateControlElements(jobItem()->status());
    updateIterationsCountLabel(fitSuiteItem()->iterationCount());

    initializeSlider();

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

    connect(jobItem(), &JobItem::jobStatusChanged, this,
            &RunFitControlWidget::updateControlElements, Qt::UniqueConnection);
}

void RunFitControlWidget::onFittingError(const QString& what)
{
    m_cautionSign->clear();
    m_iterationsCountLabel->setText("");
    m_cautionSign->setCautionMessage(what);
}

void RunFitControlWidget::onSliderValueChanged(int value)
{
    int interval = sliderValueToUpdateInterval(value);
    m_updateIntervalLabel->setText(QString::number(interval));
    if (fitSuiteItem())
        fitSuiteItem()->setUpdateInterval(interval);
    gProjectDocument.value()->setModified();
}

int RunFitControlWidget::sliderUpdateInterval()
{
    return sliderValueToUpdateInterval(m_intervalSlider->value());
}

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

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

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

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

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

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

    if (status == JobStatus::Fitting) {
        m_startButton->setEnabled(false);
        m_stopButton->setEnabled(true);
        m_cautionSign->clear();
    } else {
        m_startButton->setEnabled(true);
        m_stopButton->setEnabled(false);
    }
}

FitSuiteItem* RunFitControlWidget::fitSuiteItem()
{
    return jobItem() ? jobItem()->fitSuiteItem() : nullptr;
}

bool RunFitControlWidget::isValidJobItem()
{
    return jobItem() ? jobItem()->isValidForFitting() : false;
}

void RunFitControlWidget::updateIterationsCountLabel(int iter)
{
    m_iterationsCountLabel->setText(QString::number(iter));
}
