//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Numeric/NumWidgetUtil.cpp
//! @brief     Implements GUI::Util namespace
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2022
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/View/Numeric/NumWidgetUtil.h"
#include "GUI/Model/Data/ComboProperty.h"
#include "GUI/View/Numeric/DoubleSpinBox.h"
#include "GUI/View/Numeric/NumberUtil.h"
#include "GUI/View/Numeric/ScientificSpinBox.h"
#include <QCheckBox>
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
#include <QSpinBox>

using std::function;
using std::variant;

QComboBox* GUI::Util::createComboBox(function<ComboProperty()> comboFunction,
                                     function<void(const QString&)> slot,
                                     QList<function<void()>>* updaters, QString tooltip,
                                     bool isScrollable)
{
    QComboBox* combo = new QComboBox;
    combo->addItems(comboFunction().values());
    combo->setMaxCount(comboFunction().values().size());
    combo->setToolTip(tooltip);
    combo->setCurrentText(comboFunction().currentValue());

    if (comboFunction().toolTips().size() == combo->count())
        for (int index = 0; index < combo->count(); index++)
            combo->setItemData(index, comboFunction().toolTips().at(index), Qt::ToolTipRole);

    if (!isScrollable)
        WheelEventEater::install(combo);

    QObject::connect(combo, &QComboBox::currentTextChanged, [=] { slot(combo->currentText()); });

    if (updaters)
        (*updaters) << [=]() {
            QSignalBlocker b(combo);
            combo->setCurrentText(comboFunction().currentValue());
        };

    return combo;
}

QComboBox* GUI::Util::createSafeComboBox(function<ComboProperty()> comboFunction,
                                         function<void(const QString&)> slot,
                                         QList<function<void()>>* updaters, QString tooltip)
{
    return createComboBox(comboFunction, slot, updaters, tooltip, false);
}

QComboBox* GUI::Util::createUnitsComboBox(const QStringList& list, function<QString()> currentUnits,
                                          function<void(const QString&)> slot,
                                          QList<std::function<void()>>* updaters, bool isScrollable)
{
    QComboBox* combo = new QComboBox;
    combo->addItems(list);
    combo->setMaxCount(list.size());
    combo->setCurrentText(currentUnits());

    if (!isScrollable)
        WheelEventEater::install(combo);

    QObject::connect(combo, &QComboBox::currentTextChanged, [=] { slot(combo->currentText()); });

    if (updaters)
        (*updaters) << [=]() {
            QSignalBlocker b(combo);
            combo->setCurrentText(currentUnits());
        };

    return combo;
}

DoubleSpinBox* GUI::Util::createDoubleSpinBoxRow(QFormLayout* parentLayout, DoubleProperty& d,
                                                 function<void(double)> slot)
{
    auto* sb = new DoubleSpinBox(d);
    parentLayout->addRow(labelWithUnit(d.label(), d.unit()) + ":", sb);

    if (slot)
        QObject::connect(sb, &DoubleSpinBox::baseValueChanged, [=](int v) { slot(v); });

    return sb;
}


QString GUI::Util::labelWithUnit(const QString& label, variant<QString, Unit> unit)
{
    const QString s = std::holds_alternative<QString>(unit) ? std::get<QString>(unit)
                                                            : unitAsString(std::get<Unit>(unit));
    if (!s.isEmpty())
        return label + " [" + s + "]";

    return label;
}

QString GUI::Util::labelWithUnit(const DoubleProperty& d)
{
    return labelWithUnit(d.label(), d.unit());
}

ScientificSpinBox* GUI::Util::createScientificSpinBox(QFormLayout* parentLayout,
                                                      const DoubleProperty& d,
                                                      function<void(double)> slot)
{
    auto* spinBox = new ScientificSpinBox(parentLayout->parentWidget());
    spinBox->setFocusPolicy(Qt::StrongFocus);
    GUI::View::NumberUtil::configSpinbox(spinBox, d.decimals(), d.limits());
    spinBox->setToolTip(d.tooltip());
    spinBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
    spinBox->setValue(d.value());
    parentLayout->addRow(labelWithUnit(d.label(), d.unit()) + ":", spinBox);

    if (slot)
        QObject::connect(spinBox, &ScientificSpinBox::valueChanged, [=](double v) { slot(v); });

    return spinBox;
}

QCheckBox* GUI::Util::createCheckBox(const QString& title, function<bool()> getter,
                                     function<void(bool)> setter, QList<function<void()>>* updaters)
{
    QCheckBox* checkBox = new QCheckBox(title);
    checkBox->setChecked(getter());

    QObject::connect(checkBox, &QCheckBox::stateChanged, [=]() { setter(checkBox->isChecked()); });

    if (updaters)
        (*updaters) << [=]() {
            QSignalBlocker b(checkBox);
            checkBox->setChecked(getter());
        };

    return checkBox;
}

QLineEdit* GUI::Util::createTextEdit(function<QString()> getter, function<void(QString)> setter,
                                     QList<function<void()>>* updaters)
{
    QLineEdit* edit = new QLineEdit;
    edit->setText(getter());

    QObject::connect(edit, &QLineEdit::textEdited, [=]() { setter(edit->text()); });

    if (updaters)
        (*updaters) << [=]() {
            QSignalBlocker b(edit);
            edit->setText(getter());
        };

    return edit;
}

QSpinBox* GUI::Util::createIntSpinbox(std::function<int()> getter, std::function<void(int)> slot,
                                      const RealLimits& limits, QString tooltip,
                                      QList<std::function<void()>>* updaters, bool easyScrollable)
{
    QSpinBox* spin = new QSpinBox;
    spin->setFocusPolicy(Qt::StrongFocus);

    spin->setMinimum(limits.hasLowerLimit() ? limits.lowerLimit()
                                            : -std::numeric_limits<int>::max());
    spin->setMaximum(limits.hasUpperLimit() ? limits.upperLimit()
                                            : std::numeric_limits<int>::max());
    spin->setValue(getter());
    spin->setToolTip(tooltip);

    if (!easyScrollable)
        WheelEventEater::install(spin);

    QObject::connect(spin, qOverload<int>(&QSpinBox::valueChanged), [=] { slot(spin->value()); });

    if (updaters)
        (*updaters) << [=]() {
            QSignalBlocker b(spin);
            spin->setValue(getter());
        };

    return spin;
}

QDoubleSpinBox* GUI::Util::createDoubleSpinbox(function<double()> getter,
                                               function<void(double)> slot,
                                               QList<function<void()>>* updaters, QString tooltip,
                                               const RealLimits& limits, bool easyScrollable)
{
    QDoubleSpinBox* spin = new QDoubleSpinBox;
    spin->setFocusPolicy(Qt::StrongFocus);
    spin->setMinimum(limits.hasLowerLimit() ? limits.lowerLimit()
                                            : -std::numeric_limits<int>::max());
    spin->setMaximum(limits.hasUpperLimit() ? limits.upperLimit()
                                            : std::numeric_limits<int>::max());
    spin->setDecimals(3);
    spin->setSingleStep(0.01);
    spin->setValue(getter());
    spin->setToolTip(tooltip);

    if (!easyScrollable)
        WheelEventEater::install(spin);

    QObject::connect(spin, qOverload<double>(&QDoubleSpinBox::valueChanged),
                     [=] { slot(spin->value()); });

    if (updaters)
        (*updaters) << [=]() {
            QSignalBlocker b(spin);
            spin->setValue(getter());
        };

    return spin;
}
