#ifndef EQUALSPACING
#define EQUALSPACING

#include <algorithm>
#include <cmath>  // abs
#include <limits>
#include <string>
#include <vector>
#include <utility> // pair

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

#include "AdvMessage.hh"

#include "AdvDomain.hh"
#include "AdvParamSet.hh"

#include "AdvMultiDataMethod.hh"
#include "AdvConvolutionConsts.hh"

class AdvEqualSpacing : virtual public AdvConvolutionConsts, public AdvMultiDataMethod, virtual public AdvMessage {
    private:
        static const string className; // = string("AdvEqualSpacing");

    public:
        static const string BIN_WIDTH; // = string("bin width");

    private:
        /** index of resolution or responce data in ElementContainerArray */
        UInt4 resID;
        /** the bin of the user given resolution or response data */
        vector<Double> resBin;
        /** the value of the user given resolution or response data */
        vector<Double> resY;
        /** the value error of the user given resolution or response data */
        vector<Double> resYErr;

        /** bins for the user given source data */
        vector< vector<Double> > srcBin;
        /** values for the user given source data */
        vector< vector<Double> > srcY;
        /** value errors for the user given source data */
        vector< vector<Double> > srcYErr;

        /** the relatice value of the bin width error */
        Double widthThreshold;
        /** the common width of all bin for new data */
        Double width;

        /** new bins for resolution or responce data */
        vector<Double> newResBin;
        /** new values for resolution or responce data */
        vector<Double> newResY;
        /** new value errors for resolution or responce data */
        vector<Double> newResYErr;

        /** new bins for the source data */
        vector< vector<Double> > newSrcBin;
        /** new values for the source data */
        vector< vector<Double> > newSrcY;
        /** new value errors for the source data */
        vector< vector<Double> > newSrcYErr;

    private:
        /** check whethe all width of given bins are equally spaced or not.
         *  @paam[in] bin
         *  @paam[in] widthTreshold
         *  @retval true when all width of given bins are equally-spaced.
         */
        Bool checkAllBinWidth(const vector<Double>& bin, const Double widthThreshold);

        /** import the resolution or response data.
         *  @param[in] ec an ElementContainer as resolution or response
         *  @param[in] ec the domain for ec
         */
        void importResolution(ElementContainer& ec, AdvDomain& domain);

        /** import the source data.
         *  @param[in] src    the element container to be imported.
         *  @apram[in] domain the domain for the element container
         */
        void importSource(ElementContainer& ec, AdvDomain& domain);

        /** erase zero sequence of y at the front and the correspondiny values of b and e.
         *  @param[in] b the bin of a histogram
         *  @param[in] y the value of a histogram
         *  @param[in] e the value error of a histogram
         */
        void eraseZeroSequenceAtFront(vector<Double>& bin, vector<Double>& y, vector<Double>& e);

        /** erase zero sequence of y at the back and the correspondiny values of b and e.
         *  @param[in] b the bin of a histogram
         *  @param[in] y the value of a histogram
         *  @param[in] e the value error of a histogram
         */
        void eraseZeroSequenceAtBack(vector<Double>& bin, vector<Double>& y, vector<Double>& e);

        /** add zero sequence of y at the front and the correspondiny value of b and e.
         *  @param[in] w the width of bin to be added
         *  @param[in] n the number of bin to be added
         *  @param[in] b the bin of a histogram
         *  @param[in] y the value of a histogram
         *  @param[in] e the value error of a histogram
         */
        void addZeroSequenceAtFront(const Double w, const UInt4 n, vector<Double>& b, vector<Double>& y, vector<Double>& e);

        /** add zero sequence of y at the back and the correspondiny value of b and e
         *  @param[in] w the width of bin to be added
         *  @param[in] n the number of bin to be added
         *  @param[in] b the bin of a histogram
         *  @param[in] y the value of a histogram
         *  @param[in] e the value error of a histogram
         */
        void addZeroSequenceAtBack(const Double w, const UInt4 n, vector<Double>& b, vector<Double>& y, vector<Double>& e);

        /** merge two bin data.
         *      different from merge in &lt;algorithm$gt;, the mearged sequence do not  have duplicated value.
         *  @param[in] bin1
         *  @param[in] bin2
         */
        vector<Double> mergeBin(const vector<Double>& bin1, const vector<Double>& bin2);

