//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Aggregate/InterferenceHardDisk.cpp
//! @brief     Implements class InterferenceHardDisk.
//!
//! @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/Aggregate/InterferenceHardDisk.h"
#include "Base/Math/Bessel.h"
#include "Base/Math/IntegratorGK.h"
#include "Base/Util/Assert.h"
#include <cmath>
#include <numbers>

using std::numbers::pi;

namespace {

const double p = 7.0 / 3.0 - 4.0 * std::sqrt(3.0) / pi;

double Czero(double packing)
{
    double numerator = 1.0 + packing + 3.0 * p * packing * packing - p * std::pow(packing, 3);
    double denominator = std::pow(1.0 - packing, 3);
    return -numerator / denominator;
}

double S2(double packing)
{
    double factor = 3.0 * packing * packing / 8.0;
    double numerator = 8.0 * (1.0 - 2.0 * p) + (25.0 - 9.0 * p) * p * packing
                       - (7.0 - 3.0 * p) * p * packing * packing;
    double denominator = 1.0 + packing + 3.0 * p * packing * packing - p * std::pow(packing, 3);
    return factor * numerator / denominator;
}

double W2(double x)
{
    return 2.0 * (std::acos(x) - x * std::sqrt(1.0 - x * x)) / pi;
}

} // namespace


InterferenceHardDisk::InterferenceHardDisk(double radius, double density, double position_var)
    : IInterference(position_var)
    , m_radius(radius)
    , m_density(density)
{
    validateOrThrow();
}

InterferenceHardDisk* InterferenceHardDisk::clone() const
{
    auto* result = new InterferenceHardDisk(m_radius, m_density, m_position_var);
    return result;
}
double InterferenceHardDisk::iff_without_dw(const R3 q) const
{
    ASSERT(m_validated);
    const double qx = q.x();
    const double qy = q.y();
    const double q2r = 2.0 * std::sqrt(qx * qx + qy * qy) * m_radius;
    const double packing = packingRatio();
    const double c_zero = Czero(packing);
    const double s2 = S2(packing);
    const double c_q = (2 * pi)
                       * RealIntegrator().integrate(
                           [=](double x) -> double {
                               double cx =
                                   c_zero * (1.0 + 4.0 * packing * (W2(x / 2.0) - 1.0) + s2 * x);
                               return x * cx * Math::Bessel::J0(q2r * x);
                           },
                           0.0, 1.0);
    const double rho = 4.0 * packing / pi;
    return 1.0 / (1.0 - rho * c_q);
}

double InterferenceHardDisk::packingRatio() const
{
    return pi * m_radius * m_radius * m_density;
}

std::string InterferenceHardDisk::validate() const
{
    std::vector<std::string> errs;
    requestGt0(errs, m_radius, "Radius");
    requestGe0(errs, m_density, "TotalParticleDensity");
    requestIn(errs, packingRatio(), "packing_ratio", 0, .65);
    if (!errs.empty())
        return jointError(errs);
    m_validated = true;
    return "";
}
