#include "ArrayOperation.hh"
#include <cmath>
#include <time.h>
#include <sys/time.h>

class ProcessedTime{
private:
    timeval ini, fin;
    double iniDouble, finDouble;
public:
////////////////////////////////////////
    ProcessedTime(){
    //!< Constructor
        timerclear(&ini);
        timerclear(&fin);
        iniDouble = 0.;
        finDouble = 0.;
    }
////////////////////////////////////////
    void Start(){
    //!< Start counting
        gettimeofday(&ini, NULL);
        return;
    }
////////////////////////////////////////
    void Stop(){
    //!< Stop counting
        gettimeofday(&fin, NULL);
    }
////////////////////////////////////////
    void Print(){
    //!< Print to stdout
        iniDouble = ((double)ini.tv_sec + (double)ini.tv_usec * 0.001 * 0.001);
        finDouble = ((double)fin.tv_sec + (double)fin.tv_usec * 0.001 * 0.001);
        cout << "-------------------- ";
        cout << finDouble - iniDouble << " sec." << endl;
        return;
    }   
////////////////////////////////////////
    Double Put(){
    //!< Put as Double
        iniDouble = ((double)ini.tv_sec + (double)ini.tv_usec * 0.001 * 0.001);
        finDouble = ((double)fin.tv_sec + (double)fin.tv_usec * 0.001 * 0.001);
        return finDouble - iniDouble;
    }
////////////////////////////////////////
    string PutCurrentTime(){
        time_t t;
        time(&t);
        return (string)ctime(&t);
    }
};

double _sqrt(Double d){ return sqrt(d);};
void Print(ElementContainerArray eca, int n=5){
    for (int i=0; i<n; ++i){
        vector<Double> vd = eca(i)->PutY();
        cout << vd[0] << ", ";
    }
    cout << endl;
};
    
void Print(ElementContainerMatrix ecm, int m=3, int n=5){
    for (int i=0; i<m; ++i){
        for (int j=0; j<n; ++j){
            vector<Double> vd = ecm(i, j)->PutY();
            cout << vd[0] << ", ";
        }
        cout << endl;
    }
};

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

if (argc != 2){
    cout << "Usage: " << argv[0] << " operator" << endl;
    return 0;
}

    omp_set_num_threads( 12 );
    vector<Double> v(14400, 0.0);

ProcessedTime watch;

cout << "EC size: 1000, ECA size:100, ECM size 50" << endl;
cout << "--------  ECM --------+--------   VVD  --------+- VD " << endl;
cout << "    1,    2, ...,  100|   -1,    -2, ...,  -100|   -1" << endl;
cout << "  101,  102,       200| -101,  -102,       -200| -101" << endl;
cout << " .....................|........................|....." << endl;
cout << " 4901, 4902, ..., 5000|-4901, -4902, ..., -5000|-4901" << endl;
cout << "----------------------+------------------------+-----" << endl;

string op(argv[1]);
vector<Double> vx(1001);
for (int i=0; i<vx.size(); ++i){
    vx[i] = (Double)i;
}

vector< vector<Double> > vvd;
vector<Double> vd;
ElementContainerMatrix ecm;
for (int i=0; i<50; ++i){
    vector<Double> vd_tmp;
    ElementContainerArray eca;
    for (int j=0; j<100; ++j){
        Double d = i*100.+j+1;
        vd_tmp.push_back(-1*d);
        vector<Double> vy(1000, d);
        vector<Double> ve(1000, d);
        transform(ve.begin(), ve.end(),ve.begin(), _sqrt);
        ElementContainer ec;
        ec.Add("X", vx, "us");
        ec.Add("Y", vy, "n");
        ec.Add("E", ve, "n");
        if ( (i*100+j) != -1){
            ec.SetKeys("X", "Y", "E");
        }
        if ( (i*100+j) == 105){
            ec.AddToHeader("MASKED", 1);
        }
        eca.Add(ec);
    }
    vvd.push_back(vd_tmp);
    vd.push_back(-1*(i*100.+1.));
    ecm.Add(eca);
}

ElementContainerMatrix ecm1(ecm);
ElementContainerArray eca1 = ecm.Put(0);
eca1.MulMySelf(1.0); //dummy to access to cash
ecm1.MulMySelf(1.0); //dummy to access to cash
ElementContainer ec1 = eca1.Put(0);

