#include "AdvGaussian.hh"
#include <cassert>
#include <iostream>
#include <ostream>

//#define USE_NORMARIZED_FORM

const Double AdvGaussian::LOG2=log(2.0);
/** constant for normarize factor : sqry(log(2.0)/pi) */
const Double AdvGaussian::NC=sqrt(LOG2/fabs(atan2(0.0, -1.0)));


/** default constructor */
AdvGaussian::AdvGaussian() : AdvFuncBase(*(new std::string("gaussian")), *(new std::string("g")), NumberOfParamForGaussian) {
}

/** copy constructor */
AdvGaussian::AdvGaussian(const AdvGaussian &) {
}

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

/**
 *  evaluate the value of the function
 *
 *  \param[in] x
 *  \param[in] h the height of the function
 *  \param[in] c the center of the function
 *  \param[in] w the half-value half width of the function
 *
 *  S.D. sigma=w/sqrt(log2) (standard deviation)
 *
 *  G(x, h, c, w) = G(x-c, h, 0.0, w).
 *  G(c, h, c, w) = G(c-c, h, 0.0, w) = G(0.0, h, 0.0, w).
 *  G(c+-w, h, c, w) = G(c, h, c, w)/2.0 = G(0.0, h, 0.0, w)/2.0
 *  G(c+-S.D., h, c, w) = G(+-S.D., h, 0.0, w) = G(0.0, h, 0.0, w)/e
 *
 *  G(x, h, c, w) = G((x-c)/w, h, 0.0, 1.0) if you do not use normarized form
 */
Double AdvGaussian::eval(const Double x, const Double h, const Double c, const Double w) {
    assert(w != 0.0);
    Double t=(x-c)/w;
#ifdef USE_NORMARIZED_FORM
    return h*exp(-LOG2*t*t)*NC/w; // nomarized form
#else
    return h*exp(-LOG2*t*t);
#endif
}

#ifdef HAVE_DIFFERENTIAL_MEMBER
/**
 *  evaluate the value of the 1st. differential coefficient
 *
 *  d                       d
 *  --G(c, h, c, w) = 0.0,  - G(c+-w, h, c, w) = -+(log2)/w)*G(0.0, h, 0.0, w)
 *  dx                      dx
 */
Double AdvGaussian::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*LOG2/w)*t*eval(x, h, c, w);
}

/*
 *  evaluate the value of the 2nd. differential coefficient
 *
 *  S.D. sigma=w/sqrt(log2) (standard deviation)
 *
 *  d^2                             d^2
 *  ----G(c+-S.D., h, c, w) = 0.0,  ----G(c+-w, h, c, w) = -((log2)/pow(x, 2.0))*((2log2)-1.0)
 *  dx^2                            dx^2
 */
Double AdvGaussian::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*LOG2/(w*w))*(2.0*LOG2*t*t-1.0)*eval(x, h, c, w);
}

/**
 *  d
 *  --G(x, h, c, w)
 *  dx
 */
Double AdvGaussian::derW(const Double x, const Double h, const Double c, const Double w) {
    assert(w != 0.0);
    Double t=(x-c)/w;
#ifdef USE_NORMARIZED_FORM
    return eval(x, h, c, w)*(2.0*LOG2*t*t-1.0)/w;
#else
    return eval(x, h, c, w)*(2.0*LOG2*t*t    )/w;
#endif
}

#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 AdvGaussian::eval(const Double x, const Double *p) {
    assert(p[2] != 0.0);
    return eval(x, p[0], p[1], p[2]);
}

/**
 *  evaluate the value of the 1st. differential coefficient
 *
 *  @param[in] x  argument of the function
 *  @param[in] p  parameters of the function
 */
Double AdvGaussian::der1st(const Double x, const Double *p) {
    assert(p[2] != 0.0);
    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 AdvGaussian::der2nd(const Double x, const Double *p) {
    assert(p[2] != 0.0);
    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
 *
 *  d
 *  --G(x, h, c, w) = G(x, 1.0, c, w).
 *  dh
 *  d                   d
 *  --G(x, h, c, w) = - --G(x, h, c, w).
 *  dc                  dx
 *  d                   d
 *  --G(x, h, c, w) = - --G(x, h, c, w).
 *  dw                  dx
 */
Double *AdvGaussian::gradient(const Double x, const Double *p) {
    assert(p[2] != 0.0);

    Double *retval= new Double[NumberOfParamForGaussian];

    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 // USE_POINTER

#ifdef USE_VECTOR
/**
 *  evaluate the value of the function
 *
 *  \param[in] x  argument of the function
 *  \param[in] p  parameters of the function
 */
Double AdvGaussian::eval(const Double x, const std::vector<Double> &p) {
    //std::cerr << "Debug: AdvGaussian::eval(const Double x, const std::vector<Double> &p): p.size()=" << p.size() << std::endl;
    //std::cerr.flush();
    //std::cerr << "Debug: AdvGaussian::eval(const Double x, const std::vector<Double> &p): p=(";
    //for (std::vector<Double>::const_iterator xp=p.begin(); xp != p.end(); ++xp) {
    //    std::cerr << " " << *xp;
    //}
    //std::cerr << ")" << std::endl;
    //std::cerr.flush();

    assert(p.size() >= NumberOfParamForGaussian);
    assert(p[2] != 0.0);
    return eval(x, p[0], p[1], p[2]);
}

Double AdvGaussian::der1st(const Double x, const std::vector<Double> &p) {
    assert(p.size() == NumberOfParamForGaussian);
    assert(p[2] != 0.0);
    return der1st(x, p[0], p[1], p[2]);
}

Double AdvGaussian::der2nd(const Double x, const std::vector<Double> &p) {
    assert(p.size() == NumberOfParamForGaussian);
    assert(p[2] != 0.0);
    return der2nd(x, p[0], p[1], p[2]);
}

std::vector<Double> AdvGaussian::gradient(const Double x, const std::vector<Double> &p) {
    assert(p.size() == NumberOfParamForGaussian);
    assert(p[2] != 0.0);

    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 // USE_VECTOR
