// Lemur OLAP library (c) 2003 National Research Council of Canada by Daniel Lemire, and Owen Kaser
 /**
 *  This program is free software; you can
 *  redistribute it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation (version 2). This
 *  program is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 *  details. You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>
//#include <stdlib.h>
#include <cassert>
#include "lemurcore/datacube.h"
#include "lemurcore/chunkeddatacube.h"
#include "lemurcore/pyrps.h"


/************
* Code that follows is ugly. That's because main.cpp is not part of the
* library and used only for regression testing.
*/

int getRandom(int max) {
    return (int)(((double) rand() * max) /RAND_MAX);// that's ugly!
}
bool test(int value1, int value2, int line) {
    if(value1 != value2) {
        cout << "Error in line " << line <<endl;
        cout << "value1 = "<< value1 << ", value2 = "<<value2<<endl;
        return false;
    }
    return true;
}

bool regression(DataCube<int,int> & dc) {
    srand( (unsigned)time( NULL ) );
    bool success = true;
    vector<int> shape = dc.getShape();
    //dc.fillWithZeroes();// no longer needed
    // test randomly that it is really zero
    for(int times = 0; times < 10 ; ++times) {
        int i1 = getRandom(shape[0] - 1), i2 = getRandom(shape[1] - 1), i3 = getRandom(shape[2] -1);
        // it should be zero
        if (!test(dc.get(i1,i2,i3), 0, __LINE__)) success = false;
    }
    // test modifying one value
    for(int times = 0; times < 10 ; ++times) {
        int i1 = getRandom(shape[0] - 1), i2 = getRandom(shape[1] - 1), i3 = getRandom(shape[2] -1);
        if (!test(dc.get(i1,i2,i3), 0, __LINE__)) success = false;
        const int value = rand();
        dc.put(value,i1,i2,i3);
        if (!test(dc.get(i1,i2,i3), value, __LINE__)) success = false;
        // put 0 back in
        dc.put(0,i1,i2,i3);
        // check that it went back to zero
        if (!test(dc.get(i1,i2,i3), 0, __LINE__)) success = false;;
    }
    // test randomly again that it is really zero
    for(int times = 0; times < 10 ; ++times) {
        int i1 = getRandom(shape[0] - 1), i2 = getRandom(shape[1] - 1), i3 = getRandom(shape[2] -1);
        // it should be zero
        if (!test(dc.get(i1,i2,i3), 0, __LINE__)) success = false;
    }
    return success;
}

bool prefixRegression(DataCube<int,int> & dc) {
    srand( (unsigned)time( NULL ) );
    bool success = true;
    vector<int> shape = dc.getShape();
    const int value = rand();
    //dc.fillWithZeroes();// no longer needed
    dc.fillWith(value);
    // test randomly that it is really "value"
    for(int times = 0; times < 1000 ; ++times) {
        int i1 = getRandom(shape[0] - 1), i2 = getRandom(shape[1] - 1), i3 = getRandom(shape[2] -1);
        if (!test(dc.get(i1,i2,i3), value, __LINE__)) success = false;
    }
    // test computing prefix
    // first set it to 1
    int One(1);
    dc.fillWith(One);
    // some hand crafted tests
    if (!test(dc.prefixSum(0,0,0), (1)*(1)*(1) , __LINE__)) success = false;
    if (!test(dc.prefixSum(1,1,1), (2)*(2)*(2) , __LINE__)) success = false;
    if (!test(dc.prefixSum(shape[0] - 1,shape[1] - 1,shape[2] - 1),
              (shape[0])*(shape[1])*(shape[2]) , __LINE__)) success = false;
    for(int times = 0; times < 10 ; ++times) {
        int i1 = getRandom(shape[0] - 1), i2 = getRandom(shape[1] - 1), i3 = getRandom(shape[2] -1);
        if (!test(dc.prefixSum(i1,i2,i3), (i1 + One)*(i2 + One)*(i3 + One) , __LINE__)) success = false;
    }
    int Zero(0);
    dc.fillWith(Zero);
    // test randomly again that it is really zero
    for(int times = 0; times < 1000 ; ++times) {
        int i1 = getRandom(shape[0] - 1), i2 = getRandom(shape[1] - 1), i3 = getRandom(shape[2] -1);
        // it should be zero
        if (!test(dc.get(i1,i2,i3), Zero, __LINE__)) success = false;
    }
    return success;
}


