//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sample/Correlation/Distribution1DSampler.cpp
//! @brief     Defines class interface Profile1D, and children thereof.
//!
//! @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/Correlation/Distribution1DSampler.h"
#include <numbers>
#include <random>

using std::numbers::pi;

Distribution1DSampler::~Distribution1DSampler() = default;

double Distribution1DCauchySampler::randomSample(int seed) const
{
    // BornAgain Cauchy Distribution = std library Exponential distribution
    std::mt19937_64 rd(seed < 0 ? std::random_device{}() : seed);
    std::mt19937 gen(seed < 0 ? rd()
                              : seed); // Standard mersenne_twister_engine seeded with mt19937_64
    std::exponential_distribution<double> expDist(m_lambda);
    double value = expDist(gen);

    std::bernoulli_distribution bernoulliDist(0.5);
    bool sign = bernoulliDist(gen);

    return sign ? value : -value;
}

double Distribution1DGaussSampler::randomSample(int seed) const
{
    // BornAgain Gauss Distribution = std library Normal distribution
    std::mt19937_64 rd(seed < 0 ? std::random_device{}() : seed);
    std::mt19937 gen(seed < 0 ? rd() : seed);
    std::normal_distribution<double> normalDist(m_mean, m_stddev);

    return normalDist(gen);
}

double Distribution1DGateSampler::randomSample(int seed) const
{
    // BornAgain Gate Distribution = std library Uniform distribution
    std::mt19937_64 rd(seed < 0 ? std::random_device{}() : seed);
    std::mt19937 gen(seed < 0 ? rd() : seed);
    std::uniform_real_distribution<double> uniformDist(m_a, m_b);

    return uniformDist(gen);
}

double Distribution1DTriangleSampler::randomSample(int seed) const
{
    std::mt19937_64 rd(seed < 0 ? std::random_device{}() : seed);
    std::mt19937 gen(seed < 0 ? rd() : seed);

    // generate a cdf value between 0 and 1
    std::uniform_real_distribution<> uniformDist(0.0, 1.0);
    double cdf_value = uniformDist(gen);

    // solve for x by inverting the cdf of Triangle Distribution
    if (cdf_value <= 0.5)
        return (-m_omega + m_omega * std::sqrt(2 * cdf_value));
    return (m_omega - m_omega * std::sqrt(2 * (1 - cdf_value)));
}

double Distribution1DCosineSampler::randomSample(int seed) const
{
    std::mt19937_64 rd(seed < 0 ? std::random_device{}() : seed);
    std::mt19937 gen(seed < 0 ? rd() : seed);

    // generate a cdf value between 0 and 1
    std::uniform_real_distribution<> uniformDist(0.0, 1.0);
    double cdf_value = uniformDist(gen);

    // solve for x from the cdf of Cosine Distribution using Newton-Raphson method
    double func = 0.0, funcDeriv = 0.0, x = 0.0;

    // initial guess for x
    if (cdf_value <= 0.5)
        x = -m_omega / 2;
    else
        x = m_omega / 2;

    bool convergedSoln = false;
    while (!convergedSoln) {
        func = x + m_omega / pi * std::sin(pi * x / m_omega) + m_omega * (1 - 2 * cdf_value);
        funcDeriv = 1 + std::cos(pi * x / m_omega);

        x = x - func / funcDeriv;

        if (std::abs(func / funcDeriv) < 0.001)
            convergedSoln = true;
    }

    return x;
}
