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

#include "GUI/Model/FromCore/ItemizeSample.h"
#include "Base/Const/Units.h"
#include "GUI/Model/Material/MaterialItem.h"
#include "GUI/Model/Sample/CompoundItem.h"
#include "GUI/Model/Sample/CoreAndShellItem.h"
#include "GUI/Model/Sample/LayerItem.h"
#include "GUI/Model/Sample/MesocrystalItem.h"
#include "GUI/Model/Sample/ParticleItem.h"
#include "GUI/Model/Sample/ParticleLayoutItem.h"
#include "GUI/Model/Sample/SampleItem.h"
#include "Param/Node/NodeUtil.h"
#include "Sample/Aggregate/Interferences.h"
#include "Sample/Aggregate/ParticleLayout.h"
#include "Sample/HardParticle/HardParticles.h"
#include "Sample/Interface/LayerInterface.h"
#include "Sample/Interface/LayerRoughness.h"
#include "Sample/Multilayer/Layer.h"
#include "Sample/Multilayer/MultiLayer.h"
#include "Sample/Particle/CoreAndShell.h"
#include "Sample/Particle/Crystal.h"
#include "Sample/Particle/Mesocrystal.h"
#include "Sample/Particle/Particle.h"

using NodeUtil::ChildNodesOfType;
using NodeUtil::OnlyChildOfType;

