//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sim/Scan/PhysicalScan.cpp
//! @brief     Implements interface PhysicalScan.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2023
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "Sim/Scan/PhysicalScan.h"
#include "Base/Axis/Scale.h"
#include "Base/Math/Numeric.h"
#include "Device/Pol/PolFilter.h"
#include "Param/Distrib/Distributions.h"

PhysicalScan::PhysicalScan(Scale* axis)
    : BeamScan(axis)
{
}

PhysicalScan::~PhysicalScan() = default;

void PhysicalScan::copyPhysicalScan(PhysicalScan* dest) const
{
    BeamScan::copyBeamScan(dest);
    if (m_lambda_distrib)
        dest->setWavelengthDistribution(*m_lambda_distrib);
    if (m_alpha_distrib)
        dest->setAngleDistribution(*m_alpha_distrib);
}

std::vector<const INode*> PhysicalScan::nodeChildren() const
{
    std::vector<const INode*> result;
    for (const INode* n : BeamScan::nodeChildren())
        result << n;
    if (m_lambda_distrib)
        result << m_lambda_distrib.get();
    if (m_alpha_distrib)
        result << m_alpha_distrib.get();
    return result;
}

double PhysicalScan::wavelength() const
{
    if (!isCommonWavelength())
        throw std::runtime_error("Wavelength changes during scan. "
                                 "Use 'wavelengthAt(i)' instead.");
    return m_beams.front()->wavelength();
}

double PhysicalScan::wavelengthAt(size_t i) const
{
    return m_beams[i]->wavelength();
}

void PhysicalScan::setWavelength(double lambda)
{
    for (auto& b : m_beams)
        b->setWavelength(lambda);
}

void PhysicalScan::setWavelengthAt(size_t i, double lambda)
{
    m_beams[i]->setWavelength(lambda);
}

void PhysicalScan::setWavelengthDistribution(const IDistribution1D& distr)
{
    m_lambda_distrib.reset(distr.clone());
}

double PhysicalScan::inclinationAt(size_t i) const
{
    return m_beams[i]->alpha_i();
}

void PhysicalScan::setInclination(double alpha)
{
    for (auto& b : m_beams)
        b->setInclination(alpha);
}

void PhysicalScan::setInclinationAt(size_t i, double alpha)
{
    m_beams[i]->setInclination(alpha);
}

void PhysicalScan::setAngleDistribution(const IDistribution1D& distr)
{
    m_alpha_distrib.reset(distr.clone());
}

size_t PhysicalScan::nDistributionSamples() const
{
    size_t alpha_samples = m_alpha_distrib ? m_alpha_distrib->nSamples() : 1;
    size_t lambda_samples = m_lambda_distrib ? m_lambda_distrib->nSamples() : 1;
    return lambda_samples * alpha_samples;
}

bool PhysicalScan::isCommonWavelength() const
{
    const auto ref = m_beams.front()->wavelength();
    for (const auto& b : m_beams)
        if (!Numeric::almostEqual(b->wavelength(), ref, 1))
            return false;
    return true;
}
