//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sim/Scan/LambdaScan.cpp
//! @brief     Implements class LambdaScan.
//!
//! @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 "Sim/Scan/LambdaScan.h"
#include "Base/Axis/MakeScale.h"
#include "Base/Axis/Scale.h"
#include "Base/Const/PhysicalConstants.h"
#include "Base/Vector/GisasDirection.h"
#include "Device/Beam/Beam.h"
#include "Device/Beam/IFootprint.h"
#include "Resample/Element/SpecularElement.h"
#include <algorithm> // is_sorted

using PhysConsts::pi;

LambdaScan::LambdaScan(Scale* lambdaScale)
    : PhysicalScan(lambdaScale)
{
    std::vector<double> axis_values = m_axis->binCenters();
    if (!std::is_sorted(axis_values.begin(), axis_values.end()))
        throw std::runtime_error("LambdaScan: wavelength values are not "
                                 "sorted in ascending order.");
    if (axis_values.front() <= 0)
        throw std::runtime_error("LambdaScan: non-positive wavelengths.");

    for (size_t i = 0; i < nScan(); i++) {
        auto* beam = new Beam(defaultIntensity, m_axis->binCenter(i), defaultInclination);
        m_beams.push_back(beam);
    }
}

LambdaScan::LambdaScan(const Scale& lambdaScale)
    : LambdaScan(lambdaScale.clone())
{
}

LambdaScan::LambdaScan(std::vector<double> points)
    : LambdaScan(newListScan("lambda (nm)", std::move(points)))
{
}

LambdaScan::LambdaScan(int nbins, double lambda_min, double lambda_max)
    : LambdaScan(newEquiScan("lambda (nm)", nbins, lambda_min, lambda_max))
{
}

LambdaScan::~LambdaScan() = default;

LambdaScan* LambdaScan::clone() const
{
    auto* result = new LambdaScan(*m_axis);
    copyPhysicalScan(result);
    return result;
}

//! Generates simulation elements for specular simulations
std::vector<SpecularElement> LambdaScan::generateElements() const
{
    std::vector<SpecularElement> result;
    result.reserve(nScan());
    for (size_t i = 0; i < m_axis->size(); ++i) {
        const double kmag = 2 * pi / wavelengthAt(i);
        const R3 kvec = vecOfKAlphaPhi(kmag, inclinationAt(i), 0);
        const double footprint = footprintAt(i) ? footprintAt(i)->calculate(inclinationAt(i)) : 1;
        result.emplace_back(i, kvec.z() <= 0, 1., intensityAt(i), footprint, polarizerMatrixAt(i),
                            analyzerMatrix(), kvec);
    }
    return result;
}
