#ifndef BSPLINE_H
#define BSPLINE_H

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <string>

#include "gsl/gsl_bspline.h"
#include "gsl/gsl_multifit.h"
#include "gsl/gsl_rng.h"
#include "gsl/gsl_randist.h"
#include "gsl/gsl_statistics.h"
#include "gsl/gsl_version.h"

#include "Header.hh"
#include "ElementContainer.hh"

#include "AdvDomain.hh"
#include "AdvParamSet.hh"
#include "AdvMethod.hh"
#include "AdvMatrixUtil.hh"
#include "AdvMessage.hh"

/**
 * function fitting, smoothing, etc. by AdvBSpline functions
 *
 * @author TANIMORI Souichirou, AdvanceSoft Corp.
 * @version 0.0
 */
class AdvBSpline : virtual public AdvMethod, public AdvMatrixUtil, public AdvMessage {
//class AdvBSpline : public Method, public Message {

    private:
        static const std::string className;

    public:
        // the keys for paramters of B-spline
        static const std::string ORDER;
        static const std::string AUTOMATIC_KNOTS;
        static const std::string USE_UNIFORM_BREAK_POINTS;
        static const std::string NUMBER_OF_BREAK_POINTS;
        static const std::string BREAK_POINTS;

        // default values
        static UInt4 const DEFAULT_ORDER_OF_POLYNOMINAL;
        static Bool  const DEFAULT_AUTOMATIC_KNOTS;
        static Bool  const DEFAULT_USE_UNIFORM_BREAK_POINTS;
        static UInt4 const DEFAULT_NUMBER_OF_BREAK_POINTS;

    private:
        // control variables for fittineg B-spline
        size_t order;                    // the heighest order of B-spline basis, note that order = user-given value + 1
        Bool   automatic_knots;         // decide the number and the positions of knots automatically
        Bool   use_uniform_break_points; // true if use uniform break points
        size_t number_of_break_points;   // the number of break points
        gsl_vector *break_points;        // a list of break points
        //
        size_t number_of_coefficients;   //  of coefficients, = number_of_break_points + order - 2

        // inner valiables for B-spline
        size_t number_of_data_points;    // the number of source data points
        // gsl_vector *srcX; // source data points
        gsl_vector *srcY; // source data value at each data point
        gsl_vector *srcW; // weight for source data values at each data point

        gsl_vector *B;    // values of B-spline basis
        gsl_vector *c;    // coefficents for B-spline basis
        gsl_matrix *dB;   // values for derive B-spline basis at each data point

        gsl_matrix *X;    // matrix for determining the values of coefficients for B-spline basis
        gsl_matrix *cov;  // covariance matrix
        gsl_bspline_workspace *bspline_workspace;          // working space for B-spline
#if (GSL_MAJOR_VERSION > 1) && (GSL_MINOR_VERSION > 1)
        // gsl_bspline_workspace *deriv_workspace;     // workspace for B-spline derivatives
#else
        gsl_bspline_deriv_workspace *deriv_workspace;     // workspace for B-spline derivatives
#endif
        gsl_multifit_linear_workspace *multifit_workspace; // working square for nonlinear least square fitting

        Double chisq; // the valeu of chi square
        Double Rsq;   // residue square
        size_t dof;   // degree of freedom

        gsl_vector *resultX;      //
        gsl_vector *resultY;      // the values of the ftted function at each source data points
        gsl_vector *resultYError; //

        std::vector<Double> *resultBin; // for bins a element container as the result

    protected:
        Double likehood();


    public:
        /**
         *  constructor
         */
        AdvBSpline() ;

        /**
         *  destructor
         */
        ~AdvBSpline() ;

        /**
         *
         */
        AdvParamSet setDefaultParam(ElementContainer &src) ;

        /**
         *  check parameters
         *
         *  @param[in] src element container as source
         *  @param[in] domain the domain for the operation
         *  @param[in] param  method specific parameters
         */
        Bool checkParam(ElementContainer &src, AdvDomain &domain, AdvParamSet &param) ;

        /**
         *  translate to the inner form
         */
        void toInnerForm(ElementContainer &src, AdvDomain &domain, AdvParamSet &param) ;

        /**
         *  fit B-Spline to source data
         */
        void fit() ;

        /**
         *  evaluate the values of B-Spline at data points
         */
        void eval() ;

        void toElementContainer(ElementContainer &src, ElementContainer &dest) const ;

        void getParamDescription() const;

       AdvParamSet *getLatestConvergenceStat() const { return NULL; };

        AdvParamSet getFittedParam() const;

        Bool differentiable() const { return true; };

        Bool isMultiThreaded() const { return false; };

        std::vector< std::vector<Double> > getTrend();
};

#endif
