//  ************************************************************************************************
//
//  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/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/MultiLayer.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<MultiLayer> createMultiLayer(const SampleItem& item)
{
    auto sample = std::make_unique<MultiLayer>();
    double cross_corr_length = item.crossCorrLength();
    if (cross_corr_length > 0)
        sample->setCrossCorrLength(cross_corr_length);
    R3 external_field = item.externalField();
    sample->setExternalField(external_field);
    return sample;
}

std::unique_ptr<Layer> createLayer(const LayerItem& item)
{
    const bool isFirstOrLastLayer = item.isTopLayer() || item.isBottomLayer();
    auto layer = std::make_unique<Layer>(*item.materialItem()->createMaterial(),
                                         isFirstOrLastLayer ? 0.0 : item.thickness());
    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<LayerRoughness>
basicRoughnessItemToLayerRoughness(const BasicRoughnessItem* roughness)
{
    return std::make_unique<LayerRoughness>(roughness->sigma(), roughness->hurst(),
                                            roughness->lateralCorrelationLength());
}

} // namespace

std::unique_ptr<MultiLayer> GUI::ToCore::itemToSample(const SampleItem& sampleItem)
{
    auto sample = createMultiLayer(sampleItem);
    for (auto* layerItem : sampleItem.layerItems()) {
        auto layer = buildLayer(*layerItem);
        ASSERT(layer);

        if (layerItem->isTopLayer())
            sample->addLayer(*layer);
        else if (const auto* basicRoughnessItem = dynamic_cast<const BasicRoughnessItem*>(
                     layerItem->roughnessSelection().certainItem())) {
            auto roughness = basicRoughnessItemToLayerRoughness(basicRoughnessItem);
            sample->addLayerWithTopRoughness(*layer, *roughness);
        } else
            sample->addLayer(*layer);
    }
    return sample;
}
