//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Resample/Slice/SliceStack.cpp
//! @brief     Implements class SliceStack.
//!
//! @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 "Resample/Slice/SliceStack.h"
#include "Base/Util/Assert.h"
#include <algorithm>

void SliceStack::addTopSlice(double zbottom, const Material& material)
{
    this->emplace_back(Slice(ZLimits(zbottom, ZLimits::inf), material, {}, nullptr, 0.0));
}

void SliceStack::addSlice(double thickness, const Material& material,
                          const LayerRoughness* const roughness, double rms)
{
    ASSERT(!this->empty());
    double top = this->back().low();
    ASSERT(thickness >= 0);
    std::unique_ptr<ZLimits> zRange;
    if (thickness == 0)
        zRange = std::make_unique<ZLimits>(-ZLimits::inf, top);
    else
        zRange = std::make_unique<ZLimits>(top - thickness, top);
    this->emplace_back(Slice(*zRange, material, {}, roughness, rms));
}

//! Adds n times the same slice to the stack.

void SliceStack::addNSlices(size_t n, double thickness, const Material& material,
                            const LayerRoughness* const roughness, double rms)
{
    ASSERT(thickness > 0);
    ASSERT(n > 0);
    const double slice_thickness = thickness / n;
    addSlice(slice_thickness, material, roughness, rms);
    for (size_t i = 1; i < n; ++i)
        addSlice(slice_thickness, material);
}

SliceStack SliceStack::setBField(const R3& externalField)
{
    // Temporary forbid non-zero magnetization in fronting medium (see issue #654)
    if (this->at(0).material().magnetization() != R3())
        throw std::runtime_error("Processing fronting magnetization is not implemented yet.");

    // Temporary forbid non-zero external field (see issue #654)
    if (externalField != R3())
        throw std::runtime_error("Processing external field is not implemented yet.");

    if (this->empty())
        return *this;
    const double M_z0 = this->at(0).material().magnetization().z();
    const double H_z = externalField.z() + M_z0;
    for (Slice& slice : *this)
        slice.initBField(externalField, H_z);
    return *this;
}

const LayerRoughness* SliceStack::bottomRoughness(size_t i_slice) const
{
    if (i_slice + 1 < size())
        return (*this)[i_slice + 1].topRoughness();
    return nullptr;
}

double SliceStack::bottomRMS(size_t i_slice) const
{
    if (i_slice + 1 < size())
        return (*this)[i_slice + 1].topRMS();
    return 0.0;
}
