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

#include "GUI/View/Sample/LayerStackForm.h"
#include "Base/Util/Vec.h"
#include "GUI/Model/Sample/LayerItem.h"
#include "GUI/Model/Sample/LayerStackItem.h"
#include "GUI/Model/Sample/SampleItem.h"
#include "GUI/View/Base/LayoutUtil.h"
#include "GUI/View/Numeric/NumWidgetUtil.h"
#include "GUI/View/Sample/HeinzFormLayout.h"
#include "GUI/View/Sample/LayerForm.h"

namespace {

//! Widget with a button to add a layer or stack
//! (the "Add layer/add stack" buttons shown between items)
class AddButtonsWidget : public QWidget {
public:
    AddButtonsWidget(LayerStackForm* parentStackForm, const ItemWithLayers* itemBefore,
                     SampleEditorController* ec)
        : QWidget(parentStackForm)
        , m_item(itemBefore)
    {
        auto* l = new QHBoxLayout(this);
        l->setContentsMargins(0, 0, 0, 0);
        l->addStretch();

        auto* layer_btn = new QPushButton("Add layer", this);
        l->addWidget(layer_btn);
        connect(layer_btn, &QPushButton::clicked,
                [=] { ec->addLayerItem(*parentStackForm, itemBefore); });

        l->addSpacing(10);

        auto* stack_btn = new QPushButton("Add stack", this);
        l->addWidget(stack_btn);
        connect(stack_btn, &QPushButton::clicked,
                [=] { ec->addLayerStackItem(*parentStackForm, itemBefore); });

        l->addStretch();
    }
    const ItemWithLayers* m_item;
};

} // namespace

LayerStackForm::LayerStackForm(QWidget* parent, LayerStackItem* stack, SampleEditorController* ec)
    : LayerContainerForm(parent, stack, ec, "stack")
    , m_components_layout(new QVBoxLayout)
{
    if (isOuterStack()) {
        m_title_widget->hide();
        m_components_layout->setContentsMargins(0, 0, 0, 0);
        m_layout->setContentsMargins(0, 0, 0, 0);
    } else
        m_layout->addBoldRow(
            "Number of periods:",
            GUI::Util::createIntSpinBox([this] { return stackItem().numberOfPeriods(); },
                                        [this](int v) {
                                            stackItem().setNumberOfPeriods(v);
                                            emit gDoc->sampleChanged();
                                        },
                                        RealLimits::lowerLimited(0),
                                        "Number of periods.\n"
                                        "The content of the stack will be repeated\n"
                                        "exactly that number of times."));

    auto* components_widget = new QWidget;
    components_widget->setLayout(m_components_layout);
    m_layout->addRow(components_widget);

    m_components_layout->addWidget(new AddButtonsWidget(this, nullptr, m_ec), 0, Qt::AlignTop);
    for (auto* component : stack->componentItems())
        addComponentForm(component);
    m_components_layout->setSizeConstraint(QLayout::SetMinimumSize);

    updatePositionDependentElements();
}

LayerStackItem& LayerStackForm::stackItem() const
{
    return dynamic_cast<LayerStackItem&>(*m_item);
}

void LayerStackForm::addComponentForm(ItemWithLayers* componentItem)
{
    LayerContainerForm* newForm;
    if (auto* layer = dynamic_cast<LayerItem*>(componentItem))
        newForm = new LayerForm(this, layer, m_ec);
    else if (auto* stack = dynamic_cast<LayerStackItem*>(componentItem))
        newForm = new LayerStackForm(this, stack, m_ec);
    else
        ASSERT_NEVER;

    auto* buttons = new AddButtonsWidget(this, componentItem, m_ec);

    const int row_in_layout = rowInLayout(componentItem);
    // same row => button is above!
    m_components_layout->insertWidget(row_in_layout, buttons, 0, Qt::AlignTop);
    m_components_layout->insertWidget(row_in_layout, newForm, 0, Qt::AlignTop);
}

void LayerStackForm::onComponentMoved(const ItemWithLayers* componentItem)
{
    LayerContainerForm* wl = nullptr;
    AddButtonsWidget* al = nullptr;
    for (int index = 0; index < m_components_layout->count(); index++) {
        if (auto* w = dynamic_cast<AddButtonsWidget*>(m_components_layout->itemAt(index)->widget()))
            if (w->m_item == componentItem) {
                al = w;
                m_components_layout->takeAt(index);
                break;
            }
    }
    for (int index = 0; index < m_components_layout->count(); index++) {
        if (auto* w =
                dynamic_cast<LayerContainerForm*>(m_components_layout->itemAt(index)->widget()))
            if (w->item() == componentItem) {
                wl = w;
                m_components_layout->takeAt(index);
                break;
            }
    }
    const int row_in_layout = rowInLayout(componentItem);
    // same row => button is above!
    m_components_layout->insertWidget(row_in_layout, al, 0, Qt::AlignTop);
    m_components_layout->insertWidget(row_in_layout, wl, 0, Qt::AlignTop);
}

void LayerStackForm::removeComponentForm(ItemWithLayers* componentItem)
{
    LayerContainerForm* componentForm = nullptr;
    AddButtonsWidget* addLayerWidget = nullptr;
    for (auto* c : findChildren<QWidget*>()) {
        if (auto* w = dynamic_cast<AddButtonsWidget*>(c))
            if (w->m_item == componentItem)
                addLayerWidget = w;

        if (auto* w = dynamic_cast<LayerContainerForm*>(c)) {
            if (w->item() == componentItem)
                componentForm = w;
        }
    }
    ASSERT(componentForm);
    ASSERT(addLayerWidget);

    GUI::Util::Layout::clearLayout(componentForm->layout());
    componentForm->hide();
    componentForm->setParent(nullptr); // so it is not findable in update routines
    componentForm->deleteLater();      // delete later (this is the sender)

    delete addLayerWidget;
}

LayerContainerForm* LayerStackForm::findNextComponentForm(QWidget* w)
{
    while (w != nullptr && dynamic_cast<LayerContainerForm*>(w) == nullptr) {
        const auto index = m_components_layout->indexOf(w);
        if (index + 1 < m_components_layout->count())
            w = m_components_layout->itemAt(index + 1)->widget();
        else
            return nullptr;
    }
    return dynamic_cast<LayerContainerForm*>(w);
}

void LayerStackForm::updatePositionDependentElements()
{
    LayerContainerForm::updatePositionDependentElements();
    updateTitle();
}

void LayerStackForm::updateTitle()
{
    auto layers_inside = stackItem().uniqueLayerItems();
    if (!layers_inside.size()) {
        setTitle("Empty stack");
        return;
    }

    auto unique_layers_global = m_ec->sampleItem()->uniqueLayerItems();
    int i_first = Vec::indexOfPtr(layers_inside.front(), unique_layers_global);
    int i_last = Vec::indexOfPtr(layers_inside.back(), unique_layers_global);

    setTitle("Stack of layers from " + QString::number(i_first) + " to " + QString::number(i_last));
}

int LayerStackForm::rowInLayout(const ItemWithLayers* componentItem) const
{
    return stackItem().indexOfComponent(componentItem) * 2 + 1;
}
