#include <ios>
#include <iostream>
#include <istream>
#include <ostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>

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

#include "PeakData.hh"
#include "PeakSearch.hh"
#include "Domain.hh"
#include "ParamSet.hh"

#include "FuncBase.hh"
#include "FuncParser.hh"

#include "MovingAverage.hh"
#include "BSpline.hh"

#include "MethodType.hh"
#include "LevmarConsts.hh"
#include "MultiDataLevmar.hh"

#include "OperationType.hh"
#include "MultiDataPeakFit.hh"



Double dist(const Double x, const UInt4 nPeaks, const Double a[], const Double c[], const Double w[]) {

    Double sum=0.0;
    for (UInt4 i=0; i<nPeaks; ++i) {
        sum += a[i]*exp(-1.0*pow((x-c[i])/w[i], 2.0));
    }
    return sum;
}

ElementContainer init_src(const Double xmin, const Double xmax, const UInt4 N) {

    vector<Double> x, y, e;
    //Double a[] = { 30000.0, 100000.0, 100000.0, 30000.0 };
    //Double c[] = {     3.0,      5.0,     10.0,    12.0 };
    //Double w[] = {     1.0,      2.5,      2.5,     1.0 };
    Double a[] = { 100000.0 };
    Double c[] = {      7.5 };
    Double w[] = {      2.5 };
    const UInt4 N_PEAKS=sizeof(a)/sizeof(a[0]);
    std::cerr << "N_PEAKS=" << N_PEAKS << endl;

    Double delta=(xmax-xmin)/N;
    for (UInt4 i=0U; i<=N; ++i) {
        x.push_back( xmin + static_cast<Double>(i)*delta );
    }

    Double xm, p, q, s, t;
    for (UInt4 i=0; i<N; ++i) {
        xm=(x.at(i) + x.at(i+1))/2.0;
        p=dist(xm, N_PEAKS, a, c, w);
        s=static_cast<Double>(rand())/static_cast<Double>(RAND_MAX);
        t=static_cast<Double>(rand())/static_cast<Double>(RAND_MAX)-0.5;
        q=(t>0.0 ? 1.0 : (t=0.0 ? 0.0 : -1.0))*(p/10.0)*sqrt(-1.0*log(s));
        y.push_back(floor(p + q));
        e.push_back(p/10.0);
    }

    ElementContainer *src=new ElementContainer();
    src->AddToHeader("run number", 1);
    src->AddToHeader("level", 1);
    src->AddToHeader("Inst->", "manyo");
    src->Add("TOF",       x, "sec.");
    src->Add("Intensity", y, "count");
    src->Add("Error",     e, "count");
    src->SetKeys("TOF", "Intensity", "Error");
    
    return *src;
}


void output(FILE *f, ElementContainer &src, ElementContainer &result) { 

    string xKey = src.PutXKey();
    string yKey = src.PutYKey();
    string eKey = src.PutEKey();

    fprintf(f, "%5s [%10s %10s) %23s %23s %23s\n", "No.", "lower", "upper", "observed value", "observed error", "result");
    for (UInt4 i=0; i<src.PutSize(src.PutYKey()); ++i) {
        fprintf(f, "%5u [%10.5f %10.5f] %23.15e %23.15e %23.15e\n",
            i, src.Put(xKey, i), src.Put(xKey, i+1), src.Put(yKey, i), src.Put(eKey, i), result.Put(yKey, i)
        );
    }
}


int main(int argc, char *argv[]) {

    NeXusFileIO *input = new NeXusFileIO();
    ElementContainerArray src;
    try {
        src=input->ReadElementContainerArray("sourceArray.data");
    } catch (exception &e) {
        std::cerr << e.what() << endl;
    }
    std::cout << src.PutSize() << endl;
    for (UInt4 i=0; i<src.PutSize(); ++i) {
        src.PutPointer(i)->Dump();
    }
    UInt4 nSeries=src.PutSize();

    //---- peak search ----
//    PeakData *peakData = new PeakData[nSeries];
//    for (UInt4 i=0; i<src.PutSize(); ++i) {
//        PeakSearch peakSearch = *(new PeakSearch());
//        peakSearch.setSource(src.PutPointer(i));
//        //peakSearch.setMethod(new MovingAverage());
//        peakSearch.setMethod(new BSpline());
//        peakSearch.setDefaultParam();
//        if ( peakSearch.checkParam() ) {
//            peakSearch.execute();
//        }
//        //ElementContainer result=peakSearch.getResult();
//        //result.Dump();
//
//        peakData[i] = peakSearch.getPeaks();
//        //output(stdout, src, result);
//    }
//   
//    for (UInt4 i=0; i<src.PutSize(); ++i) {
//        peakData[i].Dump();
//    }
//    vector<Double> param = peakData[0].toVector();

    vector<Double> param = src.PutPointer(0)->PutHeader().PutDoubleVector("source parameters");
    vector< vector<FuncBase*> > funcMatrix = *(new vector< vector<FuncBase*> >());
    for (UInt4 i=0; i<src.PutSize(); ++i) {
        string expr=src.PutPointer(i)->PutHeader().PutString("source functions");
        std::cout << "exor="<< expr << endl;
        FuncParser *parser=new FuncParser(expr);
        funcMatrix.push_back( parser->parse());
    }

    vector<Double> lb=*(new vector<Double>());
    vector<Double> ub=*(new vector<Double>());
    for (UInt4 i=0; i<param.size(); ++i) {
        lb.push_back(param.at(i)*0.9);
        ub.push_back(param.at(i)*1.1);
    }

    MultiDataPeakFit op=*(new MultiDataPeakFit(&src, MULTIDATA_LEVMAR));
    op.setDefaultParam();
//
    op.setParam(LevmarConsts::CONSTRAIN,          LevmarConsts::BOX);
    op.setParam(LevmarConsts::USE_NUMERICAL_DIFF, true);
    op.setParam(LevmarConsts::DIFF_METHOD,        LevmarConsts::FORWARD);
    op.setParam(LevmarConsts::USE_DATA_WEIGHTS,   true);
//
    op.setParam(LevmarConsts::FUNCTIONS,          funcMatrix);
    op.setParam(LevmarConsts::PARAMETER_VALUES,   param);
    op.setParam(LevmarConsts::LOWER_BOUNDS,       lb);
    op.setParam(LevmarConsts::UPPER_BOUNDS,       ub);
    ParamSet ps = op.getParam();
    ps.dump();

    if ( op.checkParam() ) {
        op.execute();
    }

    ParamSet fittedParam = op.getFittedParam();
    fittedParam.dump();

//    ElementContainerArray fittedEc = op.getResult();
//    fittedEc.PutHeader().Dump();
//
//    ElementContainerMatrix compEcA = op.getResultComponents();
//    std::cout << "element container array: table size: " << compEcA.getTableSize() << endl;
//    std::cout << "element container array: depth     : " << compEcA.getDepth() << endl;
//    compEcA.Put(0).Dump();

    return EXIT_SUCCESS;
}
