//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/StandardSamples/MagneticLayersBuilder.cpp
//! @brief    Implements class to build samples with magnetic layers
//!
//! @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 "Sample/StandardSamples/MagneticLayersBuilder.h"
#include "Base/Const/Units.h"
#include "Base/Util/Assert.h"
#include "Sample/Aggregate/ParticleLayout.h"
#include "Sample/HardParticle/Sphere.h"
#include "Sample/Interface/LayerRoughness.h"
#include "Sample/Material/MaterialFactoryFuncs.h"
#include "Sample/Multilayer/Layer.h"
#include "Sample/Multilayer/MultiLayer.h"
#include "Sample/Particle/Particle.h"
#include "Sample/Scattering/Rotations.h"
#include <map>

using Units::deg;

namespace {

const double sphere_radius = 5;

const std::map<std::string, std::pair<double, RoughnessModel>> RoughnessCases = {
    {"Flat", {0., RoughnessModel::TANH}},
    {"Tanh", {2., RoughnessModel::TANH}},
    {"NevotCroce", {2., RoughnessModel::NEVOT_CROCE}},
};

} // namespace

//  ************************************************************************************************

MultiLayer* ExemplarySamples::createMagneticSubstrateZeroField()
{
    R3 substr_field(0.0, 0.0, 0.0);
    R3 particle_field(0.1, 0.0, 0.0);
    Material vacuum_material = RefractiveMaterial("Vacuum", 0.0, 0.0);
    Material substrate_material = RefractiveMaterial("Substrate", 7e-6, 2e-8, substr_field);
    Material particle_material = RefractiveMaterial("MagParticle", 6e-4, 2e-8, particle_field);

    ParticleLayout particle_layout;
    R3 position(0.0, 0.0, -10.0);
    Sphere ff_sphere(sphere_radius);
    Particle particle(particle_material, ff_sphere);
    particle.translate(position);
    particle_layout.addParticle(particle);

    Layer vacuum_layer(vacuum_material);
    Layer substrate_layer(substrate_material);
    substrate_layer.addLayout(particle_layout);

    auto* sample = new MultiLayer();
    sample->addLayer(vacuum_layer);
    sample->addLayer(substrate_layer);
    return sample;
}

//  ************************************************************************************************

MultiLayer* ExemplarySamples::createSimpleMagneticLayer()
{
    auto* sample = new MultiLayer();

    R3 layer_field = R3(0.0, 1e8, 0.0);
    Material vacuum_material = MaterialBySLD("Vacuum", 0.0, 0.0);
    Material layer_material = MaterialBySLD("MagLayer", 1e-4, 1e-8, layer_field);
    Material substrate_material = MaterialBySLD("Substrate", 7e-5, 2e-6);

    Layer vacuum_layer(vacuum_material);
    Layer intermediate_layer(layer_material, 10.0); // 10 nm layer thickness
    Layer substrate_layer(substrate_material);

    sample->addLayer(vacuum_layer);
    sample->addLayer(intermediate_layer);
    sample->addLayer(substrate_layer);
    return sample;
}

//  ************************************************************************************************

MultiLayer* ExemplarySamples::createMagneticLayer()
{
    auto* sample = new MultiLayer();

    R3 layer_field = R3(0.0, 0.0, 1e6);
    R3 particle_field(1e6, 0.0, 0.0);
    Material vacuum_material = RefractiveMaterial("Vacuum0", 0.0, 0.0);
    Material layer_material = RefractiveMaterial("Vacuum1", 0.0, 0.0, layer_field);
    Material substrate_material = RefractiveMaterial("Substrate", 7e-6, 2e-8);
    Material particle_material = RefractiveMaterial("MagParticle", 6e-4, 2e-8, particle_field);

    ParticleLayout particle_layout;
    Sphere ff_sphere(sphere_radius);
    Particle particle(particle_material, ff_sphere);
    particle_layout.addParticle(particle);

    Layer vacuum_layer(vacuum_material);
    vacuum_layer.addLayout(particle_layout);
    Layer substrate_layer(substrate_material);

    sample->addLayer(vacuum_layer);
    sample->addLayer(substrate_layer);
    return sample;
}

//  ************************************************************************************************

MultiLayer*
ExemplarySamples::createSimpleMagneticRotationWithRoughness(const std::string& roughnessKey)
{
    const auto& [sigmaRoughness, roughnessModel] = RoughnessCases.at(roughnessKey);
    auto* sample = new MultiLayer();

    R3 substr_field = R3(0.0, 1e6, 0.0);
    R3 layer_field = R3(1e6, 1e6, 0.0);
    Material vacuum_material = RefractiveMaterial("Vacuum", 0.0, 0.0);
    Material substrate_material = RefractiveMaterial("Substrate", 7e-6, 2e-8, substr_field);
    Material layer_material = RefractiveMaterial("MagLayer", 6e-4, 2e-8, layer_field);

    auto roughness = LayerRoughness();
    roughness.setSigma(sigmaRoughness * Units::angstrom);

    Layer vacuum_layer(vacuum_material);
    Layer substrate_layer(substrate_material);
    Layer layer(layer_material, 200 * Units::angstrom);
    sample->addLayer(vacuum_layer);
    sample->addLayerWithTopRoughness(layer, roughness);
    sample->addLayerWithTopRoughness(substrate_layer, roughness);
    sample->setRoughnessModel(roughnessModel);
    return sample;
}

//  ************************************************************************************************

MultiLayer* ExemplarySamples::createMagneticRotation()
{
    auto* sample = new MultiLayer();

    R3 substr_field = R3(0.0, 1e6, 0.0);
    R3 particle_field(1e6, 0.0, 0.0);
    Material vacuum_material = RefractiveMaterial("Vacuum", 0.0, 0.0);
    Material substrate_material = RefractiveMaterial("Substrate", 7e-6, 2e-8, substr_field);
    Material particle_material = RefractiveMaterial("MagParticle", 6e-4, 2e-8, particle_field);

    ParticleLayout particle_layout;
    R3 position(0.0, 0.0, -10.0);
    Sphere ff_sphere(sphere_radius);
    Particle particle(particle_material, ff_sphere);
    RotationZ rot_z(20 * deg);
    particle.rotate(rot_z);
    particle.translate(position);
    particle_layout.addParticle(particle);

    Layer vacuum_layer(vacuum_material);
    Layer substrate_layer(substrate_material);
    substrate_layer.addLayout(particle_layout);

    sample->addLayer(vacuum_layer);
    sample->addLayer(substrate_layer);
    return sample;
}