        /** return the index No. of the section that includes the given value.
         *         the section number defined in following way,
         *         (-&Inf;, x(0)) &rarrow; -1,
         *         [x(i), x(i+1)) &rarrow; i (0 &le; i &le; n-1)
         *         [x[n], &Inf;) &rarrow; n
         *
         *  @param[in] bin
         *  @param[in] v
         *  @retval the No. of the section that includes the given value
         */
        Int4 indexOfBin(const vector<Double>& bin, const Double v);

        /** search the bin bound with the specified value and return the index of the found bound.
         *  @param[in] bin a bins to be searched
         *  @param[in] v   the bin bound to search
         *  @retval the index of the found bin bound
         *  @retval negative integer when not found the given bound
         */
        Int4 indexOfBinBound(const vector<Double>& bin, const Double v);

        /** re-assign the values and errors on old bin to a hisogram on new bin.
         *  @param[in]  oldBin  the old bins
         *  @param[in]  oldY    th value of histogram on the old bins
         *  @param[in]  oldErr  the value error on the old bins
         *  @param[in]  newBin  the newBin of a new histogram
         *  @param[out] newY    the new values on the new bins
         *  @param[out] newErr  the new value errors on the new bins
         */
        void assignValueAndErr(const vector<Double>& oldBin, const vector<Double>& oldY, const vector<Double>& oldErr,
                   const vector<Double>& newBin, vector<Double>& newY, vector<Double>& newErr);

        /** reconstruct the old histogram on a new bin
         *  @param[in]  oldBin
         *  @param[in]  oldY
         *  @param[in]  oldErr
         *  @param[in]  newBin
         *  @param[out] newY
         *  @param[out] newErr
         */
        void binning(const vector<Double>& oldBin, const vector<Double>& oldY, const vector<Double>& oldErr,
                     const vector<Double>& newBin, vector<Double>& newY, vector<Double>& newErr);

        void extRebin(const vector<Double>& oldB, const vector<Double>& oldY, const vector<Double>& oldE,
                      const vector<Double>& newB,       vector<Double>& newY,       vector<Double>& newE);

        /** export results as an element container.
         *  @param[in] src
         *  @param[in] bin the bin of a histogram to be exported
         *  @param[in] y   the value of a histogram to be exported
         *  @param[in] e   the value errors of a histogram to be exported
         *  @retval an ElementContainer that includes a histogram to be exported.
         */
        ElementContainer* exportAsElementContainer(ElementContainer* src, const vector<Double>& bin, const vector<Double>& y, const vector<Double>& e) const ;

    protected:
        /** always return 0.0 */
        Double likehood() { return 0.0; };

    public:
        ////////////////    constructors and destructor    ////////////////
        /** constructor */
        AdvEqualSpacing() : AdvMultiDataMethod(string("AdvEqualSpacing")) {};

        /** destructor */
        ~AdvEqualSpacing() {};

        ////////////////    the method property    ////////////////
        /** always false since the method does not support diffrential calculation */
        Bool differentiable() const { return false; };

        /** always false since the method does not support Multi Thread */
        Bool isMultiThreaded() const { return false; };

        ////////////////    control method    ////////////////
        /** set parameters to default values
         *  @param[in] src an instance of an ElementContainer.
         */
        AdvParamSet setDefaultParam(ElementContainerArray& src);

        /** check the consistency for parameters
         *  @param[in] src      an instance of an ElementContainer
         *  @param[in] domains  a list of domains for ElementContainers in the array.
         *  @param[in] paramSet a set of parameters
         */
        Bool checkParam(ElementContainerArray& src, vector<AdvDomain>& domains, AdvParamSet& paramSet);

        /** import parameters
         *  @param[in] src      an instance of an ElementContainer
         *  @param[in] domains  a list of domains for ElementContainers in the array.
         *  @param[in] paramSet a set of parameters
         */
        void toInnerForm(ElementContainerArray& src, vector<AdvDomain>& domains, AdvParamSet& paramSet);

        /** evaluate
         */
        void eval();

        void toElementContainerArray(ElementContainerArray& src, ElementContainerArray& dest) const ;


        ////////////////    stab    ////////////////
        void fit() {};
        Bool isRunning () { return false; };
        void stop() {};
        AdvParamSet  getFittedParam() const { return *(new AdvParamSet()); };
        AdvParamSet* getLatestConvergenceStat() const { return new AdvParamSet(); };
        void toElementContainer(ElementContainerArray& src, UInt4 id, ElementContainer& dest) const {};
        vector< vector<Double> > getTrend()        { return *(new vector< vector<Double> >()); } ;
        vector< vector<Double> > getTrend(UInt4 i) { return *(new vector< vector<Double> >()); } ;
        void getParamDescription() const {};
};

#endif // EQUALSPACING
