//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/StandardSample/FeNiBilayerBuilder.cpp
//! @brief     Defines various sample builder classes to.
//!            test polarized specular computations
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2020
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "Sample/StandardSample/FeNiBilayerBuilder.h"
#include "Base/Const/PhysicalConstants.h"
#include "Base/Const/Units.h"
#include "Sample/Interface/LayerRoughness.h"
#include "Sample/Material/MaterialFactoryFuncs.h"
#include "Sample/Multilayer/Layer.h"
#include "Sample/Multilayer/MultiLayer.h"

using Units::deg;

namespace {

auto constexpr rhoMconst = -PhysConsts::m_n * PhysConsts::g_factor_n * PhysConsts::mu_N
                           / PhysConsts::h_bar / PhysConsts::h_bar * 1e-27;

const complex_t sldFe = complex_t{8.02e-06, 0};
const complex_t sldAu = complex_t{4.6665e-6, 0};
const complex_t sldNi = complex_t{9.4245e-06, 0};

class Options {
public:
    int m_NBilayers = 4;
    double m_angle = 0.;
    double m_magnetization_magnitude = 1e7;
    double m_thickness_fe = 100. * Units::angstrom;
    double m_thickness_ni = 40. * Units::angstrom;
    double m_sigma_roughness = 0.;
    int m_effectiveSLD = 0;
    std::unique_ptr<RoughnessModel> m_roughness_model;

    Options() = default;
    void setNBilayers(int n) { m_NBilayers = n; }
    void setAngle(double angle) { m_angle = angle; }
    void setMagnetizationMagnitude(double M) { m_magnetization_magnitude = M; }
    void setThicknessFe(double t) { m_thickness_fe = t; }
    void setThicknessNi(double t) { m_thickness_ni = t; }
    void setSigmaRoughness(double r) { m_sigma_roughness = r; }
    void setEffectiveSLD(int i) { m_effectiveSLD = i; }
    void setRoughnessModel(RoughnessModel* rm) { m_roughness_model.reset(rm); }
};


//! Creates the sample demonstrating an Fe-Ni Bilayer with and without roughness
class FeNiBilayer {
public:
    explicit FeNiBilayer(Options opt = {})
        : NBilayers(opt.m_NBilayers)
        , angle(opt.m_angle)
        , magnetizationMagnitude(opt.m_magnetization_magnitude)
        , thicknessFe(opt.m_thickness_fe)
        , thicknessNi(opt.m_thickness_ni)
        , sigmaRoughness(opt.m_sigma_roughness)
        , effectiveSLD(opt.m_effectiveSLD)
        , roughnessModel(opt.m_roughness_model ? opt.m_roughness_model->clone() : nullptr)
    {
        if (angle != 0. && effectiveSLD != 0.)
            throw std::runtime_error("Cannot perform scalar computation "
                                     "for non-colinear magnetization");

        magnetizationVector = R3(magnetizationMagnitude * std::sin(angle),
                                 magnetizationMagnitude * std::cos(angle), 0);
        sample = constructSample();
    }

    MultiLayer* release() { return sample.release(); }

private:
    int NBilayers;
    double angle;
    double magnetizationMagnitude;
    double thicknessFe;
    double thicknessNi;
    double sigmaRoughness;
    int effectiveSLD;
    std::unique_ptr<RoughnessModel> roughnessModel;

    R3 magnetizationVector;

    std::unique_ptr<MultiLayer> sample;

    std::unique_ptr<MultiLayer> constructSample();
};

std::unique_ptr<MultiLayer> FeNiBilayer::constructSample()
{
    auto result = std::make_unique<MultiLayer>();

    auto m_ambient = MaterialBySLD("Ambient", 0.0, 0.0);
    auto m_Fe =
        effectiveSLD == 0
            ? MaterialBySLD("Fe", sldFe.real(), sldFe.imag(), magnetizationVector)
            : MaterialBySLD("Fe", sldFe.real() + effectiveSLD * rhoMconst * magnetizationMagnitude,
                            sldFe.imag(), R3());

    auto m_Ni = MaterialBySLD("Ni", sldNi.real(), sldNi.imag());
    auto m_Substrate = MaterialBySLD("Au", sldAu.real(), sldAu.imag());

    Layer l_Fe{m_Fe, thicknessFe};
    Layer l_Ni{m_Ni, thicknessNi};

    LayerRoughness roughness{sigmaRoughness, 0., 0., roughnessModel.get()};
    result->addLayer(Layer{m_ambient});

    for (auto i = 0; i < NBilayers; ++i) {
        result->addLayerWithTopRoughness(l_Fe, roughness);
        result->addLayerWithTopRoughness(l_Ni, roughness);
    }

    result->addLayerWithTopRoughness(Layer{m_Substrate}, roughness);
    return result;
}

} // namespace


MultiLayer* ExemplarySamples::createFeNiBilayer()
{
    auto sample = FeNiBilayer{Options()};
    return sample.release();
}

MultiLayer* ExemplarySamples::createFeNiBilayerTanh()
{
    Options opt;
    opt.setSigmaRoughness(2. * Units::angstrom);
    opt.setRoughnessModel(new TanhRoughness);
    auto sample = FeNiBilayer{std::move(opt)};
    return sample.release();
}

MultiLayer* ExemplarySamples::createFeNiBilayerNC()
{
    Options opt;
    opt.setSigmaRoughness(2. * Units::angstrom);
    opt.setRoughnessModel(new ErfRoughness);
    auto sample = FeNiBilayer{std::move(opt)};
    return sample.release();
}

MultiLayer* ExemplarySamples::createFeNiBilayerSpinFlip()
{
    Options opt;
    opt.setAngle(38. * deg);
    auto sample = FeNiBilayer{std::move(opt)};
    return sample.release();
}

MultiLayer* ExemplarySamples::createFeNiBilayerSpinFlipTanh()
{
    Options opt;
    opt.setAngle(38. * deg);
    opt.setSigmaRoughness(2. * Units::angstrom);
    opt.setRoughnessModel(new TanhRoughness);
    auto sample = FeNiBilayer{std::move(opt)};
    return sample.release();
}

MultiLayer* ExemplarySamples::createFeNiBilayerSpinFlipNC()
{
    Options opt;
    opt.setAngle(38. * deg);
    opt.setSigmaRoughness(2. * Units::angstrom);
    opt.setRoughnessModel(new ErfRoughness);
    auto sample = FeNiBilayer{std::move(opt)};
    return sample.release();
}
