#include "cantera/base/ct_defs.h"
#include <math.h>

#ifdef USE_BOOST_MATH
#include <boost/math/special_functions/erf.hpp>
using boost::math::erf;
#endif

#include "cantera/spectra/LineBroadener.h"

using namespace std;
using namespace Cantera;

namespace Cantera
{

Lorentzian::Lorentzian(doublereal gamma)
{
    m_hwhm = gamma;
    m_hwhm2 = m_hwhm*m_hwhm;
}

/**
 * The Lorentzian profile for collision-broadened lines.
 *
 *\f[
 * \frac{1}{\pi} \frac{\gamma}{ (\Delta\nu)^2 + \gamma^2}
 *\f]
 * Units: 1/wavenumber (or cm).
 */
doublereal Lorentzian::profile(doublereal deltaFreq)
{
    return (1.0/Cantera::Pi) *m_hwhm/(deltaFreq*deltaFreq + m_hwhm2);
}

/**
 *
 * The cumulative profile, given by
 * \f[
 * \frac{1}{\pi} \tan^{-1}\left(\frac{\Delta\nu}{gamma}\right) + 0.5
 * \f]
 */
doublereal Lorentzian::cumulative(doublereal deltaFreq)
{
    return (1.0/Pi) * atan(deltaFreq/m_hwhm) + 0.5;
}

doublereal Lorentzian::width()
{
    return 2.0*m_hwhm;
}

Gaussian::Gaussian(doublereal sigma)
{
    m_sigma = sigma;
    m_sigma2 = m_sigma*m_sigma;
}

doublereal Gaussian::profile(doublereal deltaFreq)
{
    //cout << "entered Gaussian::profile" << endl;
    //cout << "deltaFreq = " << deltaFreq << endl;
    //cout << "m_sigma = " << m_sigma << endl;
    return 1.0/(m_sigma * Cantera::SqrtTwo *Cantera::SqrtPi) *
           exp(-deltaFreq*deltaFreq/(2.0*m_sigma2));
}

doublereal Gaussian::cumulative(doublereal deltaFreq)
{
    return 0.5*(1.0 + erf(deltaFreq/(m_sigma*SqrtTwo)));
}

doublereal Gaussian::width()
{
    return 2.0*m_sigma*sqrt(log(4.0));
}



/**
 * @param sigma The standard deviation of the Gaussian
 * @param gamma The half-width of the Lorentzian.
 */
Voigt::Voigt(doublereal sigma, doublereal gamma)
{
    m_sigma = sigma;
    m_sigma2 = m_sigma*m_sigma;
    m_gamma_lor = gamma;
    m_sigsqrt2 = SqrtTwo*m_sigma;
    m_gamma = gamma/m_sigsqrt2;
    m_eps = 1.0e-20;
}

void Voigt::testv()
{
    m_gamma = 1.0e1;
    cout << F(1.0) << endl;
    m_gamma = 0.5;
    cout << F(1.0) << endl;
    m_gamma = 0.0001;
    cout << F(10.0) << endl;
}
/**
 * This method evaluates the function
 * \f[
 * F(x, y) = \frac{y}{\pi}\int_{-\infty}^{+\infty} \frac{e^{-z^2}}
 * {(x - z)^2 + y^2} dz
 * \f]
 * The algorithm used to cmpute this function is described in the
 * reference below.  @see F. G. Lether and P. R. Wenston, "The
 * numerical computation of the %Voigt function by a corrected
 * midpoint quadrature rule for \f$ (-\infty, \infty) \f$. Journal
 * of Computational and Applied Mathematics}, 34 (1):75--92, 1991.
 */
doublereal Voigt::F(doublereal x)
{

    if (x < 0.0) {
        x = -x;
    }
    double y = m_gamma;

    double c3 = log(Pi*m_eps/2.0);
    double tau = sqrt(-log(y) - c3);
    double b = (tau + x)/y;
    double t = b*y;
    double f1, f2, f3;
    const double c0 = 2.0/(Pi*exp(0.0));
    const double c1 = 1.0/SqrtTwo;
    const double c2 = 2.0/SqrtPi;

    if (y > c0/m_eps) {
        return 0.0;
    }
    double f0, ef0;
    while (1 > 0) {
        f0 = Pi*Pi/(t*t);
        ef0 = exp(-f0);
        f1 = c2*y*ef0;
        f2 = fabs(y*y - Pi*Pi/(t*t));
        f3 = 1.0 - ef0*ef0;
        t *= c1;
        if (f1/(f2*f3) < 0.5*m_eps) {
            break;
        }
    }
    double h = t/y;
    int N = int(0.5 + b/h);
    double S = 0.0;
    double u = h/2;
    for (int i = 0; i < N; i++) {
        S += (1.0 + exp(-4.0*x*y*u))*exp(-pow(y*u-x,2))/(u*u+1.0);
        u += h;
    }
    double Q = h*S/Pi;
    double C = 0.0;
    if (y*y < Pi/h) {
        C = 2.0*exp(y*y - x*x)*cos(2*x*y)/(1.0 + exp(2*Pi/h));
    } else {
        return 0.0;
    }
    return Q + C;
}

/**
 * Voigt profile.
 *
 * Not sure that constant is right.
 */
doublereal Voigt::profile(doublereal deltaFreq)
{
    const double ff = 1.0/(m_sigsqrt2*SqrtPi);
    return ff*F(deltaFreq/m_sigsqrt2);
}

}
