//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Base/Math/IntegratorCollection.cpp
//! @brief     Implements Integrator classes.
//!
//! @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 "Base/Math/IntegratorCollection.h"
#include "Base/Util/Assert.h"
#include <gsl/gsl_errno.h>
#include <stdexcept>
#include <string>

Integrator::Integrator()
    : m_gsl_f{m_Cfunction, nullptr}
{
}

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

GKAdaptiveIntegrator::GKAdaptiveIntegrator(size_t max_subintervals)
    : Integrator()
    , max_subintervals(max_subintervals)
    , m_workspace{gsl_integration_workspace_alloc(max_subintervals)}
{
}

GKAdaptiveIntegrator::~GKAdaptiveIntegrator()
{
    gsl_integration_workspace_free(m_workspace);
}

double GKAdaptiveIntegrator::integrate(const std::function<double(double)>& f, double lmin,
                                       double lmax, double epsabs, double epsrel, int key,
                                       size_t limit)
{
    ASSERT(limit <= max_subintervals);
    m_gsl_f.params = (void*)&f;
    double result, abserror;
    int status = gsl_integration_qag(&m_gsl_f, lmin, lmax, epsabs, epsrel, limit, key, m_workspace,
                                     &result, &abserror);
    if (status != GSL_SUCCESS && status != GSL_EROUND)
        throw std::runtime_error(std::string("QAG integration failed: ") + gsl_strerror(status));
    return result;
}

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

GKIntegrator::GKIntegrator()
    : Integrator()
{
}

double GKIntegrator::integrate(const std::function<double(double)>& f, double lmin, double lmax,
                               double epsabs, double epsrel)
{
    m_gsl_f.params = (void*)&f;
    double result, abserror;
    size_t neval;
    int status =
        gsl_integration_qng(&m_gsl_f, lmin, lmax, epsabs, epsrel, &result, &abserror, &neval);
    if (status != GSL_SUCCESS)
        throw std::runtime_error(std::string("QNG integration failed: ") + gsl_strerror(status));
    return result;
}

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

RombergIntegrator::RombergIntegrator(size_t subdivision_depth)
    : Integrator()
    , m_romberg_workspace{gsl_integration_romberg_alloc(subdivision_depth)}
{
}

RombergIntegrator::~RombergIntegrator()
{
    gsl_integration_romberg_free(m_romberg_workspace);
}

double RombergIntegrator::integrate(const std::function<double(double)>& f, double lmin,
                                    double lmax, double epsabs, double epsrel)
{
    m_gsl_f.params = (void*)&f;
    double result;
    size_t neval;
    int status = gsl_integration_romberg(&m_gsl_f, lmin, lmax, epsabs, epsrel, &result, &neval,
                                         m_romberg_workspace);
    if (status != GSL_SUCCESS)
        throw std::runtime_error(std::string("Romberg integration failed: ")
                                 + gsl_strerror(status));
    return result;
}
