#include "AdvLorentzian.hh"
#include <cassert>
#include <cmath>

const Double AdvLorentzian::NC=1.0/fabs(atan2(0.0, -1.0));

/** default constructor */
AdvLorentzian::AdvLorentzian() : AdvFuncBase(*(new std::string("lorentzian")), *(new std::string("l")), NumberOfParamForLorentzian) {
}

AdvLorentzian::AdvLorentzian(const AdvLorentzian &obj) {
}

/** destructor */
AdvLorentzian::~AdvLorentzian() {
}

AdvLorentzian AdvLorentzian::operator=(const AdvLorentzian &obj) {
    return *this;
}

/**
 *  calcurate the denominator of the function
 *
 *  \param[in] x  argument of the function
 *  \param[in] c  the center of the function
 *  \param[in] w  the HWHM of the function
 *  \param[in] n  the power,  positive integer 1, 2, 3, ...
 *
 *  d(x, c, w, n) = d(x-c, 0.0, w, n)
 */
Double AdvLorentzian::denominator(const Double x, const Double c, const Double w, const Double n ) {
    assert (w != 0.0);
    Double t=(x-c)/w;
    return pow(w*(t*t+1), n);
}

/**
 *  evaluate the value of the function
 *
 *  \param[in] x  argument of the function
 *  \param[in] h  the height of the function
 *  \param[in] c  the center of the function
 *  \param[in] w  the HWHM of the function, w != 0.0
 *
 *  L(x,    h,   c, w) = L(x-c, h, 0.0, w)
 *  L(c,    h,   c, w) = L(c-c, h, 0.0, w) = L(0.0, h, 0.0, w) = h/(pi*w)
 *  L(c+-w, h,   c, w) = L(+-w, h, 0.0, w) = L(0.0, h, 0.0, w)/2.0
 *  L(x,    0.0, c, w) = 0.0
 */
Double AdvLorentzian::eval(const Double x, const Double h, const Double c, const Double w) {
    assert(w != 0.0);
    return NC*h/denominator(x, c, w, 1.0);
}

#ifdef HAVE_DIFFERENTIAL_MEMBER
/**
 *  evaluate the value of the 1st differential coefficient
 *
 *  \param[in] x argument of the function
 *  \param[in] h the height of the function
 *  \param[in] c the center of the function
 *  \param[in] w the HWHM of the function, w != 0.0
 *
 *                   d                  d
 *  L'(x, h, c, w) = --L(x, h, c, w) =  --L(x-c, h, 0.0, w)
 *                   dx                 dx
 *
 *  d                   d
 *  --L(x, h, c, w) = - --L(x, h, c, w)
 *  dc                  dx
 * L'(c,    h, c, w) = L'(c-c, h, 0.0, w) = L'(0.0, h, 0.0, w) = 0.0
 * L'(c+-w, h, c, w) = L'(+-w, h, 0.0, w) = L'(0.0. h, 0.0, w)/4.0
 *  
 */
Double AdvLorentzian::der1st(const Double x, const Double h, const Double c, const Double w) {
    assert(w != 0.0);
    Double t=(x-c)/w;
    return -2.0*NC*t/denominator(x, c, w,  2.0);
}

/**
 *  evaluate the value of the 2nd differential coefficient
 *
 *  \param[in] x argument of the function
 *  \param[in] h the height of the function
 *  \param[in] c the center of the function
 *  \param[in] w the HWHM of the function, w != 0.0
 *
 *                       d^2                  d^2
 *  L''(x,    h, c, w) = ----L(x, h, c, w) =  ----L(x-c, h, 0.0, w)
 *                       dx^2                 dx^2
 *  L''(c,    h, c, w) = L''(c-c, h, 0.0, w) =  L''(0.0, h, 0.0, w);
 *  L''(c+-w, h, c, w) = L''(+-w, h, 0.0, w) = -L''(0.0, h, 0.0, w)/8.0
 */
Double AdvLorentzian::der2nd(const Double x, const Double h, const Double c, const Double w) {
    assert(w != 0.0);
    Double t=(x-c)/w;
    return 2.0*NC*h*(3.0*t*t-1.0)/denominator(x, c, w, 3.0);
}

/**
 *  evaluate the differeitiation coefficient for w
 *
 *                   d                  d
 *  L'(x, h, c, w) = -- L(x, h, c, w) = --L(x-c, h, 0.0, w)
 *                   dw                 dw
 *  L'(c+-w, h, c, w) = L'(w, h, 0.0, w) = 0.0
 */
Double AdvLorentzian::derW(const Double x, const Double h, const Double c, const Double w) {
    assert(w != 0.0);
    Double t=(x-c)/w;
    return  NC*h*(t*t-1.0)/denominator(x, c, w, 2.0);
}

#endif // HAVE_DIFFERENTIAL_MEMBER

#ifdef USE_POINTER
/**
 *  evaluate the value of the function
 *
 *  @param[in] x  argument of the function
 *  @param[in] p  parameters of the function
 */
Double AdvLorentzian::eval(const Double x, const Double *p) {
    return eval(x, p[0], p[1], p[2]);
}

#ifdef HAVE_DIFFERENTIAL_MEMBER
/**
 *  evaluate the value of the 1st differential coefficient
 *
 *  @param[in] x  argument of the function
 *  @param[in] p  parameters of the function
 */
Double AdvLorentzian::der1st(const Double x, const Double *p) {
    return der1st(x, p[0], p[1], p[2]);
}

/**
 *  evaluate the value of the 2nd. differential coefficient
 *
 *  @param[in] x  argument of the function
 *  @param[in] p  parameters of the function
 */
Double AdvLorentzian::der2nd(const Double x, const Double *p) {
    return der2nd(x, p[0], p[1], p[2]);
}

/**
 *  evaluate the gradient of the coefficient for parameters
 *
 *  @param[in] x  argument of the function
 *  @param[in] p  parameters of the function
 */
Double *AdvLorentzian::gradient(const Double x, const Double *p) {

    Double *retval= new Double[NumberOfParamForLorentzian];
    retval[0]=        eval(x, 1.0,  p[1], p[2]);
    retval[1]= -1.0*der1st(x, p[0], p[1], p[2]);
    retval[2]=        derW(x, p[0], p[1], p[2]);

    return retval;
}
#endif // HAVE_DIFFERENTIAL_MEMBER
#endif // USE_POINTER

#ifdef USE_VECTOR
Double AdvLorentzian::eval(const Double x, const std::vector<Double> &p) {
    return eval(x, p[0], p[1], p[2]);
}

#ifdef HAVE_DIFFERENTIAL_MEMBER
Double AdvLorentzian::der1st(const Double x, const std::vector<Double> &p) {
    return der1st(x, p[0], p[1], p[2]);
}

Double AdvLorentzian::der2nd(const Double x, const std::vector<Double> &p) {
    return der2nd(x, p[0], p[1], p[2]);
}

std::vector<Double> AdvLorentzian::gradient(const Double x, const std::vector<Double> &p) {

    std::vector<Double> *retval= new std::vector<Double>();
    retval->push_back(        eval(x, 1.0,  p[1], p[2]) );
    retval->push_back( -1.0*der1st(x, p[0], p[1], p[2]) );
    retval->push_back(        derW(x, p[0], p[1], p[2]) );

    return *retval;
}
#endif // HAVE_DIFFERENTIAL_MEMBER
#endif // USE_VECTOR
