//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/SampleDesigner/FormLayouter.cpp
//! @brief     Implements class 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)
//
//  ************************************************************************************************

#include "GUI/View/SampleDesigner/FormLayouter.h"
#include "GUI/Model/Descriptor/UIntDescriptor.h"
#include "GUI/Model/Descriptor/VectorDescriptor.h"
#include "GUI/View/Edit/DoubleSpinBox.h"
#include "GUI/View/SampleDesigner/LayerEditorUtils.h"
#include "GUI/View/SampleDesigner/SampleEditorController.h"
#include "GUI/View/Tool/GroupBoxCollapser.h"

#include <QFormLayout>
#include <QGroupBox>
#include <QLabel>
#include <QPushButton>

namespace {

QWidget* createSpinBox(QWidget* parentWidget, const UIntDescriptor& d, SampleEditorController* ec)
{
    auto* spinBox = new QSpinBox(parentWidget);
    spinBox->setFocusPolicy(Qt::StrongFocus);
    spinBox->setKeyboardTracking(false);
    spinBox->setToolTip(d.tooltip);
    spinBox->setMaximum(std::numeric_limits<int>::max());
    spinBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);

    if (d.limits.hasLowerLimit())
        spinBox->setMinimum(static_cast<int>(d.limits.lowerLimit()));
    if (d.limits.hasUpperLimit())
        spinBox->setMaximum(static_cast<int>(d.limits.upperLimit()));

    spinBox->setValue(d.get());

    QObject::connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged),
                     [=](int newValue) { ec->setInt(newValue, d); });

    return spinBox;
}

} // namespace

FormLayouter::FormLayouter(QWidget* parent, SampleEditorController* ec)
    : m_ec(ec)
{
    if (parent->layout() != nullptr) {
        m_formLayout = dynamic_cast<QFormLayout*>(parent->layout());
        if (m_formLayout == nullptr) {
            auto* collapser =
                GroupBoxCollapser::findInstalledCollapser(dynamic_cast<QGroupBox*>(parent));
            if (collapser)
                m_formLayout = dynamic_cast<QFormLayout*>(collapser->contentArea()->layout());
        }
        ASSERT(m_formLayout);
    } else {
        m_formLayout = new QFormLayout(parent);
        m_formLayout->setFormAlignment(Qt::AlignLeft | Qt::AlignBottom);
        m_formLayout->setFieldGrowthPolicy(QFormLayout::FieldsStayAtSizeHint);
    }
}

QFormLayout* FormLayouter::layout()
{
    return m_formLayout;
}

void FormLayouter::setContentsMargins(int left, int top, int right, int bottom)
{
    m_formLayout->setContentsMargins(left, top, right, bottom);
}

int FormLayouter::addRow(const QString& label, QWidget* w)
{
    insertRow(m_formLayout->rowCount(), label, w);
    return m_formLayout->rowCount() - 1;
}

void FormLayouter::insertRow(int row, QString label, QWidget* w)
{
    if (!label.endsWith(":"))
        label += ":";
    m_formLayout->insertRow(row, LayerEditorUtils::createBoldLabel(label), w);
}

int FormLayouter::addGroupOfValues(const QString& labelText, const DoubleDescriptors& values)
{
    auto* w = new QWidget(m_formLayout->parentWidget());
    w->setObjectName("PropertyBaseWidget");
    w->setAttribute(Qt::WA_StyledBackground, true);
    w->setStyleSheet("#PropertyBaseWidget {background-color: transparent}");

    auto* gridLayout = new QGridLayout(w);
    gridLayout->setContentsMargins(0, 0, 0, 0);
    gridLayout->setSpacing(6);

    LayerEditorUtils::addMultiPropertyToGrid(gridLayout, 0, values, m_ec, true);

    return addRow(labelText, w);
}

int FormLayouter::addVector(const VectorDescriptor& d, bool vertically /*= true*/)
{
    auto* w = new QWidget(m_formLayout->parentWidget());
    w->setObjectName("PropertyBaseWidget");
    w->setAttribute(Qt::WA_StyledBackground, true);
    w->setStyleSheet("#PropertyBaseWidget {background-color: transparent}");

    auto* gridLayout = new QGridLayout(w);
    gridLayout->setContentsMargins(0, 0, 0, 0);
    gridLayout->setSpacing(6);

    LayerEditorUtils::addMultiPropertyToGrid(gridLayout, 0, {d.x, d.y, d.z}, m_ec, vertically,
                                             true);

    return addRow(d.label, w);
}

void FormLayouter::setRowVisible(int row, bool visible)
{
    m_formLayout->itemAt(row, QFormLayout::LabelRole)->widget()->setVisible(visible);
    m_formLayout->itemAt(row, QFormLayout::FieldRole)->widget()->setVisible(visible);
}

void FormLayouter::removeRow(int row)
{
    m_formLayout->removeRow(row);
}

void FormLayouter::addStructureEditingRow(QPushButton* button)
{
    auto* w = new QWidget(m_formLayout->parentWidget());
    auto* l = new QHBoxLayout(w);
    l->setContentsMargins(0, 0, 0, 0);
    l->setAlignment(Qt::AlignLeft);
    l->setSizeConstraint(QLayout::SetMinimumSize);
    l->addWidget(button);
    l->addStretch();
    addRow(w);
}
int FormLayouter::addValue(const UIntDescriptor& d)
{
    auto* editor = createSpinBox(m_formLayout->parentWidget(), d, m_ec);
    return addRow(d.label, editor);
}

int FormLayouter::addValue(const DoubleDescriptor& d)
{
    insertValue(m_formLayout->rowCount(), d);
    return m_formLayout->rowCount() - 1;
}

int FormLayouter::addValue(const DoubleDescriptor& d, function<void(double)> onValueChange)
{
    insertValue(m_formLayout->rowCount(), d, onValueChange);
    return m_formLayout->rowCount() - 1;
}

void FormLayouter::insertValue(int row, const DoubleDescriptor& d)
{
    auto* ec = m_ec; // to allow copy-capture in the following lambda
    insertValue(row, d, [ec, d](double newValue) { ec->setDouble(newValue, d); });
}

void FormLayouter::insertValue(int row, const DoubleDescriptor& d,
                               function<void(double)> onValueChange)
{
    auto labelText = d.label;
    if (!labelText.endsWith(":"))
        labelText += ":";

    auto* label = LayerEditorUtils::createBoldLabel(labelText);
    label->setAlignment(Qt::AlignLeft | Qt::AlignBottom);
    label->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);

    auto* editor = new DoubleSpinBox(m_formLayout->parentWidget(), d);
    QObject::connect(editor, &DoubleSpinBox::baseValueChanged, onValueChange);

    label->setBuddy(editor);

    LayerEditorUtils::updateLabelUnit(label, editor);
    m_formLayout->insertRow(row, label, editor);
}

int FormLayouter::addRow(QWidget* w)
{
    m_formLayout->addRow(w);
    return m_formLayout->rowCount() - 1;
}

void FormLayouter::insertRow(int row, QWidget* w)
{
    m_formLayout->insertRow(row, w);
}