//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Interface/AutocorrelationModels.cpp
//! @brief     Implement AutocorrelationModel classes.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2024
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "Sample/Interface/AutocorrelationModels.h"
#include "Base/Util/Assert.h"
#include <cmath>
#include <numbers>

using std::numbers::pi;

AutocorrelationModel::AutocorrelationModel(double maxSpatFrequency)
    : m_max_spatial_frequency(maxSpatFrequency)
{
}

void AutocorrelationModel::validate() const
{
    if (m_max_spatial_frequency <= 0)
        throw std::runtime_error("maxSpatialFrequency <= 0");
}

//-------------------------------------------------------------------------------------------------

//! @param sigma: height scale of the roughness in nanometers (usually close to rms)
//! @param hurstParameter: hurst parameter which describes how jagged the interface,
//! dimensionless (0.0, 1.0], where 0.0 gives more spikes, 1.0 more smoothness
//! @param lateralCorrLength: lateral correlation length of the roughness in nanometers
SelfAffineFractalModel::SelfAffineFractalModel(double sigma, double hurst, double lateralCorrLength,
                                               double maxSpatFrequency)
    : AutocorrelationModel(maxSpatFrequency)
    , m_sigma(sigma)
    , m_hurst_parameter(hurst)
    , m_lateral_corr_length(lateralCorrLength)
{
    SelfAffineFractalModel::validate();
}

//! Power spectral density of the surface roughness is a result of two-dimensional
//! Fourier transform of the correlation function of the roughness profile.
//!
//! Based on Palasantzas, Phys Rev B, 48, 14472 (1993)
double SelfAffineFractalModel::spectralFunction(double spatial_f) const
{
    ASSERT(m_validated);
    if (spatial_f > m_max_spatial_frequency)
        return 0;

    const double Qpar2 = pow(2 * pi * spatial_f, 2);
    const double H = m_hurst_parameter;
    const double clength2 = m_lateral_corr_length * m_lateral_corr_length;
    return 4.0 * pi * H * m_sigma * m_sigma * clength2 * std::pow(1 + Qpar2 * clength2, -1 - H);
}

SelfAffineFractalModel* SelfAffineFractalModel::clone() const
{
    return new SelfAffineFractalModel(m_sigma, m_hurst_parameter, m_lateral_corr_length,
                                      m_max_spatial_frequency);
}

void SelfAffineFractalModel::validate() const
{
    AutocorrelationModel::validate();
    if (m_sigma < 0)
        throw std::runtime_error("sigma < 0");
    if (m_hurst_parameter < 0 || m_hurst_parameter > 1)
        throw std::runtime_error("hurst_parameter not in [0,1]");
    if (m_lateral_corr_length < 0)
        throw std::runtime_error("lateral_corr_length < 0");

    m_validated = true;
}

double SelfAffineFractalModel::rms() const
{
    // integration of spectral function: rms^2 = Integrate[PSD(f) * 2*pi*f, {f, 0, max_frequency}]
    const double H = m_hurst_parameter;
    const double val = 2 * pi * m_lateral_corr_length * m_max_spatial_frequency;
    return m_sigma * std::sqrt(1. - std::pow(1 + val * val, -H));
}

//-------------------------------------------------------------------------------------------------

LinearGrowthModel::LinearGrowthModel(double particle_volume, double damp1, double damp2,
                                     double damp3, double damp4, double maxSpatFrequency)
    : AutocorrelationModel(maxSpatFrequency)
    , m_cluster_volume(particle_volume)
    , m_damp1(damp1)
    , m_damp2(damp2)
    , m_damp3(damp3)
    , m_damp4(damp4)
{
    LinearGrowthModel::validate();
}

double LinearGrowthModel::spectralFunction(double spectrum_below, double thickness,
                                           double spatial_f) const
{
    ASSERT(m_validated);
    if (spatial_f > m_max_spatial_frequency)
        return 0;

    if (!std::isfinite(thickness))
        return spectrum_below;

    double inherited_spectrum = spectrum_below;
    double growth_spectrum = m_cluster_volume * thickness;

    const double damp = damping(spatial_f);
    if (damp > 0) {
        const double exponent = std::exp(-damp * thickness);
        inherited_spectrum = spectrum_below * exponent;
        growth_spectrum = m_cluster_volume * (1. - exponent) / damp;
    }
    return inherited_spectrum + growth_spectrum;
}

double LinearGrowthModel::crosscorrSpectrum(double spectrum_below, double thickness,
                                            double spatial_f) const
{
    ASSERT(m_validated);
    const double damp = damping(spatial_f);
    if (damp == 0 || !std::isfinite(thickness))
        return spectrum_below;

    return spectrum_below * std::exp(-damp * thickness);
}

LinearGrowthModel* LinearGrowthModel::clone() const
{
    return new LinearGrowthModel(m_cluster_volume, m_damp1, m_damp2, m_damp3, m_damp4,
                                 m_max_spatial_frequency);
}

void LinearGrowthModel::validate() const
{
    AutocorrelationModel::validate();
    if (m_cluster_volume < 0)
        throw std::runtime_error("particleVolume < 0");
    if (m_damp1 < 0 || m_damp2 < 0 || m_damp3 < 0 || m_damp4 < 0)
        throw std::runtime_error("a damping coefficient is < 0");

    m_validated = true;
}

double LinearGrowthModel::damping(double spatial_f) const
{
    return m_damp1 * spatial_f + m_damp2 * std::pow(spatial_f, 2) + m_damp3 * std::pow(spatial_f, 3)
           + m_damp4 * std::pow(spatial_f, 4);
}
