//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/SampleDesigner/FormLayouter.h
//! @brief     Defines classes FormLayouter
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2021
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#ifndef BORNAGAIN_GUI_VIEW_SAMPLEDESIGNER_FORMLAYOUTER_H
#define BORNAGAIN_GUI_VIEW_SAMPLEDESIGNER_FORMLAYOUTER_H

#include "GUI/Model/Descriptor/SelectionProperty.h"
#include "GUI/View/SampleDesigner/SelectionContainerForm.h"
#include <QFormLayout>
#include <QSpinBox>

class SampleEditorController;
class VectorProperty;

//! Utility class to populate a QFormLayout.
//!
//! Helps to add edit controls which operate on Properties (DoubleProperty, VectorProperty,
//! SelectionProperty). Also takes care of bold printed labels, the connection of labels and edit
//! controls (buddies), which is necessary to realize the "label shows unit of value" feature.
//! Connections to a given SampleEditorController are established as well.
class FormLayouter {
public:
    //! Create a layouter which operates on the given parent widget.
    //!
    //! If the parent has no layout yet, a QFormLayout is installed. If the parentWidget already has
    //! a QFormLayout, this will be used for the calls later on.
    //! The given SampleEditorController is used to create connections e.g. when a DoubleSpinBox
    //! value has been changed.
    FormLayouter(QWidget* parent, SampleEditorController* ec);

    //! The layout where this layouter is operating on.
    //!
    //! Use this, if you want to modify the layout with means not given in here.
    QFormLayout* layout();

    //! Convenience method to set the contents margins.
    //!
    //! Directly calls QFormLayout::setContentsMargins()
    void setContentsMargins(int left, int top, int right, int bottom);

    //! Convenience method to add a widget.
    //!
    //! Calls QFormLayout::addRow() and returns the row of the newly added widget.
    int addRow(QWidget* w);

    //! Convenience method to insert a widget.
    //!
    //! Calls QFormLayout::insertRow().
    void insertRow(int row, QWidget* w);

    //! Add a row with a bold printed label.
    //!
    //! If necessary, a colon (':') is added to the label.
    //! Returns the newly added row.
    int addRow(const QString& label, QSpinBox* w);
    int addRow(const QString& label, QWidget* w);

    //! Insert a row with a bold printed label.
    //!
    //! If necessary, a colon (':') is added to the label.
    void insertRow(int row, QString label, QWidget* w);

    //! Add a row with a selection.
    //!
    //! The whole selection is realized by adding a SelectionContainerForm. This
    //! SelectionContainerForm is limited to contain the selection combo box and a list of double
    //! values represented by DoubleProperties. To add more complex selections (e.g. with
    //! sub-selections or different value types), this method and the SelectionContainerForm is not
    //! sufficient. It has to be done "manually".
    //! For more details, see SelectionContainerForm.
    //! Returns the newly added row.
    template <typename T>
    int addSelection(SelectionProperty<T>& d)
    {
        auto* w = new SelectionContainerForm(m_formLayout->parentWidget(), d, m_ec);
        return addRow(d.label(), w);
    }

    //! Adds a row with a bold printed label and a DoubleSpinBox.
    //!
    //! The DoubleSpinBox is initialized with the contents found in the DoubleProperty (e.g. limits,
    //! decimals, unit). The DoubleSpinBox is set as the "buddy" of the label. This is necessary to
    //! realize the "label shows unit of value" feature. Changes of the DoubleSpinBox are signaled
    //! to the SampleEditorController which has been overhanded in the constructor of this
    //! FormLayouter. It is connected to SampleEditorController::setDouble(). If a different method
    //! should be called (e.g. for a special undo functionality), this method is not sufficient. It
    //! would have to be done "manually" or with the overload which takes a slot (see below).
    //! Returns the newly added row.
    int addValue(DoubleProperty& d);

    //! Adds a row with a bold printed label and a DoubleSpinBox.
    //!
    //! Same as above, but the called slot in case of a value change has to be overhanded.
    //! Use this only if the standard (calling SampleEditorController::setDouble()) is not
    //! sufficient.
    int addValue(DoubleProperty& d, std::function<void(double)> onValueChange);

    //! Inserts a row with a bold printed label and a DoubleSpinBox.
    //!
    //! Same functionality as addValue(), please read there.
    void insertValue(int row, DoubleProperty& d);

    //! Inserts a row with a bold printed label and a DoubleSpinBox.
    //!
    //! Same functionality as addValue(), please read there.
    void insertValue(int row, DoubleProperty& d, std::function<void(double)> onValueChange);

    //! Adds a row with a bold printed label and a set of DoubleProperties.
    //!
    //! The label describes the set of the DoubleProperties and is located in the first column of
    //! the layout. The DoubleSpinBoxes for each DoubleProperty are created as children of a newly
    //! created widget, which is positioned in the second column of the layout. If the number of
    //! values is greater than 1, the related labels are shown above them, to limit the necessary
    //! space to the right. For the creation, signaling, unit handling etc. of one DoubleProperty
    //! the same applies as when adding a single DoubleProperty with the method addValue(),
    //! therefore please read there for more details. Returns the newly added row.
    int addGroupOfValues(const QString& labelText, const DoubleProperties& values);

    //! Adds a row with a bold printed label and the 3 values of a 3D vector.
    //!
    //! Works the same as addGroupOfValues. The label is taken from the VectorProperty.
    //! In addition, the caller can define whether the labels are above the values
    //! (vertically=true), or whether labels and values are all placed in one row
    //! (vertically=false).
    int addVector(VectorProperty& d, bool vertically = true);

    //! Shows or hides the widgets in a row.
    void setRowVisible(int row, bool visible);

    //! Convenience method to remove a row.
    //!
    //! Calls QFormLayout::removeRow().
    void removeRow(int row);

    //! Adds a button for structure editing.
    //!
    //! Creates a widget, places the given button as a child left-aligned into this widget and adds
    //! the newly created widget as a row in the layout.
    void addStructureEditingRow(QPushButton* button);

    //! Find a widget in the given position.
    template <typename T>
    T widgetAt(int row, QFormLayout::ItemRole role)
    {
        return qobject_cast<T>(m_formLayout->itemAt(row, role)->widget());
    }

private:
    SampleEditorController* m_ec;
    QFormLayout* m_formLayout;
};

#endif // BORNAGAIN_GUI_VIEW_SAMPLEDESIGNER_FORMLAYOUTER_H
