//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Multilayer/Layer.cpp
//! @brief     Implements class Layer.
//!
//! @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/Multilayer/Layer.h"
#include "Base/Util/Assert.h"
#include "Base/Util/StringUtil.h"
#include "Sample/Aggregate/Disordered2D.h"
#include "Sample/Aggregate/Disordered3D.h"
#include "Sample/Aggregate/Struct2D.h"
#include "Sample/Aggregate/Struct3D.h"
#include "Sample/Interface/Roughness.h"
#include "Sample/Material/Material.h"
#include "Sample/Particle/IParticle.h"
#include "Sample/Particle/Mixture.h"
#include <cmath>
#include <limits>
#include <stdexcept>

namespace {

using ZFn = double (IParticle::*)() const;

//! Translates each non-Mixture descendant so its z-reference (bottom, center, or
//! top, per zFn) lands at z=0 in the struct frame. Recurses through Mixture nodes.
static void placeRef(IParticle* root, ZFn zFn)
{
    if (auto* mix = dynamic_cast<Mixture*>(root))
        for (IParticle* alt : mix->mutableParticles())
            placeRef(alt, zFn);
    else
        root->translate(0, 0, -(root->*zFn)());
}

} // namespace

Layer::Layer(const Material& material, double thickness, const Roughness* roughness)
    : PlainLayer(material, thickness, roughness)
{
}

Layer::Layer(const Material& material, const Roughness* roughness)
    : Layer(material, Span::inf, roughness)
{
}

Layer::~Layer() = default;

Layer::Layer(const Layer& other)
    : PlainLayer(other)
{
    for (const auto& s : other.m_structsAtBottom)
        m_structsAtBottom.emplace_back(s->clone());
    for (const auto& s : other.m_structsAtTop)
        m_structsAtTop.emplace_back(s->clone());
    for (const auto& s : other.m_structs3D)
        m_structs3D.emplace_back(s->clone());
    m_n_slices = other.numberOfSlices();
}

Layer::Layer(Layer&& other) noexcept(false)
    : PlainLayer(std::move(other))
    , m_structsAtBottom(std::move(other.m_structsAtBottom))
    , m_structsAtTop(std::move(other.m_structsAtTop))
    , m_structs3D(std::move(other.m_structs3D))
    , m_n_slices(other.m_n_slices)
{
}

//... 2D structures (deposited on bottom interface)

void Layer::deposit2D(const Struct2D& s)
{
    addDeposit2D(1.0, s);
}

void Layer::addDeposit2D(double coverage, const Struct2D& s)
{
    ASSERT(!m_validated);
    std::unique_ptr<Struct2D> my_s(s.clone());
    my_s->setCoverage(coverage);
    ::placeRef(my_s->particle(), &IParticle::intrinsicBottomZ);
    m_structsAtBottom.emplace_back(std::move(my_s));
}

//... 2D structures (suspended below top interface)

void Layer::suspend2D(const Struct2D& s)
{
    addSuspend2D(1.0, s);
}

void Layer::addSuspend2D(double coverage, const Struct2D& s)
{
    ASSERT(!m_validated);
    std::unique_ptr<Struct2D> my_s(s.clone());
    my_s->setCoverage(coverage);
    ::placeRef(my_s->particle(), &IParticle::intrinsicTopZ);
    m_structsAtTop.emplace_back(std::move(my_s));
}

//... 3D structures (particles filling layer volume)

void Layer::fill3D(const Struct3D& s)
{
    ASSERT(!m_validated);
    std::unique_ptr<Struct3D> my_s(s.clone());
    ::placeRef(my_s->particle(), &IParticle::intrinsicCenterZ);
    m_structs3D.emplace_back(std::move(my_s));
}

void Layer::add3D(double coverage, const Struct3D& s)
{
    ASSERT(!m_validated);
    std::unique_ptr<Struct3D> my_s(s.clone());
    my_s->setCoverage(coverage);
    ::placeRef(my_s->particle(), &IParticle::intrinsicCenterZ);
    m_structs3D.emplace_back(std::move(my_s));
}

void Layer::setNumberOfSlices(unsigned int n_slices)
{
    ASSERT(!m_validated);
    m_n_slices = n_slices;
}

//... Public but not exposed to Swig

std::vector<std::unique_ptr<Layer>> Layer::flatClones() const
{
    std::vector<std::unique_ptr<Layer>> result;
    result.emplace_back(clone());
    return result;
}

Layer* Layer::clone() const
{
    return new Layer(*this);
}

Layer::Layer(const Material& material, const Span& span, const Roughness* roughness)
    : PlainLayer(material, span, roughness)
{
}