ArrayOperation ao;

cout << "############ ecm(0) " << op << " 0.1" << endl;
eca1 = ecm.Put(0);
watch.Start();
if ( ao.Calculate(eca1, op, 0.1) ){
    cout << "Calculation succeeded." << endl;
    Print(eca1);
} else {
    cout << "Calculation failed." << endl;
}
watch.Stop();
watch.Print();

cout << "############ ecm(0) " << op << " vvd[0]" << endl;
eca1 = ecm.Put(0);
watch.Start();
if ( ao.Calculate(eca1, op, vvd[0]) ){
    cout << "Calculation succeeded." << endl;
    Print(eca1);
} else {
    cout << "Calculation failed." << endl;
}
watch.Stop();
watch.Print();

cout << "############ ecm(0) " << op << " ecm(0, 0)" << endl;
eca1 = ecm.Put(0);
watch.Start();
if ( ao.Calculate(eca1, op, ec1) ){
    cout << "Calculation succeeded." << endl;
    Print(eca1);
} else {
    cout << "Calculation failed." << endl;
}
watch.Stop();
watch.Print();

cout << "############ ecm(0) " << op << " ecm(1)" << endl;
eca1 = ecm.Put(0);
ElementContainerArray eca2 = ecm.Put(1);
watch.Start();
if ( ao.Calculate(eca1, op, eca2) ){
    cout << "Calculation succeeded." << endl;
    Print(eca1);
} else {
    cout << "Calculation failed." << endl;
}
watch.Stop();
watch.Print();

cout << "############ ecm " << op << " 10.0" << endl;
ecm1 = ElementContainerMatrix(ecm);
watch.Start();
if (ao.Calculate(ecm1, op, 10.0) ){
    cout << "Calculation succeeded." << endl;
    Print(ecm1);
} else {
    cout << "Calculation failed." << endl;
}
watch.Stop();
watch.Print();

cout << "############ ecm " << op << " vd" << " direction: column" << endl;
ecm1 = ElementContainerMatrix(ecm);
watch.Start();
if (ao.Calculate(ecm1, op, vd, false) ){
    cout << "Calculation succeeded." << endl;
    Print(ecm1);
} else {
    cout << "Calculation failed." << endl;
}
watch.Stop();
watch.Print();

cout << "############ ecm " << op << " vvd[0]" << " direction: row" << endl;
ecm1 = ElementContainerMatrix(ecm);
watch.Start();
if (ao.Calculate(ecm1, op, vvd[0]) ){
    cout << "Calculation succeeded." << endl;
    Print(ecm1);
} else {
    cout << "Calculation failed." << endl;
}
watch.Stop();
watch.Print();

cout << "############ ecm " << op << " ecm(i, 0)" << " direction: column" << endl;
ecm1 = ElementContainerMatrix(ecm);
ElementContainerArray eca3;
for (int i=0; i<ecm.PutSize(); ++i){
    eca3.Add(ecm(i)->Put(0));
}
watch.Start();
if (ao.Calculate(ecm1, op, eca3, false) ){
    cout << "Calculation succeeded." << endl;
    Print(ecm1);
} else {
    cout << "Calculation failed." << endl;
}
watch.Stop();
watch.Print();

cout << "############ ecm " << op << " ecm(1)" << " direction: row" << endl;
ecm1 = ElementContainerMatrix(ecm);
watch.Start();
if (ao.Calculate(ecm1, op, eca2) ){
    cout << "Calculation succeeded." << endl;
    Print(ecm1);
} else {
    cout << "Calculation failed." << endl;
}
watch.Stop();
watch.Print();

cout << "############ ecm " << op << " vvd" << endl;
ecm1 = ElementContainerMatrix(ecm);
watch.Start();
if (ao.Calculate(ecm1, op, vvd) ){
    cout << "Calculation succeeded." << endl;
    Print(ecm1);
} else {
    cout << "Calculation failed." << endl;
}
watch.Stop();
watch.Print();

cout << "############ ecm " << op << " ecm" << endl;
ecm1 = ElementContainerMatrix(ecm);
watch.Start();
if (ao.Calculate(ecm1, op, ecm) ){
    cout << "Calculation succeeded." << endl;
    Print(ecm1);
} else {
    cout << "Calculation failed." << endl;
}
watch.Stop();
watch.Print();

return 0;
}
