//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/ToCore/SampleToCore.cpp
//! @brief     Implements part of namespace GUI::ToCore.
//!
//! @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/ToCore/SampleToCore.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Material/MaterialItem.h"
#include "GUI/Model/Sample/CompoundItem.h"
#include "GUI/Model/Sample/CoreAndShellItem.h"
#include "GUI/Model/Sample/InterferenceItems.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 "Sample/Aggregate/IInterference.h"
#include "Sample/Aggregate/ParticleLayout.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/IParticle.h"
#include "Sample/Particle/Mesocrystal.h"
#include "Sample/Particle/Particle.h"

namespace {

std::unique_ptr<Sample> createSample(const SampleItem& item)
{
    auto sample = std::make_unique<Sample>();
    R3 external_field = item.externalField();
    sample->setExternalField(external_field);
    return sample;
}

std::unique_ptr<Layer> createLayer(const LayerItem& item)
{
    const RoughnessItem* roughItem = item.certainRoughness();
    std::unique_ptr<LayerRoughness> roughness;
    if (!item.isAmbient() && roughItem) {
        std::unique_ptr<AutocorrelationModel> autocorrelation = roughItem->createModel();

        std::unique_ptr<InterlayerModel> interlayer =
            roughItem->certainInterlayerModel()->createModel();

        std::unique_ptr<CrosscorrelationModel> crosscorrelation;
        if (!item.isSubstrate() && !dynamic_cast<LinearGrowthModel*>(autocorrelation.get()))
            if (const auto* item = roughItem->certainCrosscorrModel())
                crosscorrelation = item->createModel();

        roughness = std::make_unique<LayerRoughness>(autocorrelation.get(), interlayer.get(),
                                                     crosscorrelation.get());
    }
    const bool isFirstOrLastLayer = item.isAmbient() || item.isSubstrate();
    auto layer = std::make_unique<Layer>(*item.materialItem()->createMaterial(),
                                         isFirstOrLastLayer ? 0.0 : item.thickness().dVal(),
                                         roughness.get());
    layer->setNumberOfSlices(item.numSlices());
    return layer;
}

std::unique_ptr<ParticleLayout> createParticleLayout(const ParticleLayoutItem& item)
{
    auto layout = std::make_unique<ParticleLayout>();
    layout->setTotalParticleSurfaceDensity(item.totalDensityValue());
    return layout;
}

std::unique_ptr<IParticle> createIParticle(const ItemWithParticles& item)
{
    std::unique_ptr<IParticle> particle;
    if (const auto* particle_item = dynamic_cast<const ParticleItem*>(&item))
        particle = particle_item->createParticle();
    else if (const auto* particle_coreshell_item = dynamic_cast<const CoreAndShellItem*>(&item))
        particle = particle_coreshell_item->createCoreAndShell();
    else if (const auto* particle_composition_item = dynamic_cast<const CompoundItem*>(&item))
        particle = particle_composition_item->createCompound();
    else if (const auto* mesocrystal_item = dynamic_cast<const MesocrystalItem*>(&item))
        particle = mesocrystal_item->createMesocrystal();

    return particle;
}

std::unique_ptr<ParticleLayout> buildParticleLayout(const ParticleLayoutItem& item)
{
    auto layout = createParticleLayout(item);
    for (auto* particleItem : item.itemsWithParticles()) {
        if (auto particle = createIParticle(*particleItem)) {
            layout->addParticle(*particle);
            continue;
        }
        ASSERT_NEVER; // case not implemented yet?
    }

    if (InterferenceItem* interferenceItem = item.interferenceSelection().certainItem())
        if (auto interference = interferenceItem->createInterference())
            layout->setInterference(*interference);
    return layout;
}

std::unique_ptr<Layer> buildLayer(const LayerItem& item)
{
    auto layer = createLayer(item);
    for (ParticleLayoutItem* layoutItem : item.layoutItems()) {
        if (auto layout = buildParticleLayout(*layoutItem))
            layer->addLayout(*layout);
    }
    return layer;
}

std::unique_ptr<LayerStack> buildLayerStack(const LayerStackItem& item);

void fillLayerStackWithComponents(LayerStack& stack, const std::vector<ItemWithLayers*>& components)
{
    for (auto* component : components) {
        if (auto* layerItem = dynamic_cast<LayerItem*>(component))
            stack.addLayer(*buildLayer(*layerItem));
        else if (auto* layerStackItem = dynamic_cast<LayerStackItem*>(component))
            stack.addStack(*buildLayerStack(*layerStackItem));
        else
            ASSERT_NEVER;
    }
}

std::unique_ptr<LayerStack> buildLayerStack(const LayerStackItem& item)
{
    auto stack = std::make_unique<LayerStack>(item.numberOfPeriods());
    fillLayerStackWithComponents(*stack, item.componentItems());
    return stack;
}

} // namespace

std::unique_ptr<Sample> GUI::ToCore::itemToSample(const SampleItem& sampleItem)
{
    auto sample = createSample(sampleItem);
    auto outer_stack = buildLayerStack(sampleItem.outerStackItem());
    sample->setOuterStack(*outer_stack);
    return sample;
}
