//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Numeric/NumWidgetUtil.h
//! @brief     Defines 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)
//
//  ************************************************************************************************

#ifndef BORNAGAIN_GUI_VIEW_NUMERIC_NUMWIDGETUTIL_H
#define BORNAGAIN_GUI_VIEW_NUMERIC_NUMWIDGETUTIL_H

#include "Fit/Param/RealLimits.h"
#include "GUI/Model/Descriptor/SelectionProperty.h"
#include "GUI/Support/Type/Unit.h"
#include "GUI/Support/Util/CustomEventFilters.h"
#include <QCheckBox>
#include <QComboBox>
#include <QFormLayout>
#include <QSpinBox>
#include <variant>

class ComboProperty;
class DoubleSpinBox;
class DoubleProperty;
class ScientificSpinBox;

namespace GUI::Util {

//! Create a combo box with the information found in a ComboProperty.
//!
//! The combo box will be filled with the available options and will get the found tooltips,
//! the common one and individual for each item. The current text will be just passes to "slot"
//! function. Furthermore, the combo box can prohibit accidental changes by the mouse wheel.
//! Otherwise it would be dangerous if the combo is on a scrollable form - unintended and unnoticed
//! changes would take place when just scrolling through the form.
//!
//! The combo can be updated from outside using "updaters" list.
//!
QComboBox* createComboBox(std::function<ComboProperty()> comboFunction,
                          std::function<void(const QString&)> slot,
                          QList<std::function<void()>>* updaters = nullptr, QString tooltip = "",
                          bool isScrollable = true);
QComboBox* createSafeComboBox(std::function<ComboProperty()> comboFunction,
                              std::function<void(const QString&)> slot,
                              QList<std::function<void()>>* updaters = nullptr,
                              QString tooltip = "");

QComboBox* createUnitsComboBox(const QStringList& list, std::function<QString()> currentUnits,
                               std::function<void(const QString&)> slot,
                               QList<std::function<void()>>* updaters = nullptr,
                               bool isScrollable = true);

//! Create a combo box with the information found in a selection property.
//!
//! The combo will be filled with the available options and will get the found tooltip.
//! The current index will be set according to the current index in the selection.
//! Furthermore, the combo box can prohibit accidental changes by the mouse wheel. Otherwise it
//! would be dangerous if the combo is on a scrollable form - unintended and unnoticed changes would
//! take place when just scrolling through the form.
//!
//! Changes in the combobox will be notified to the SelectionProperty already. The additional (and
//! optional) slot can be used to be notified about an already executed change.
//!
template <typename T>
QComboBox* createComboBoxFromProperty(SelectionProperty<T>& d,
                                      std::function<void(int)> slot = nullptr,
                                      bool isScrollable = false)
{
    QComboBox* combo = new QComboBox;
    combo->addItems(d.options());
    combo->setMaxCount(d.options().size());
    combo->setToolTip(d.tooltip());
    combo->setCurrentIndex(d.currentIndex());

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

    QObject::connect(combo, qOverload<int>(&QComboBox::currentIndexChanged), [&d, slot](int index) {
        d.setCurrentIndex(index);
        if (slot)
            slot(index);
    });

    return combo;
}

//! Create a label and a spin box with the information found in a DoubleProperty and place them in
//! a row in a form layout.
//!
//! The spin box will be fully initialized (tooltip, limits, current value, unit, size policy).
//! Furthermore, the spin box will prohibit accidental changes by the mouse wheel. Otherwise it
//! would be dangerous if the spin box is on a scrollable form - unintended and unnoticed changes
//! would take place when just scrolling through the form.
//!
//! No connections to update the property will be established! Therefore changes in the spin box
//! will *not* be notified to the property. The additional (and optional) slot can be used to be
//! notified about a value change (the notified new value is in the base unit of the
//! DoubleProperty).
DoubleSpinBox* createDoubleSpinBoxRow(QFormLayout* parentLayout, DoubleProperty& d,
                                      std::function<void(double)> slot = nullptr);

//! Create a label and a scientific spin box with the information found in a DoubleProperty and
//! place them in a row in a form layout.
//!
//! The spin box will be fully initialized (tooltip, limits, current value, unit, size policy).
//! Furthermore, the spin box will prohibit accidental changes by the mouse wheel. Otherwise it
//! would be dangerous if the spin box is on a scrollable form - unintended and unnoticed changes
//! would take place when just scrolling through the form.
//!
//! No connections to update the property will be established! Therefore changes in the spin box
//! will *not* be notified to the property. The additional (and optional) slot can be used to be
//! notified about a value change.
ScientificSpinBox* createScientificSpinBox(QFormLayout* parentLayout, const DoubleProperty& d,
                                           std::function<void(double)> slot = nullptr);

//! Create a label with an optional unit in brackets.
//!
//! No trailing ':' will be appended
QString labelWithUnit(const QString& label, std::variant<QString, Unit> unit);

//! Create a label with an optional unit in brackets, both taken from the given descriptor.
//!
//! No trailing ':' will be appended
QString labelWithUnit(const DoubleProperty& d);

//! Creates an updatable checkbox
QCheckBox* createCheckBox(const QString& title, std::function<bool()> getter,
                          std::function<void(bool)> setter, QList<std::function<void()>>* updaters);

//! Creates an updatable lineedit
QLineEdit* createTextEdit(std::function<QString()> getter, std::function<void(QString)> setter,
                          QList<std::function<void()>>* updaters);

//! Creates configurable spinBox
//!
//! The spin box will be initialized by tooltip, limits, current value. Getter and setter are passed
//! as functional pointers.
//!
//! Furthermore, the spin box will prohibit accidental changes by the mouse wheel if
//! "easyScrollable" flag is false. Otherwise it would be dangerous if the spin box is on a
//! scrollable form - unintended and unnoticed changes would take place when just scrolling through
//! the form.
QSpinBox* createIntSpinbox(std::function<int()> getter, std::function<void(int)> slot,
                           const RealLimits& limits = RealLimits(), QString tooltip = "",
                           QList<std::function<void()>>* updaters = nullptr,
                           bool easyScrollable = false);

//! Creates a scrollable updatable doublespinBox
QDoubleSpinBox* createDoubleSpinbox(std::function<double()> getter,
                                    std::function<void(double)> slot,
                                    QList<std::function<void()>>* updaters, QString tooltip = "",
                                    const RealLimits& limits = RealLimits(),
                                    bool easyScrollable = true);
} // namespace GUI::Util

#endif // BORNAGAIN_GUI_VIEW_NUMERIC_NUMWIDGETUTIL_H