/**/
void pyrpstest(DataCube<int,int> & dc) {
    if(true) {
        cout << "Next, testing PyRPS (toy tests)" << endl;
        vector<int> ToyShape(2,0); ToyShape[0]=16; ToyShape[1]=16;
        DataCube<int,int> ToyDC(ToyShape);
        ToyDC.open();
        //ToyDC.fillWithZeroes();// no longer needed
        ToyDC.fillWith(1);
        vector<int> ToyBasis(2,0); ToyBasis[0] = 2; ToyBasis[1] = 2;
        PyRPS<int,int> ToyPyRPS(&ToyDC,ToyBasis);
        ToyPyRPS.open();
        ToyPyRPS.transformBuffer();
        cout << "ToyDC.prefixSum(4,4) = " << ToyDC.prefixSum(4,4) << endl;
        cout << "ToyPyRPS.prefixSum(4,4) = " << ToyPyRPS.prefixSum(4,4) << endl;
        ToyPyRPS.close();
        ToyDC.close();
    }
    if(true) {
        cout << "Next, real testing of PyRPS" <<endl;
        vector<int>Shape(3,0);
        Shape[0] = 8; Shape[1]=16;Shape[2]=8;
        DataCube<int,int> sdc(Shape);
        cout <<" creating a new cube"<<endl;
        sdc.open();
        //sdc.fillWithZeroes();// no longer needed
        vector<int> Basis(3,0);
        Basis[0]=2;Basis[1]=4;Basis[2]=2;
        sdc.fillWith(1);
        cout << "dc.prefixSum(4,9,4) = " << sdc.prefixSum(4,9,4)<<endl;
        PyRPS<int,int> myrps(&sdc,Basis);
        cout << "Opening PyRPS "<< endl;
        myrps.open();
        // might need a fill with zeroes here?
        cout << "transforming buffer "<< endl;
        myrps.transformBuffer();
        cout << "Querying "<< endl;
        cout << "dc.prefixSum(4,9,4) = " << sdc.prefixSum(4,9,4)<<endl;
        cout << "myrps.prefixSum(4,9,4) = " << myrps.prefixSum(4,9,4)<<endl;
        myrps.close();
    }
    if( true ) { // regression testing on pyrps!
        cout << "Testing PyRPS (regression)"<<endl;
        uint dimension = 3;
        vector<int> Basis(dimension,0);
        Basis[0]=2;Basis[1]=4;Basis[2]=4;
        PyRPS<int,int> myrps(&dc,Basis);
        myrps.open();
        myrps.transformBuffer();
        bool PyRPSRegression = false, PyRPSPrefixRegression = false;
        if( PyRPSRegression = regression(myrps) )
            cout << "regression passed!"<<endl;
        else
            cout << "regression failed!" << endl;
        if( PyRPSPrefixRegression = prefixRegression(myrps) )
            cout << "prefix regression passed!"<<endl;
        else
            cout << "prefix regression failed!" << endl;
        myrps.close();
    }

}/**/

bool multidimensionalIncrementRegression() {
    const int dimension = 3;
    vector<int> index(dimension,0);
    vector<int> start(index);
    vector<int> bound(dimension,2);
    int sum = 1;
    uint dim = 0;
    while(MathUtil::increment( index, start, bound, dim)) {
        sum ++;
        //cout << index[0] << " " << index[1] <<  " " << index[2] << endl;
    }
    return test(sum,8,__LINE__);
}

int main(int/* argc*/, char */*argv[]*/) {
    bool CriticalRegression = false, DataCubeRegression = false,
                              DataCubePrefixRegression = false, ChunkedDataCubeRegression = false,
                                                         ChunkedDataCubePrefixRegression = false;
    if( CriticalRegression = multidimensionalIncrementRegression() )
        cout << "Critical regression succeeded!"<<endl;
    else
        cout << "Critical regression failure!"<<endl;
    const int dimension = 3;
    vector<int> shape(dimension,0);
    shape[0] = 32; shape[1] = 64; shape[2] = 64;
    cout << "Testing DataCube"<<endl;
    DataCube<int,int> dc(shape);
    dc.open();
    if( DataCubeRegression = regression(dc) )
        cout << "regression passed!"<<endl;
    else
        cout << "regression failed!" << endl;
    cout << " Testing DataCube prefix sums" << endl;
    if( DataCubePrefixRegression = prefixRegression(dc) )
        cout << "prefix regression passed!"<<endl;
    else
        cout << "prefix regression failed!" << endl;
    cout << "Testing ChunkedDataCube" << endl;
    vector<int> ChunkShape(dimension,0);
    ChunkShape[0] = 4; ChunkShape[1] = 2; ChunkShape[2] = 2;
    ChunkedDataCube<int,int> cdc(shape, ChunkShape);
    cdc.open();
    if( ChunkedDataCubeRegression = regression(cdc) )
        cout << "prefix regression passed!"<<endl;
    else
        cout << "prefix regression failed!" << endl;
    if( ChunkedDataCubePrefixRegression = prefixRegression(cdc) )
        cout << "regression passed!"<<endl;
    else
        cout << "regression failed!" << endl;
    cout << "======Report======"<<endl;
    cout <<  "\tCriticalRegression = "<<CriticalRegression<<endl;
    cout <<  "\tDataCubeRegression = "<<DataCubeRegression <<endl;
    cout <<  "\tDataCubePrefixRegression = "<<DataCubePrefixRegression <<endl;
    cout <<  "\tChunkedDataCubeRegression = "<<ChunkedDataCubeRegression <<endl;
    cout <<  "\tChunkedDataCubePrefixRegression = "<<ChunkedDataCubePrefixRegression <<endl;
    //pyrpstest();
    cout << "Done."<<endl;
    cout << "Cleaning up "<< endl;
    dc.close();
    cdc.close();
    //  cout << "Next, we test proposition 4 " << endl;
    //testProposition4();
    cout << "Done cleaning up " <<endl;
}
