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

#include "Header.hh"
#include "ElementContainer.hh"
#include "PeakData.hh"
#include "Domain.hh"
#include "MovingAverage.hh"
#include "BSpline.hh"
#include "Gaussian.hh"
#include "Smoothing.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 };
    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 &smoothed) {

    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", "smoothed");
    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), smoothed.Put(yKey, i)
        );
    }
}

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

    //---- initialize an element container as source ----
    UInt4 nDiv;
    Double xmin, xmax;

    std::cin >> xmin >> xmax >> nDiv;
    ElementContainer src=init_src(xmin, xmax, nDiv);
    //src.Dump();

    //---- smoothing search ----
    //Smoothing smoothing;
    //smoothing.setSource(&src);
    //smoothing.setMethod(new MovingAverage());
    //smoothing.setMethod(new BSpline());
    //smoothing.setDefaultParam();
    Smoothing smoothing = *(new Smoothing(&src, new BSpline));
    
    if ( smoothing.checkParam() ) {
        smoothing.execute();
    }
    ElementContainer result=smoothing.getResult();
    //result.Dump();

    output(stdout, src, result);

    return EXIT_SUCCESS;
}
