#include <iostream>
#include <istream>
#include <ostream>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <exception>
#include <sys/select.h>

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

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

#include "MultiDataLevmar.hh"
#include "Gaussian.hh"
#include "Lorentzian.hh"



void outputElementContainer(ElementContainer &ec) {

    for (UInt4 i=0; i<ec.PutSize(ec.PutYKey()); ++i) {
        std::cout << setw(5) << i;
        std::cout << " ";
        std::cout << "[";
        std::cout << setw(10) << setprecision(10) << setiosflags(std::ios::right) << ec.Put(ec.PutXKey(), i);
        std::cout << ", ";
        std::cout << setw(10) << setprecision(10) << setiosflags(std::ios::right) << ec.Put(ec.PutXKey(), i+1);
        std::cout << "]";
        std::cout << " ";
        std::cout << setw(23) << setprecision(16) << setiosflags(std::ios::right) << ec.Put(ec.PutYKey(), i);
        std::cout << " ";
        std::cout << setw(23) << setprecision(16) << setiosflags(std::ios::right) << ec.Put(ec.PutEKey(), i);
        std::cout << endl;
    }
}

Int4 main(Int4 argc, Char *argv[]) {

    ElementContainerArray eca;
    NeXusFileIO *inputIO = new NeXusFileIO();
    try {
        eca=inputIO->ReadElementContainerArray("sourceArray.data");
    } catch (exception e) {
        std::cerr << e.what() << endl;
    }

    MultiDataLevmar levmar = *(new MultiDataLevmar());

    /* domain */
    vector<Domain> domainArray = *(new vector<Domain>());
    for (UInt4 i=0; i<eca.PutSize(); ++i) {
        ElementContainer *ec=eca.PutPointer(i);
        string xkey=ec->PutXKey();
        Domain *domain=new Domain();
        domain->setSource(*ec);
        domain->setRange(ec->Put(xkey, 0), ec->Put(xkey, ec->PutSize(xkey)-1));
        domainArray.push_back(*domain);
    }

    /* functions */
    vector<FuncBase*> funcList1 = *(new vector<FuncBase*>());
    funcList1.push_back(new Gaussian());
    funcList1.push_back(new Gaussian());
    funcList1.push_back(new Gaussian());
    //funcList1.push_back(new Gaussian());
    //funcList1.push_back(new Lorentzian());
    //funcList1.push_back(new Lorentzian());
    //funcList1.push_back(new Lorentzian());
    //funcList1.push_back(new Lorentzian());
    funcList1.push_back(new Constant());

    vector<FuncBase*> funcList2 = *(new vector<FuncBase*>());
    //funcList2.push_back(new Gaussian());
    //funcList2.push_back(new Gaussian());
    //funcList2.push_back(new Gaussian());
    //funcList2.push_back(new Gaussian());
    funcList2.push_back(new Lorentzian());
    funcList2.push_back(new Lorentzian());
    funcList2.push_back(new Lorentzian());
    //funcList2.push_back(new Lorentzian());
    funcList2.push_back(new Constant());

    vector< vector<FuncBase*> > funcMatrix = *(new vector< vector<FuncBase*> >());
    funcMatrix.push_back(funcList1);
    funcMatrix.push_back(funcList2);

    /* default values for fittinng parameters */
    vector<Double> fittingParams; // = peakData.toVector();
    fittingParams.push_back( 60000.0);
    fittingParams.push_back(     3.5);
    fittingParams.push_back(     1.5);
    fittingParams.push_back( 60000.0);
    fittingParams.push_back(     6.0);
    fittingParams.push_back(     3.0);
    fittingParams.push_back( 50000.0);
    fittingParams.push_back(    12.0);
    fittingParams.push_back(     3.5);
    fittingParams.push_back(     1.0);

//    vector<Double> lowerBounds   = *(new vector<Double>());
//    vector<Double> upperBounds   = *(new vector<Double>());
//
//    for (UInt4 i=0; i < fittingParams.size(); ++i) {
//        lowerBounds.push_back(fittingParams.at(i)*0.9);
//        upperBounds.push_back(fittingParams.at(i)*1.1);
//    }
//
//
    ParamSet param=levmar.setDefaultParam(eca);

    param.replace(MultiDataLevmar::CONSTRAIN,          MultiDataLevmar::NO_CONSTRAIN);
    param.replace(MultiDataLevmar::USE_NUMERICAL_DIFF, true);
    param.replace(MultiDataLevmar::DIFF_METHOD,        MultiDataLevmar::FORWARD);
    param.replace(MultiDataLevmar::USE_DATA_WEIGHTS,   true);
    param.replace(MultiDataLevmar::MAX_ITERATIONS,     2000U);
    param.replace(MultiDataLevmar::OUTPUT_INTERVAL,    20U);

    param.add(MultiDataLevmar::FUNCTIONS, funcMatrix);
    //param.add(MultiDataLevmar::REFERENCE_VALUES, referenceData);
    param.add(MultiDataLevmar::PARAMETER_VALUES, fittingParams);
//
//    param.add(MultiDataLevmar::LOWER_BOUNDS, lowerBounds);
//    param.add(MultiDataLevmar::UPPER_BOUNDS, upperBounds);
//
    param.dump();
//
    std::cout<< "check param: " << levmar.checkParam(eca, domainArray, param) << endl;
    std::cerr<< "check param: " << levmar.checkParam(eca, domainArray, param) << endl;
//

    fd_set rfds; FD_ZERO(&rfds); FD_SET(0, &rfds);
    timeval waitTime;
    string stopCharSet=string("q");
    if (levmar.checkParam(eca, domainArray, param)) {
        levmar.toInnerForm(eca, domainArray, param);
        levmar.fit();

        while (levmar.isFitting()) {
            Int4 i = levmar.getLatestConvergenceStat()->getInt4(LevmarConsts::ITERATION_TIME);
            if (i > 0) {
                waitTime.tv_sec  = i/1000000;
                waitTime.tv_usec = i%1000000;
            } else {
                waitTime.tv_sec  = 1;
                waitTime.tv_usec = 0;
            }
            int retval=select(1, &rfds, NULL, NULL, &waitTime);
            if (retval == -1) {
                std::cerr << "error" <<endl;
            } else if (retval == 0) {
                ;
            } else {
                Char c;
                std::cin >> c;
                std::cerr << c << ": check char" <<  stopCharSet.find(c) << endl;
                if (c='q') {
                    levmar.stopFit();
                    while (levmar.isFitting()) {
                        sleep(1);
                    }
                    break;
                }
            }
        }
    
        levmar.eval();

        ElementContainerArray result=*(new ElementContainerArray(eca.PutHeader()));
        levmar.toElementContainerArray(eca, result);
        result.PutHeader().Dump();
        std::cout << "the number of element containers: " << result.PutSize() << endl;
        for (UInt4 i=0; i<result.PutSize(); ++i) {
            result.Put(i).PutHeader().Dump();
        }

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

        NeXusFileIO *io=new NeXusFileIO();
        io->Write(result, "fittedArray.data", "tanimori");
    }
}
