//  ************************************************************************************************
//
//  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/LayerStackItem.h"
#include "GUI/Model/Sample/MesocrystalItem.h"
#include "GUI/Model/Sample/ParticleItem.h"
#include "GUI/Model/Sample/ParticleLayoutItem.h"
#include "GUI/Model/Sample/RoughnessItems.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/LayerRoughness.h"
#include "Sample/Multilayer/Layer.h"
#include "Sample/Multilayer/LayerStack.h"
#include "Sample/Multilayer/Sample.h"
#include "Sample/Particle/Compound.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 <typename T> struct SetterPDF1Type {
    T* operator()(Interference2DParacrystalItem* parent)
    {
        auto* p = new T;
        parent->setPDF1Type(p);
        return p;
    }
};

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

template <template <typename 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);
}

void set_InterlayerModel(RoughnessItem* parent, const InterlayerModel* interlayer)
{
    if (dynamic_cast<const ErfInterlayer*>(interlayer))
        parent->interlayerModelSelection().setCertainItem(new ErfInterlayerItem);
    else if (dynamic_cast<const TanhInterlayer*>(interlayer))
        parent->interlayerModelSelection().setCertainItem(new TanhInterlayerItem);
    else
        ASSERT_NEVER;
}

void set_CrosscorrelationModel(RoughnessItem* parent, const CrosscorrelationModel* crosscorr)
{
    if (const auto cd = dynamic_cast<const CommonDepthCrosscorrelation*>(crosscorr))
        parent->crossrorrModelSelection().setCertainItem(
            new CommonDepthCrosscorrelationItem(cd->crossCorrDepth()));
    else if (const auto cd = dynamic_cast<const SpatialFrequencyCrosscorrelation*>(crosscorr))
        parent->crossrorrModelSelection().setCertainItem(new SpatialFrequencyCrosscorrelationItem(
            cd->baseCrossCorrDepth(), cd->baseSpatialFrequency(), cd->power()));
}

void set_Roughness(LayerItem* parent, const LayerRoughness* roughness)
{
    if (!roughness) {
        parent->roughnessSelection().setCertainItem(nullptr);
        return;
    }

    if (!roughness->showInScriptOrGui()) {
        parent->roughnessSelection().setCertainItem(nullptr);
        return;
    }

    const AutocorrelationModel* autocorrelation = roughness->autocorrelationModel();
    const InterlayerModel* interlayer = roughness->interlayerModel();
    const CrosscorrelationModel* crosscorrelation = roughness->crosscorrelationModel();

    if (const auto* autocorrItem = dynamic_cast<const K_CorrelationModel*>(autocorrelation)) {
        auto* k_corr = new K_CorrelationRoughnessItem(autocorrItem->sigma(), autocorrItem->hurst(),
                                                      autocorrItem->lateralCorrLength(),
                                                      autocorrItem->maxSpatialFrequency());
        set_InterlayerModel(k_corr, interlayer);
        set_CrosscorrelationModel(k_corr, crosscorrelation);
        parent->roughnessSelection().setCertainItem(k_corr);
    } else if (const auto* autocorrItem = dynamic_cast<const LinearGrowthModel*>(autocorrelation)) {
        auto* k_corr = new LinearGrowthRoughnessItem(
            autocorrItem->clusterVolume(), autocorrItem->damp1(), autocorrItem->damp2(),
            autocorrItem->damp3(), autocorrItem->damp4(), autocorrItem->maxSpatialFrequency());
        set_InterlayerModel(k_corr, interlayer);
        // external crosscorrelation model is not needed here
        parent->roughnessSelection().setCertainItem(k_corr);
    } else
        ASSERT_NEVER;
}

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->setRadiusXY(f->radiusXY());
        item->setRadiusZ(f->radiusZ());
    } 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->setcutFromTop(f->cutFromTop());
        item->setcutFromBottom(f->cutFromBottom());
    } else if (const auto* f = dynamic_cast<const TruncatedSpheroid*>(ff)) {
        auto* item = addFormfactorItem<TruncatedSpheroidItem>(parent);
        item->setRadiusXY(f->radiusXY());
        item->setRadiusZ(f->radiusZ());
        item->setcutFromBottom(f->cutFromBottom());
        item->setcutFromTop(f->cutFromTop());
    } 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());
    result->setMagnetizationEnabled(material->isMagneticMaterial());

    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 {

LayerItem* itemizeLayer(const Layer& layer, MaterialsSet& materialModel)
{
    auto* result = new LayerItem(&materialModel);
    result->expandGroupbox = false;
    result->setMaterial(findMaterialItem(materialModel, &layer));
    result->thickness().setDVal(layer.thickness());
    result->setNumSlices(layer.numberOfSlices());

    set_Roughness(result, layer.roughness());

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

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

LayerStackItem* itemizeLayerStack(const LayerStack& stack, MaterialsSet& materialModel)
{
    auto* result = new LayerStackItem(&materialModel, stack.numberOfPeriods());
    result->expandGroupbox = false;
    const auto& components = stack.components();
    for (const auto& component : components) {
        if (const auto* layer = dynamic_cast<const Layer*>(component))
            result->addNewItemAt(itemizeLayer(*layer, materialModel), -1);
        else if (const auto* substack = dynamic_cast<const LayerStack*>(component))
            result->addNewItemAt(itemizeLayerStack(*substack, materialModel), -1);
        else
            ASSERT_NEVER;
    }
    return result;
}

SampleItem* itemizeSample(const Sample& sample, const QString& nodeName)
{
    auto* result = new SampleItem;
    result->setName(nodeName.isEmpty() ? QString::fromStdString(sample.name()) : nodeName);
    result->setExternalField(sample.externalField());
    auto outer_stack = itemizeLayerStack(sample.outerStack(), result->materialModel());
    outer_stack->expandGroupbox = true;
    result->setOuterStackItem(outer_stack);

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

} // namespace GUI::FromCore