namespace {

void set_PDF1D(InterferenceRadialParacrystalItem* parent, const IProfile1D* ipdf)
{
    if (const auto* pdf = dynamic_cast<const Profile1DCauchy*>(ipdf)) {
        auto* item = new Profile1DCauchyItem();
        item->setOmega(pdf->omega());
        parent->setPDFType(item);
    } else if (const auto* pdf = dynamic_cast<const Profile1DGauss*>(ipdf)) {
        auto* item = new Profile1DGaussItem();
        item->setOmega(pdf->omega());
        parent->setPDFType(item);
    } else if (const auto* pdf = dynamic_cast<const Profile1DGate*>(ipdf)) {
        auto* item = new Profile1DGateItem();
        item->setOmega(pdf->omega());
        parent->setPDFType(item);
    } else if (const auto* pdf = dynamic_cast<const Profile1DTriangle*>(ipdf)) {
        auto* item = new Profile1DTriangleItem();
        item->setOmega(pdf->omega());
        parent->setPDFType(item);
    } else if (const auto* pdf = dynamic_cast<const Profile1DCosine*>(ipdf)) {
        auto* item = new Profile1DCosineItem();
        item->setOmega(pdf->omega());
        parent->setPDFType(item);
    } else if (const auto* pdf = dynamic_cast<const Profile1DVoigt*>(ipdf)) {
        auto* item = new Profile1DVoigtItem();
        item->setOmega(pdf->omega());
        item->setEta(pdf->eta());
        parent->setPDFType(item);
    } else
        throw std::runtime_error("Profile1D not supported by GUI importer");
}

// note: SetterPDF(1|2)Type are needed because template template parameter must be classes

template <class T> struct SetterPDF1Type {
    T* operator()(Interference2DParacrystalItem* parent)
    {
        auto* p = new T();
        parent->setPDF1Type(p);
        return p;
    }
};

template <class T> struct SetterPDF2Type {
    T* operator()(Interference2DParacrystalItem* parent)
    {
        auto* p = new T();
        parent->setPDF2Type(p);
        return p;
    }
};

template <template <class T> class U>
void set_PDF2D(Interference2DParacrystalItem* parent, const IProfile2D* pdf)
{
    if (const auto* pdf_cauchy = dynamic_cast<const Profile2DCauchy*>(pdf)) {
        Profile2DCauchyItem* item = U<Profile2DCauchyItem>()(parent);
        item->setOmegaX(pdf_cauchy->omegaX());
        item->setOmegaY(pdf_cauchy->omegaY());
        item->setGamma(Units::rad2deg(pdf_cauchy->gamma()));
    } else if (const auto* pdf_gauss = dynamic_cast<const Profile2DGauss*>(pdf)) {
        Profile2DGaussItem* item = U<Profile2DGaussItem>()(parent);
        item->setOmegaX(pdf_gauss->omegaX());
        item->setOmegaY(pdf_gauss->omegaY());
        item->setGamma(Units::rad2deg(pdf_gauss->gamma()));
    } else if (const auto* pdf_gate = dynamic_cast<const Profile2DGate*>(pdf)) {
        Profile2DGateItem* item = U<Profile2DGateItem>()(parent);
        item->setOmegaX(pdf_gate->omegaX());
        item->setOmegaY(pdf_gate->omegaY());
        item->setGamma(Units::rad2deg(pdf_gate->gamma()));
    } else if (const auto* pdf_cone = dynamic_cast<const Profile2DCone*>(pdf)) {
        Profile2DConeItem* item = U<Profile2DConeItem>()(parent);
        item->setOmegaX(pdf_cone->omegaX());
        item->setOmegaY(pdf_cone->omegaY());
        item->setGamma(Units::rad2deg(pdf_cone->gamma()));
    } else if (const auto* pdf_voigt = dynamic_cast<const Profile2DVoigt*>(pdf)) {
        Profile2DVoigtItem* item = U<Profile2DVoigtItem>()(parent);
        item->setOmegaX(pdf_voigt->omegaX());
        item->setOmegaY(pdf_voigt->omegaY());
        item->setGamma(Units::rad2deg(pdf_voigt->gamma()));
        item->setEta(pdf_voigt->eta());
    } else
        throw std::runtime_error("Profile2D not supported by GUI importer");
}

void set_DecayFunction1D(Interference1DLatticeItem* parent, const IProfile1D* ipdf)
{
    if (const auto* pdf = dynamic_cast<const Profile1DCauchy*>(ipdf)) {
        auto* item = new Profile1DCauchyItem();
        item->setOmega(pdf->omega());
        parent->setDecayFunctionType(item);
    } else if (const auto* pdf = dynamic_cast<const Profile1DGauss*>(ipdf)) {
        auto* item = new Profile1DGaussItem();
        item->setOmega(pdf->omega());
        parent->setDecayFunctionType(item);
    } else if (const auto* pdf = dynamic_cast<const Profile1DTriangle*>(ipdf)) {
        auto* item = new Profile1DTriangleItem();
        item->setOmega(pdf->omega());
        parent->setDecayFunctionType(item);
    } else if (const auto* pdf = dynamic_cast<const Profile1DVoigt*>(ipdf)) {
        auto* item = new Profile1DVoigtItem();
        item->setOmega(pdf->omega());
        item->setEta(pdf->eta());
        parent->setDecayFunctionType(item);
    } else
        throw std::runtime_error("Profile1D not supported by GUI importer");
}

void set_DecayFunction2D(Interference2DLatticeItem* parent, const IProfile2D* pdf)
{
    if (const auto* pdf_cauchy = dynamic_cast<const Profile2DCauchy*>(pdf)) {
        auto* item = new Profile2DCauchyItem();
        item->setOmegaX(pdf_cauchy->omegaX());
        item->setOmegaY(pdf_cauchy->omegaY());
        item->setGamma(Units::rad2deg(pdf_cauchy->gamma()));
        parent->setDecayFunctionType(item);
    } else if (const auto* pdf_gauss = dynamic_cast<const Profile2DGauss*>(pdf)) {
        auto* item = new Profile2DGaussItem();
        item->setOmegaX(pdf_gauss->omegaX());
        item->setOmegaY(pdf_gauss->omegaY());
        item->setGamma(Units::rad2deg(pdf_gauss->gamma()));
        parent->setDecayFunctionType(item);
    } else if (const auto* pdf_voigt = dynamic_cast<const Profile2DVoigt*>(pdf)) {
        auto* item = new Profile2DVoigtItem();
        item->setOmegaX(pdf_voigt->omegaX());
        item->setOmegaY(pdf_voigt->omegaY());
        item->setGamma(Units::rad2deg(pdf_voigt->gamma()));
        item->setEta(pdf_voigt->eta());
        parent->setDecayFunctionType(item);
    } else
        throw std::runtime_error("Profile2D not supported by GUI importer");
}

void set_2DLatticeParameters(Interference2DAbstractLatticeItem* parent, const Lattice2D& lattice)
{
    Lattice2DItem* item(nullptr);
    if (lattice.className() == "SquareLattice2D") {
        auto* squareLatticeItem = new SquareLattice2DItem();
        squareLatticeItem->setLatticeLength(lattice.length1());
        item = squareLatticeItem;
    } else if (lattice.className() == "HexagonalLattice2D") {
        auto* hexLatticeItem = new HexagonalLattice2DItem();
        hexLatticeItem->setLatticeLength(lattice.length1());
        item = hexLatticeItem;
    } else {
        auto* basicLatticeItem = new BasicLattice2DItem();
        basicLatticeItem->setLatticeLength1(lattice.length1());
        basicLatticeItem->setLatticeLength2(lattice.length2());
        basicLatticeItem->setLatticeAngle(Units::rad2deg(lattice.latticeAngle()));
        item = basicLatticeItem;
    }
    item->setLatticeRotationAngle(Units::rad2deg(lattice.rotationAngle()));
    parent->setLatticeType(item);
}

void set_PositionVariance(InterferenceItem* parent, const IInterference& iff)
{
    double pos_var = iff.positionVariance();
    parent->setPositionVariance(pos_var);
}

template <typename T> T* addFormFactorItem(std::variant<ParticleItem*, MesocrystalItem*> parent)
{
    if (std::holds_alternative<ParticleItem*>(parent))
        return std::get<ParticleItem*>(parent)->setFormFactorItemType<T>();

    return std::get<MesocrystalItem*>(parent)->setOuterShapeType<T>();
}

void set_1DLatticeItem(Interference1DLatticeItem* parent, const Interference1DLattice& sample)
{
    parent->setLength(sample.length());
    parent->setRotationAngle(Units::rad2deg(sample.xi()));

    const auto* pdf = OnlyChildOfType<IProfile1D>(sample);
    set_DecayFunction1D(parent, pdf);
    set_PositionVariance(parent, sample);
}

void set_2DLatticeItem(Interference2DLatticeItem* parent, const Interference2DLattice& sample)
{
    set_2DLatticeParameters(parent, sample.lattice());

    parent->setXiIntegration(sample.integrationOverXi());

    const auto* p_pdf = OnlyChildOfType<IProfile2D>(sample);
    set_DecayFunction2D(parent, p_pdf);
    set_PositionVariance(parent, sample);
}

void set_2DParacrystalItem(Interference2DParacrystalItem* parent,
                           const Interference2DParacrystal& sample)
{
    set_2DLatticeParameters(parent, sample.lattice());

    parent->setDampingLength(sample.dampingLength());
    parent->setDomainSize1(sample.domainSizes()[0]);
    parent->setDomainSize2(sample.domainSizes()[1]);
    parent->setXiIntegration(sample.integrationOverXi());

    auto pdfs = ChildNodesOfType<IProfile2D>(sample);

    if (!pdfs.empty()) {
        set_PDF2D<SetterPDF1Type>(parent, pdfs[0]);
        if (pdfs.size() >= 2)
            set_PDF2D<SetterPDF2Type>(parent, pdfs[1]);
    }
    set_PositionVariance(parent, sample);
}

void set_Finite2DLatticeItem(InterferenceFinite2DLatticeItem* parent,
                             const InterferenceFinite2DLattice& sample)
{
    set_2DLatticeParameters(parent, sample.lattice());

    parent->setDomainSize1(sample.numberUnitCells1());
    parent->setDomainSize2(sample.numberUnitCells2());
    parent->setXiIntegration(sample.integrationOverXi());
    set_PositionVariance(parent, sample);
}

void set_HardDiskItem(InterferenceHardDiskItem* parent, const InterferenceHardDisk& sample)
{
    parent->setRadius(sample.radius());
    parent->setDensity(sample.density());
    set_PositionVariance(parent, sample);
}

void set_RadialParacrystalItem(InterferenceRadialParacrystalItem* parent,
                               const InterferenceRadialParacrystal& sample)
{
    parent->setPeakDistance(sample.peakDistance());
    parent->setDampingLength(sample.dampingLength());
    parent->setDomainSize(sample.domainSize());
    parent->setKappa(sample.kappa());

    const auto* ipdf = OnlyChildOfType<IProfile1D>(sample);
    set_PDF1D(parent, ipdf);
    set_PositionVariance(parent, sample);
}

//! Returns true if given roughness is non-zero roughness with basic model
bool isValidBasicRoughness(const LayerRoughness* roughness)
{
    if (!roughness)
        return false;
    if (roughness->sigma() == 0 && roughness->hurst() == 0.0
        && roughness->lateralCorrLength() == 0.0)
        return false;
    return true;
}

void set_BasicRoughnessItem(BasicRoughnessItem* parent, const LayerRoughness& sample)
{
    parent->setSigma(sample.sigma());
    parent->setHurst(sample.hurst());
    parent->setLateralCorrelationLength(sample.lateralCorrLength());
}

void set_LayerItem(LayerItem* parent, const Layer* layer, const LayerInterface* top_interface)
{
    parent->setThickness(layer->thickness());
    parent->clearRoughness();
    parent->setNumSlices(layer->numberOfSlices());

    if (top_interface) {
        const LayerRoughness* roughness = top_interface->roughness();
        if (isValidBasicRoughness(roughness)) {
            parent->setBasicRoughness();
            auto* item =
                dynamic_cast<BasicRoughnessItem*>(parent->roughnessSelection().certainItem());
            ASSERT(item);
            set_BasicRoughnessItem(item, *roughness);
        }
    }
}

void set_Rotation(ItemWithParticles* parent, const IRotation* rotation)
{
    if (!rotation)
        parent->setRotationType(nullptr);
    else if (const auto* r = dynamic_cast<const RotationX*>(rotation)) {
        auto* item = new XRotationItem();
        item->setAngle(Units::rad2deg(r->angle()));
        parent->setRotationType(item);
    } else if (const auto* r = dynamic_cast<const RotationY*>(rotation)) {
        auto* item = new YRotationItem();
        item->setAngle(Units::rad2deg(r->angle()));
        parent->setRotationType(item);
    } else if (const auto* r = dynamic_cast<const RotationZ*>(rotation)) {
        auto* item = new ZRotationItem();
        item->setAngle(Units::rad2deg(r->angle()));
        parent->setRotationType(item);
    } else if (const auto* r = dynamic_cast<const RotationEuler*>(rotation)) {
        auto* item = new EulerRotationItem();
        item->setAlpha(Units::rad2deg(r->alpha()));
        item->setBeta(Units::rad2deg(r->beta()));
        item->setGamma(Units::rad2deg(r->gamma()));
        parent->setRotationType(item);
    }
}

void set_Interference(ParticleLayoutItem* parent, const IInterference* interference)
{
    if (!interference)
        parent->removeInterference();
    else if (const auto* itf = dynamic_cast<const Interference1DLattice*>(interference)) {
        auto* item = new Interference1DLatticeItem();
        set_1DLatticeItem(item, *itf);
        parent->setInterference(item);
    } else if (const auto* itf = dynamic_cast<const Interference2DLattice*>(interference)) {
        auto* item = new Interference2DLatticeItem();
        set_2DLatticeItem(item, *itf);
        parent->setInterference(item);
    } else if (const auto* itf = dynamic_cast<const Interference2DParacrystal*>(interference)) {
        auto* item = new Interference2DParacrystalItem();
        set_2DParacrystalItem(item, *itf);
        parent->setInterference(item);
    } else if (const auto* itf = dynamic_cast<const InterferenceFinite2DLattice*>(interference)) {
        auto* item = new InterferenceFinite2DLatticeItem();
        set_Finite2DLatticeItem(item, *itf);
        parent->setInterference(item);
    } else if (const auto* itf = dynamic_cast<const InterferenceHardDisk*>(interference)) {
        auto* item = new InterferenceHardDiskItem();
        set_HardDiskItem(item, *itf);
        parent->setInterference(item);
    } else if (const auto* itf = dynamic_cast<const InterferenceRadialParacrystal*>(interference)) {
        auto* item = new InterferenceRadialParacrystalItem();
        set_RadialParacrystalItem(item, *itf);
        parent->setInterference(item);
    } else
        throw std::runtime_error("Interference function not supported by GUI importer");
}

void set_FormFactor(std::variant<ParticleItem*, MesocrystalItem*> parent, const IFormFactor* ff)
{
    if (const auto* f = dynamic_cast<const Pyramid2*>(ff)) {
        auto* item = addFormFactorItem<Pyramid2Item>(parent);
        item->setLength(f->length());
        item->setWidth(f->width());
        item->setHeight(f->height());
        item->setAlpha(Units::rad2deg(f->alpha()));
    } else if (const auto* f = dynamic_cast<const BarGauss*>(ff)) {
        auto* item = addFormFactorItem<BarGaussItem>(parent);
        item->setLength(f->length());
        item->setWidth(f->width());
        item->setHeight(f->height());
    } else if (const auto* f = dynamic_cast<const BarLorentz*>(ff)) {
        auto* item = addFormFactorItem<BarLorentzItem>(parent);
        item->setLength(f->length());
        item->setWidth(f->width());
        item->setHeight(f->height());
    } else if (const auto* f = dynamic_cast<const Box*>(ff)) {
        auto* item = addFormFactorItem<BoxItem>(parent);
        item->setLength(f->length());
        item->setWidth(f->width());
        item->setHeight(f->height());
    } else if (const auto* f = dynamic_cast<const Cone*>(ff)) {
        auto* item = addFormFactorItem<ConeItem>(parent);
        item->setRadius(f->radius());
        item->setHeight(f->height());
        item->setAlpha(Units::rad2deg(f->alpha()));
    } else if (const auto* f = dynamic_cast<const Pyramid6*>(ff)) {
        auto* item = addFormFactorItem<Pyramid6Item>(parent);
        item->setBaseEdge(f->baseEdge());
        item->setHeight(f->height());
        item->setAlpha(Units::rad2deg(f->alpha()));
    } else if (const auto* f = dynamic_cast<const Bipyramid4*>(ff)) {
        auto* item = addFormFactorItem<Bipyramid4Item>(parent);
        item->setLength(f->length());
        item->setBaseHeight(f->base_height());
        item->setHeightRatio(f->heightRatio());
        item->setAlpha(Units::rad2deg(f->alpha()));
    } else if (const auto* f = dynamic_cast<const Cylinder*>(ff)) {
        auto* item = addFormFactorItem<CylinderItem>(parent);
        item->setRadius(f->radius());
        item->setHeight(f->height());
    } else if (const auto* f = dynamic_cast<const Dodecahedron*>(ff)) {
        auto* item = addFormFactorItem<DodecahedronItem>(parent);
        item->setEdge(f->edge());
    } else if (const auto* f = dynamic_cast<const EllipsoidalCylinder*>(ff)) {
        auto* item = addFormFactorItem<EllipsoidalCylinderItem>(parent);
        item->setRadiusX(f->radiusX());
        item->setRadiusY(f->radiusY());
        item->setHeight(f->height());
    } else if (const auto* f = dynamic_cast<const Sphere*>(ff)) {
        auto* item = addFormFactorItem<SphereItem>(parent);
        item->setRadius(f->radius());
    } else if (const auto* f = dynamic_cast<const Spheroid*>(ff)) {
        auto* item = addFormFactorItem<SpheroidItem>(parent);
        item->setRadius(f->radius());
        item->setHeight(f->height());
    } else if (const auto* f = dynamic_cast<const Icosahedron*>(ff)) {
        auto* item = addFormFactorItem<IcosahedronItem>(parent);
        item->setEdge(f->edge());
    } else if (const auto* f = dynamic_cast<const HemiEllipsoid*>(ff)) {
        auto* item = addFormFactorItem<HemiEllipsoidItem>(parent);
        item->setRadiusX(f->radiusX());
        item->setRadiusY(f->radiusY());
        item->setHeight(f->height());
    } else if (const auto* f = dynamic_cast<const Prism3*>(ff)) {
        auto* item = addFormFactorItem<Prism3Item>(parent);
        item->setBaseEdge(f->baseEdge());
        item->setHeight(f->height());
    } else if (const auto* f = dynamic_cast<const Prism6*>(ff)) {
        auto* item = addFormFactorItem<Prism6Item>(parent);
        item->setBaseEdge(f->baseEdge());
        item->setHeight(f->height());
    } else if (const auto* f = dynamic_cast<const Pyramid4*>(ff)) {
        auto* item = addFormFactorItem<Pyramid4Item>(parent);
        item->setBaseEdge(f->baseEdge());
        item->setHeight(f->height());
        item->setAlpha(Units::rad2deg(f->alpha()));
    } else if (const auto* f = dynamic_cast<const CosineRippleBox*>(ff)) {
        auto* item = addFormFactorItem<CosineRippleBoxItem>(parent);
        item->setLength(f->length());
        item->setWidth(f->width());
        item->setHeight(f->height());
    } else if (const auto* f = dynamic_cast<const CosineRippleGauss*>(ff)) {
        auto* item = addFormFactorItem<CosineRippleGaussItem>(parent);
        item->setLength(f->length());
        item->setWidth(f->width());
        item->setHeight(f->height());
    } else if (const auto* f = dynamic_cast<const CosineRippleLorentz*>(ff)) {
        auto* item = addFormFactorItem<CosineRippleLorentzItem>(parent);
        item->setLength(f->length());
        item->setWidth(f->width());
        item->setHeight(f->height());
    } else if (const auto* f = dynamic_cast<const SawtoothRippleBox*>(ff)) {
        auto* item = addFormFactorItem<SawtoothRippleBoxItem>(parent);
        item->setLength(f->length());
        item->setWidth(f->width());
        item->setHeight(f->height());
        item->setAsymmetry(f->asymmetry());
    } else if (const auto* f = dynamic_cast<const SawtoothRippleGauss*>(ff)) {
        auto* item = addFormFactorItem<SawtoothRippleGaussItem>(parent);
        item->setLength(f->length());
        item->setWidth(f->width());
        item->setHeight(f->height());
        item->setAsymmetry(f->asymmetry());
    } else if (const auto* f = dynamic_cast<const SawtoothRippleLorentz*>(ff)) {
        auto* item = addFormFactorItem<SawtoothRippleLorentzItem>(parent);
        item->setLength(f->length());
        item->setWidth(f->width());
        item->setHeight(f->height());
        item->setAsymmetry(f->asymmetry());
    } else if (const auto* f = dynamic_cast<const Pyramid3*>(ff)) {
        auto* item = addFormFactorItem<Pyramid3Item>(parent);
        item->setBaseEdge(f->baseEdge());
        item->setHeight(f->height());
        item->setAlpha(Units::rad2deg(f->alpha()));
    } else if (const auto* f = dynamic_cast<const TruncatedCube*>(ff)) {
        auto* item = addFormFactorItem<TruncatedCubeItem>(parent);
        item->setLength(f->length());
        item->setRemovedLength(f->removedLength());
    } else if (const auto* f = dynamic_cast<const TruncatedSphere*>(ff)) {
        auto* item = addFormFactorItem<TruncatedSphereItem>(parent);
        item->setRadius(f->radius());
        item->setUntruncatedHeight(f->untruncated_height());
        item->setRemovedTop(f->removedTop());
    } else if (const auto* f = dynamic_cast<const TruncatedSpheroid*>(ff)) {
        auto* item = addFormFactorItem<TruncatedSpheroidItem>(parent);
        item->setRadius(f->radius());
        item->setUntruncatedHeight(f->untruncated_height());
        item->setHeightFlattening(f->heightFlattening());
        item->setRemovedTop(f->removedTop());
    } else if (const auto* f = dynamic_cast<const CantellatedCube*>(ff)) {
        auto* item = addFormFactorItem<CantellatedCubeItem>(parent);
        item->setLength(f->length());
        item->setRemovedLength(f->removedLength());
    } else if (const auto* f = dynamic_cast<const HorizontalCylinder*>(ff)) {
        auto* item = addFormFactorItem<HorizontalCylinderItem>(parent);
        item->setRadius(f->radius());
        item->setLength(f->length());
        item->setSliceBottom(f->slice_bottom());
        item->setSliceTop(f->slice_top());
    } else if (const auto* f = dynamic_cast<const PlatonicOctahedron*>(ff)) {
        auto* item = addFormFactorItem<PlatonicOctahedronItem>(parent);
        item->setEdge(f->edge());
    } else if (const auto* f = dynamic_cast<const PlatonicTetrahedron*>(ff)) {
        auto* item = addFormFactorItem<PlatonicTetrahedronItem>(parent);
        item->setEdge(f->edge());
    } else
        throw std::runtime_error("Formfactor not supported by GUI importer");
}

MaterialItem* findMaterialItem(MaterialsSet& matItems, const ISampleNode* node)
{
    const Material* material = node->material();

    QString materialName = QString::fromStdString(material->materialName());

    MaterialItem* result = matItems.materialItemFromName(materialName);
    if (result)
        return result;

    complex_t material_data = material->refractiveIndex_or_SLD();
    if (material->typeID() == MATERIAL_TYPES::RefractiveMaterial) {
        result = matItems.addRefractiveMaterialItem(materialName, material_data.real(),
                                                    material_data.imag());
    } else if (material->typeID() == MATERIAL_TYPES::MaterialBySLD) {
        result =
            matItems.addSLDMaterialItem(materialName, material_data.real(), material_data.imag());
    } else
        ASSERT_NEVER;

    result->setMagnetization(material->magnetization());

    return result;
}

void copyParticleItem(ParticleItem* parent, MaterialsSet& matItems, const Particle* particle)
{
    parent->setAbundance(particle->abundance());
    parent->setPosition(particle->particlePosition());
    parent->setMaterial(findMaterialItem(matItems, particle));
    set_Rotation(parent, particle->rotation());
    set_FormFactor(parent, particle->pFormfactor());
}

void copyParticle(const IParticle* iparticle, MaterialsSet& matItems,
                  std::function<void(ItemWithParticles*)> addToParent)
{
    if (const auto* particle = dynamic_cast<const Particle*>(iparticle)) {
        auto* item = new ParticleItem(&matItems);
        copyParticleItem(item, matItems, particle);
        addToParent(item);
    } else if (const auto* coreshell = dynamic_cast<const CoreAndShell*>(iparticle)) {
        ASSERT(coreshell->coreParticle());
        ASSERT(coreshell->shellParticle());

        auto* item = new CoreAndShellItem(&matItems);
        item->setAbundance(coreshell->abundance());
        item->setPosition(coreshell->particlePosition());
        set_Rotation(item, coreshell->rotation());

        if (const Particle* p = coreshell->coreParticle()) {
            item->createCoreItem(&matItems);
            copyParticleItem(item->coreItem(), matItems, p);
        }
        if (const Particle* p = coreshell->shellParticle()) {
            item->createShellItem(&matItems);
            copyParticleItem(item->shellItem(), matItems, p);
        }

        addToParent(item);
    } else if (const auto* meso = dynamic_cast<const Mesocrystal*>(iparticle)) {
        ASSERT(meso->particleStructure().basis());

        auto* item = new MesocrystalItem(&matItems);
        item->setAbundance(meso->abundance());
        item->setPosition(meso->particlePosition());
        set_FormFactor(item, meso->outerShape());
        set_Rotation(item, meso->rotation());

        const Lattice3D* lattice = meso->particleStructure().lattice();
        item->setVectorA(lattice->basisVectorA());
        item->setVectorB(lattice->basisVectorB());
        item->setVectorC(lattice->basisVectorC());
        addToParent(item);

        copyParticle(meso->particleStructure().basis(), matItems,
                     [=](ItemWithParticles* p) { item->setBasisItem(p); });

    } else if (const auto* particleComposition = dynamic_cast<const Compound*>(iparticle)) {
        auto* item = new CompoundItem(&matItems);

        item->setAbundance(particleComposition->abundance());
        item->setPosition(particleComposition->particlePosition());
        set_Rotation(item, particleComposition->rotation());
        addToParent(item);

        for (const auto* p : particleComposition->particles())
            copyParticle(p, matItems,
                         [=](ItemWithParticles* p) { item->addItemWithParticleSelection(p); });
    }
}

} // namespace


