//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Sample/LayerStackItem.cpp
//! @brief     Implements class LayerStackItem.
//!
//! @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/Model/Sample/LayerStackItem.h"
#include "Base/Util/Vec.h"
#include "GUI/Model/Material/MaterialsSet.h"
#include "GUI/Model/Sample/LayerItem.h"
#include "GUI/Model/Util/UtilXML.h"

namespace {
namespace Tag {

const QString BaseData("BaseData");
const QString Component("Component");
const QString NumPeriods("NumPeriods");

} // namespace Tag
} // namespace

LayerStackItem::LayerStackItem(const MaterialsSet* materials, uint n_periods)
    : m_n_periods(n_periods)
    , m_materials(materials)
{
    ASSERT(materials);
    m_color = QColor(Qt::white);
}

std::vector<LayerItem*> LayerStackItem::uniqueLayerItems() const
{
    std::vector<LayerItem*> result;
    for (const auto& component : m_components) {
        if (auto* layerItem = dynamic_cast<LayerItem*>(component->certainItem()))
            result.push_back(layerItem);
        else if (auto* layerStackItem = dynamic_cast<LayerStackItem*>(component->certainItem())) {
            std::vector<LayerItem*> sublayers = layerStackItem->uniqueLayerItems();
            result.insert(result.end(), sublayers.begin(), sublayers.end());
        } else
            ASSERT_NEVER;
    }
    return result;
}

std::vector<LayerItem*> LayerStackItem::unwrappedLayerItems() const
{
    std::vector<LayerItem*> unwrapped;
    for (const auto& component : m_components) {
        if (auto* layerItem = dynamic_cast<LayerItem*>(component->certainItem()))
            unwrapped.push_back(layerItem);
        else if (auto* layerStackItem = dynamic_cast<LayerStackItem*>(component->certainItem())) {
            std::vector<LayerItem*> sublayers = layerStackItem->unwrappedLayerItems();
            unwrapped.insert(unwrapped.end(), sublayers.begin(), sublayers.end());
        } else
            ASSERT_NEVER;
    }
    std::vector<LayerItem*> result;
    for (size_t i = 0; i < m_n_periods; i++)
        result.insert(result.end(), unwrapped.begin(), unwrapped.end());
    return result;
}

std::vector<ItemWithLayers*> LayerStackItem::componentItems() const
{
    std::vector<ItemWithLayers*> result;
    for (const auto& component : m_components)
        result.push_back(component->certainItem());
    return result;
}

int LayerStackItem::indexOfComponent(const ItemWithLayers* item) const
{
    const std::vector<ItemWithLayers*> component_items = componentItems();
    return Vec::indexOfPtr(item, component_items);
}

LayerStackItem* LayerStackItem::parentOfComponent(const ItemWithLayers* searchedItem)
{
    for (auto* component : componentItems()) {
        if (component == searchedItem)
            return this;
        if (auto* stack = dynamic_cast<LayerStackItem*>(component)) {
            auto* subparent = stack->parentOfComponent(searchedItem);
            if (subparent)
                return subparent;
        }
    }
    return nullptr;
}

ItemWithLayers* LayerStackItem::addNewItemAt(ItemWithLayers* item, int index)
{
    ASSERT(item);
    if (index < 0)
        index = m_components.size();

    auto new_item =
        new PolyPtrWithContext<ItemWithLayers, LayeredComponentCatalog, MaterialsSet>(m_materials);
    new_item->setCertainItem(item); // gets ownership
    m_components.insert_at(index, new_item);
    return m_components[index]->certainItem();
}

LayerItem* LayerStackItem::createLayerItemAt(int index)
{
    ItemWithLayers* new_item = addNewItemAt(new LayerItem(m_materials), index);
    auto* layer = dynamic_cast<LayerItem*>(new_item);
    ASSERT(layer);
    return layer;
}

LayerStackItem* LayerStackItem::createLayerStackItemAt(int index)
{
    ItemWithLayers* new_item = addNewItemAt(new LayerStackItem(m_materials), index);
    auto* stack = dynamic_cast<LayerStackItem*>(new_item);
    ASSERT(stack);
    return stack;
}

void LayerStackItem::removeComponent(const ItemWithLayers* component)
{
    ASSERT(component);
    int index = indexOfComponent(component);
    ASSERT(index >= 0)
    m_components.delete_at(index);
}

void LayerStackItem::moveComponent(ItemWithLayers* component, ItemWithLayers* aboveThisComponent)
{
    if (component == aboveThisComponent)
        return;

    int currentIndex = indexOfComponent(component);
    ASSERT(currentIndex >= 0);

    int abothThisIndex = indexOfComponent(aboveThisComponent);
    if (abothThisIndex < 0)
        abothThisIndex = m_components.size();

    ASSERT(currentIndex != abothThisIndex);

    auto b = m_components.begin();
    if (currentIndex < abothThisIndex)
        std::rotate(b + currentIndex, b + currentIndex + 1, b + abothThisIndex);
    else
        std::rotate(b + abothThisIndex, b + currentIndex, b + currentIndex + 1);
}

void LayerStackItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeBaseElement<ItemWithLayers>(w, Tag::BaseData, this);
    XML::writeTaggedValue(w, Tag::NumPeriods, m_n_periods);
    for (const auto* component : m_components)
        XML::writeTaggedElement(w, Tag::Component, *component);
}

void LayerStackItem::readFrom(QXmlStreamReader* r)
{
    m_components.clear();
    while (r->readNextStartElement()) {
        QString tag = r->name().toString();
        if (tag == Tag::BaseData)
            XML::readBaseElement<ItemWithLayers>(r, tag, this);
        else if (tag == Tag::NumPeriods)
            m_n_periods = XML::readTaggedUInt(r, tag);
        else if (tag == Tag::Component) {
            m_components.push_back(
                new PolyPtrWithContext<ItemWithLayers, LayeredComponentCatalog, MaterialsSet>(
                    m_materials));
            m_components.back()->readFrom(r, m_materials);
            XML::gotoEndElementOfTag(r, tag);
        } else
            r->skipCurrentElement();
    }
}