namespace GUI::FromCore {

SampleItem* itemizeSample(const MultiLayer& sample, const QString& nodeName)
{
    auto* result = new SampleItem();
    result->setName(nodeName.isEmpty() ? QString::fromStdString(sample.name()) : nodeName);
    result->setCrossCorLength(sample.crossCorrLength());
    result->setExternalField(sample.externalField());

    MaterialsSet& matItems = result->materialModel();

    //  iterate over layers
    for (size_t layerIndex = 0; layerIndex < sample.numberOfLayers(); layerIndex++) {
        const auto* layer = sample.layer(layerIndex);

        const LayerInterface* top_interface =
            layerIndex == 0 ? nullptr : sample.layerInterface(layerIndex - 1);

        auto* layerItem = result->createLayerItemAt();
        layerItem->setMaterial(findMaterialItem(matItems, layer));

        set_LayerItem(layerItem, layer, top_interface);

        // iterate over particle layouts
        for (const auto* layout : layer->layouts()) {
            auto* layoutItem = layerItem->addLayoutItem();
            layoutItem->setOwnDensity(layout->totalParticleSurfaceDensity());
            set_Interference(layoutItem, layout->interferenceFunction());

            // create particles/particle compositions
            for (const IParticle* particle : layout->particles())
                copyParticle(particle, matItems, [=](ItemWithParticles* p) {
                    layoutItem->addItemWithParticleSelection(p);
                });
        }
    }

    // append standard materials to the example-specific ones
    result->addStandardMaterials();

    return result;
}

} // namespace GUI::FromCore
