/*
 * File:   test_Paillier_Image.cc
 * Author: Mohamed TarekIbnZiad
 *
 * Created on September 16, 2015, 7:48 AM
 */

#include <assert.h>
#include <vector>
#include <crypto/paillier.hh>
#include <Img.hh>
#include <ImgP.hh>
#include <crypto/gm.hh>
#include <NTL/ZZ.h>
#include <gmpxx.h>
#include <math/util_gmp_rand.h>

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

#include <ctime>

#include<iostream>

#define PI 3.14159265359

using namespace cv;
using namespace std;
using namespace NTL;

/*
 * convert an image from Mat type to double type used as input in encryption
 */
vector < vector<double> >
Mat2Double(Mat src)
{
    vector < vector<double> > A(src.rows, vector<double>(src.cols));
    Scalar s;
    for (int i = 0; i < src.rows; i++) {
        for (int j = 0; j < src.cols; j++){
            s = src.at<uchar>(i,j);
            A[i][j] = (int)s[0];
        }
    }
    return A;
}

/*
 * convert an image from double type to Mat type (opencv)
 */
Mat
Double2Mat(vector < vector<double> > &A)
{
    int rows = A.size();
    int cols = A.data()->size();
    Mat dst(rows,cols,CV_8UC1);
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++){
            dst.at<uchar>(i,j) = (int)A[i][j];  //Through away the float part or round it?
            //dst.at<uchar>(i,j) = round(A[i][j]);
        }
    }
    return dst;
}

/*
 * convert an image from encnum type used in encryption to Mat type (opencv)
 */
Mat
encnum2Mat(vector < vector<encnum> > &A)
{
    int rows = A.size();
    int cols = A.data()->size();
    Mat dst(rows,cols,CV_8UC1);
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++){
            dst.at<uchar>(i,j) = (int)A[i][j].mantissa.get_d(); //double to int
        }
    }
    return dst;
}

double
getRMSE(const Mat& I1, const Mat& I2)
{
    Mat s1;
    absdiff(I1, I2, s1);       // |I1 - I2|
    s1.convertTo(s1, CV_32F);  // cannot make a square on 8 bits
    s1 = s1.mul(s1);           // |I1 - I2|^2

    Scalar s = sum(s1);        // sum elements per channel
    double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels
    double mse  = sse / (double)(I1.channels() * I1.total());
    double rmse = sqrt(mse);

    return rmse;
 }

/*
 * Get the absolute values for a resultant image
 */
vector < vector <double> >
getAbsolute(vector < vector<double> > &A)
{
    int rows = A.size();
    int cols = A.data()->size();
    vector < vector<double> > B(rows, vector<double>(cols));
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++){
            if (A[i][j] < 0 )
                B[i][j] = A[i][j] * -1;
            else
                B[i][j] = A[i][j];
        }
    }
    return B;
}

/*
 * Add the absolute values of 2 resultant images (H and V)
 * IF accepted and implemented on Mob --> combine it with get absolute
 */
vector < vector <double> >
AddAbsolute(vector < vector<double> > &A, vector < vector<double> > &B)
{
    int rows = A.size();
    int cols = A.data()->size();
    vector < vector<double> > C(rows, vector<double>(cols));
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++){
                C[i][j] = A[i][j] + B[i][j];
        }
    }
    return C;
}

/*
 * Creates an average filter with a specified kernel size
 */
vector < vector <double> >
CreateBoxFilter(int kSize){

    vector < vector <double> > filter (kSize, vector<double>(kSize));
    for (int i = 0; i < kSize; i++)
        for(int j = 0;j< kSize; j++)
            filter[i][j] = 1.0/(kSize*kSize);
    return filter;
}

/*
 * Creates a Gaussian filter with a specified kernel size
 */
vector < vector <double> >
CreateGaussianFilter(int kSize, double sigma){

    vector < vector <double> > filter (kSize, vector<double>(kSize));
    if (sigma < 0)
        sigma = 0.3*((kSize-1)*0.5 - 1) + 0.8;

    double alpha = 1/(2*PI*sigma*sigma);
    //(0,0) is in the center
    for (int x = -kSize/2; x <= kSize/2; x++){
        for(int y = -kSize/2;y<= kSize/2; y++){
            //Needs To be checked! http://prntscr.com/8h6q9b
            filter[x+(kSize/2)][y+(kSize/2)] = alpha*exp(-(x*x + y*y)/(2*sigma*sigma));
        }
    }
    return filter;
}

/*
 * Post processing for erosion
 */
vector < vector <double> >
erodePost(vector < vector<double> > &A)
{
    int rows = A.size();
    int cols = A.data()->size();
    vector < vector<double> > B(rows, vector<double>(cols));
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++){
            if (A[i][j] == (9*255) ) //All the values under the mask are 255s
                B[i][j] = 1*255;
            else
                B[i][j] = 0*255;
        }
    }
    return B;
}

/*
 * Post processing for dilation
 */
vector < vector <double> >
dilatePost(vector < vector<double> > &A)
{
    int rows = A.size();
    int cols = A.data()->size();
    vector < vector<double> > B(rows, vector<double>(cols));
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++){
            if (A[i][j] >= 1*255 && A[i][j] <= 9*255 )
                B[i][j] = 1*255;
            else //All the values under the mask are 0s
                B[i][j] = 0*255;
        }
    }
    return B;
}

/*
 * Takes an image as input
 * returns a 2d vector with the 1st columns represents colors and
 * the 2nd represents their freq.
 * The vector length is the total intensity levels G
 */
vector < vector <double> >
countHist(vector < vector<double> > src)
{
    int rows = src.size();
    int cols = src.data()->size();
    vector <int> Frequencies (256);
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++){
            Frequencies[src[i][j]]++;
        }
    }

    vector < vector<double> > ColorFreq(256, vector<double>(2));
    int G = 0;
    for (int i=0; i<256; i++){
        if(Frequencies[i] != 0){
            ColorFreq[G][0] = i;
            ColorFreq[G][1] = Frequencies[i];
            G++;
        }
    }
    return ColorFreq;
}

/*
 * Post processing Histogram function that constructs the new image based on the new histogram counts
 */
Mat
updateHist(vector < vector<double> > &newFreqHist, Mat src)
{
    int rows = src.rows;
    int cols = src.cols;
    int G = newFreqHist.size();

    vector <int> newColors (256);
    for (int i = 0; i<256; i++)
        newColors[i] = i;

    for (int i = 0; i<G; i++){
        newColors[newFreqHist[i][0]] = round(newFreqHist[i][1]);
    }

    Mat dst(rows,cols,CV_8UC1);
    Scalar s;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++){
            s = src.at<uchar>(i,j);
            dst.at<uchar>(i,j) = newColors[(int)s[0]];
        }
    }
    return dst;
}

void
testImagePaillier()
{
    //Measuring time
    struct timespec t0,t1;
    uint64_t t;

    //Generate Paillier key
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    gmp_randstate_t randstate;
    gmp_randinit_default(randstate);
    gmp_randseed_ui(randstate,time(NULL));

    auto sk = Paillier_priv::keygen(randstate,256,2); //600 ,256 , 128
    Paillier_priv pp(sk,randstate);

    auto pk = pp.pubkey();
    mpz_class n = pk[0];
    Paillier p(pk,randstate);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Key Generation : "<<  ((double)t/1000000) <<"ms" << endl;
    cout << "key ready" << endl;

    //read image
    Mat src = imread( "cameraman.JPG", 0); //cameraman.JPG
    imshow( "Original Image", src );

    int rows = src.rows;
    int cols = src.cols;
    vector < vector<double> > A(rows, vector<double>(cols));
    A = Mat2Double(src);

    //Encrypt image
    vector < vector<encnum> > A_enc;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    A_enc = p.encryptMatrix(A);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "public encryption: "<<  ((double)t/1000000) <<"ms per image" << endl;
    cout << "Image encrypted " << endl;
    //imshow( "Encrypted Image", encnum2Mat(A_enc) );

    //The client only can decrypt a matrix
    vector < vector<double> > A_dec;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    A_dec = pp.decryptMatrix(A_enc);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "private decryption: "<<  ((double)t/1000000) <<"ms per image" << endl;
    cout << "Image decrypted " << endl;
    Mat dst;
    dst = Double2Mat(A_dec);
    imshow( "Decrypted Image", dst );
    cout << "Root Mean Square Error in decryption: "<<getRMSE(src,dst)<<endl;

    //Create image to work in plain domain
    ImgP img1P;

    //Create image to work in encrypted domain
    Img img1(A_enc);
    img1.setPbKey(p);

    //Debug code
    cout << "Test sub Paillier numbers...\n" << flush;
    double a = 255 , b = 5;
    encnum a_enc = p.encrypt_f(a);
    encnum b_enc = p.encrypt_f(b);
    b_enc = p.constMult_f(1.0 , b_enc);
    //b_enc = p.encrypt_f(pp.decrypt_f(b_enc)); //Re-encrypt
    encnum sum = p.sub_f(a_enc, b_enc);
    cout << a << " - " << b << " = " << pp.decrypt_f(sum)<< endl<< endl;
    cout << "---------------------------------------\n" << flush;

    //Negate image in plain domain
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    dst = img1P.NegativeImage(src);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Negative Image (Plain): "<<  ((double)t/1000000) <<"ms" << endl;
    imshow( "Negative Image", dst );

    //Negate image in encrypted domain
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<encnum> > Neg_enc = img1.NegativeImageH(A_enc);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Negative Image (Enc): "<<  ((double)t/1000000) <<"ms per image" << endl;
    //imshow( "Encrypted Negative Image", encnum2Mat(Neg_enc) );

    vector < vector<double> > Neg_dec = pp.decryptMatrix(Neg_enc);
    imshow( "Decrypted Negative Image", Double2Mat(Neg_dec) );
    cout << "Root Mean Square Error in Negative image: "<<getRMSE(dst,Double2Mat(Neg_dec))<<endl;

    //Test Brightness
    int value = 10;
    //Increase brightness in plain domain
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    dst = img1P.AdjustBrightness(src, value);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Brightness Image (Plain): "<<  ((double)t/1000000) <<"ms per image" << endl;
    imshow( "Brightness Image", dst );

    //Increase brightness in encrypted domain
    encnum value_enc = p.encrypt_f(value);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<encnum> > Bright_enc = img1.AdjustBrightnessH(A_enc, value_enc);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Brightness Image (Enc): "<<  ((double)t/1000000) <<"ms per image" << endl;
    //imshow( "Encrypted Brightness Image", encnum2Mat(Bright_enc) );

    vector < vector<double> > Bright_dec = pp.decryptMatrix(Bright_enc);
    imshow( "Decrypted Brightness Image", Double2Mat(Bright_dec) );
    cout << "Root Mean Square Error in Brightness image: "<<getRMSE(dst,Double2Mat(Bright_dec))<<endl;
    //post-processing to fix the image
    //Bright_dec = FixRange(Bright_dec);
    //imshow( "Decrypted Brightness Image", Double2Mat(Bright_dec) );

    //Test convolution
    vector < vector<double> > filter;
    //filter = {{1,0,-1},{2,0,-2},{1,0,-1}};//Vertical edges
    filter = {{1,2,1},{0,0,0},{-1,-2,-1}};  //Horizontal edges
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<encnum> > conv_enc = img1.convolutionH(A_enc, filter);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Convolution (Enc): "<<  ((double)t/1000000) <<"ms per image" << endl;
    //imshow( "Encrypted convoluted Image", encnum2Mat(conv_enc) );
    vector < vector<double> > conv_dec = pp.decryptMatrix(conv_enc);
    //Temp solution to the -ve value problem in edge detection
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<double> > conv_dec_fixed = getAbsolute(conv_dec);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Convolution (Post Processing): "<<  ((double)t/1000000) <<"ms per image" << endl;
    imshow( "Decrypted convoluted Image", Double2Mat(conv_dec_fixed) );

    //Test convolution in Plain domain
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    dst = img1P.convolution(src, filter);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Convolution (Plain): "<<  ((double)t/1000000) <<"ms per image" << endl;
    //Take care of rounding and borders to reduce error
    imshow( "convoluted Image", dst );
    cout << "Root Mean Square Error in convoluted image: "<<getRMSE(dst,Double2Mat(conv_dec))<<endl;
    cout << "Root Mean Square Error in convoluted image (Absolute): "<<getRMSE(dst,Double2Mat(conv_dec_fixed))<<endl;

    //Test Average filter
    //filter = {{1.0/9,1.0/9,1.0/9},{1.0/9,1.0/9,1.0/9},{1.0/9,1.0/9,1.0/9}};
    filter = CreateBoxFilter(3);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<encnum> > Avg_enc = img1.convolutionH(A_enc, filter);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Average (Enc): "<<  ((double)t/1000000) <<"ms per image" << endl;
    //imshow( "Encrypted averaged Image", encnum2Mat(Avg_enc) );
    vector < vector<double> > Avg_dec = pp.decryptMatrix(Avg_enc);
    //vector < vector<double> > Avg_dec_d = Mat
    imshow( "Decrypted averaged Image", Double2Mat(Avg_dec) );

    //Test average filter in plain domain
    //dst = img1P.AverageFilter(src,filter);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    dst = img1P.convolution(src, filter);
    vector < vector<double> >  aa = Mat2Double(dst);//Debug Line
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Average (Plain): "<<  ((double)t/1000000) <<"ms per image" << endl;
    imshow( "Averaged Image", dst );
    cout << "Root Mean Square Error in Averaged image: "<<getRMSE(dst,Double2Mat(Avg_dec))<<endl; //Error due to rounding

    //Test Gaussian Filtering
    //filter = CreateGaussianFilter(5,1);

    //Laplacian:Calculates the Laplacian of an image.
    //filter = {{0,1,0},{1,-4,1},{0,1,0}};

    //Test Histogram equalization
    //Pre-processing
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<double> > ColorFreq = countHist(Mat2Double(src));
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Histogram Equalization (Enc_Pre): "<<  ((double)t/1000000) <<"ms" << endl;

    vector < vector<encnum> > ColorFreq_enc = p.encryptMatrix(ColorFreq);

    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<encnum> > newColorFreq_enc = img1.equalHistH(ColorFreq_enc, src.rows, src.cols);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Histogram Equalization (Enc): "<<  ((double)t/1000000) <<"ms" << endl;

    vector < vector<double> > newColorFreq_dec = pp.decryptMatrix(newColorFreq_enc);

    //Post-processing
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    Mat equalizedSrc = updateHist(newColorFreq_dec, src);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Histogram Equalization (Enc_post): "<<  ((double)t/1000000) <<"ms" << endl;
    imshow( "Decrypted Equalized Image", equalizedSrc );
    //OpenCV --> TODO
    //imshow( "Equalized Image Open CV", srcB );
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    dst = img1P.equalHist(src);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Histogram Equalization (Plain): "<<  ((double)t/1000000) <<"ms" << endl;
    imshow( "Equalized Image ", dst );
    cout << "Root Mean Square Error in Equalized image: "<<getRMSE(dst,equalizedSrc)<<endl;
    //To eliminate error here --> use precision = 0.00000001 with 128-bit key

    //Test Edge filters
    //In plain domain
    char direction = 'H'; //'H' for horizontal and 'V' for vertical
    char type = 'S'; //'R' Roberts, 'P' Prewitt, 'S' Sobel, 'R' Robinson, 'K' Kirsch
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    dst = img1P.EdgeDetectionFilter(src, type, direction);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Edge filter in H (Plain): "<<  ((double)t/1000000) <<"ms" << endl;
    imshow( "Edge Image H", dst );
    //In encrypted domain
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<encnum> > Edge_enc = img1.EdgeDetectionFilterH(A_enc, type, direction);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Edge filter (Enc): "<<  ((double)t/1000000) <<"ms" << endl;
    imshow( "Encrypted Edge Image", encnum2Mat(Edge_enc) );
    vector < vector<double> > Edge_dec = pp.decryptMatrix(Edge_enc);
    imshow( "Decrypted Edge Image H", Double2Mat(Edge_dec) );
    cout << "Root Mean Square Error in Edge image H: "<<getRMSE(dst,Double2Mat(Edge_dec))<<endl;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<double> > Edge_dec_fixed = getAbsolute(Edge_dec);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Edge filter in H (Enc_post): "<<  ((double)t/1000000) <<"ms" << endl;
    imshow( "Decrypted Edge Image in H (Absolute)", Double2Mat(Edge_dec_fixed) );
    cout << "Root Mean Square Error in Edge image H (Absolute): "<<getRMSE(dst,Double2Mat(Edge_dec_fixed))<<endl;

    //In vertical direction
    direction = 'V'; //'H' for horizontal and 'V' for vertical
    type = 'S'; //'R' Roberts, 'P' Prewitt, 'S' Sobel, 'R' Robinson, 'K' Kirsch
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    dst = img1P.EdgeDetectionFilter(src, type, direction);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Edge filter in V (Plain): "<<  ((double)t/1000000) <<"ms" << endl;
    imshow( "Edge Image V", dst );
    //In encrypted domain
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    Edge_enc = img1.EdgeDetectionFilterH(A_enc, type, direction);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Edge filter (Enc): "<<  ((double)t/1000000) <<"ms" << endl;
    //imshow( "Encrypted Edge Image V", encnum2Mat(Edge_enc) );
    Edge_dec = pp.decryptMatrix(Edge_enc);
    imshow( "Decrypted Edge Image V", Double2Mat(Edge_dec) );
    cout << "Root Mean Square Error in Edge image V: "<<getRMSE(dst,Double2Mat(Edge_dec))<<endl;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<double> > Edge_dec_fixedV = getAbsolute(Edge_dec);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Edge filter in V (Enc_post): "<<  ((double)t/1000000) <<"ms" << endl;
    imshow( "Decrypted Edge Image in V (Absolute)", Double2Mat(Edge_dec_fixedV) );
    cout << "Root Mean Square Error in Edge image V (Absolute): "<<getRMSE(dst,Double2Mat(Edge_dec_fixedV))<<endl;

    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<double> > EdgeTotal = AddAbsolute(Edge_dec_fixed,Edge_dec_fixedV);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Edge filter (Add abs_post): "<<  ((double)t/1000000) <<"ms" << endl;
    imshow( "Decrypted Edge Image Total ", Double2Mat(EdgeTotal) );

    //Test morphological operations
    // Load an image from file
    Mat srcB = imread( "butterfly-11.jpg", 0); //0 --> gray, 1 --> RGB image
    srcB = srcB > 128; // Convert gray to binary
    //show the loaded image
    imshow( "Original Binary Image", srcB );

    Mat dstB;
    dstB = srcB.clone(); //Copy of src as initial value

    int element_shape = MORPH_RECT;
    Mat element = getStructuringElement(element_shape, Size(3, 3), Point(-1, -1) );
    vector < vector<double> > element_d = Mat2Double(element);

    int rowsB = srcB.rows;
    int colsB = srcB.cols;
    vector < vector<double> > B(rowsB, vector<double>(colsB));
    B = Mat2Double(srcB);
    //Encrypt image
    vector < vector<encnum> > B_enc = p.encryptMatrix(B);
    //imshow( "Encrypted Binary Image", encnum2Mat(B_enc) );

    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<encnum> > morph_enc = img1.morphH(B_enc, element_d);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Morph (Enc): "<<  ((double)t/1000000) <<"ms" << endl;
    //imshow( "Encrypted Morphological Image", encnum2Mat(morph_enc) );
    vector < vector<double> > morph_dec = pp.decryptMatrix(morph_enc);
    //Post processing for erosion/dilation
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<double> > morph_dec_erode = erodePost(morph_dec);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Morph (Enc_post_Erode): "<<  ((double)t/1000000) <<"ms" << endl;
    imshow( "Decrypted eroded Image", Double2Mat(morph_dec_erode) );

    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    erode(srcB, dstB, element);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Morph (Plain_Erode): "<<  ((double)t/1000000) <<"ms" << endl;
    imshow("Erode OpenCV",dstB);
    cout << "Root Mean Square Error in eroded image: "<<getRMSE(dstB,Double2Mat(morph_dec_erode))<<endl;

    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<double> > morph_dec_dilate = dilatePost(morph_dec);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Morph (Enc_post_Dilate): "<<  ((double)t/1000000) <<"ms" << endl;
    imshow( "Decrypted dilated Image", Double2Mat(morph_dec_dilate) );
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    dilate(srcB, dstB, element);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Morph (Plain_Dilate): "<<  ((double)t/1000000) <<"ms" << endl;
    imshow("Dilate OpenCV",dstB);
    cout << "Root Mean Square Error in dilated image: "<<getRMSE(dstB,Double2Mat(morph_dec_dilate))<<endl;

    //wait for a key press infinitely
    waitKey(0);
}

void
testImagePaillier_N(int n_iteration)
{
    //Measuring time
    struct timespec t0,t1;
    uint64_t t;

    //Generate Paillier key
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    gmp_randstate_t randstate;
    gmp_randinit_default(randstate);
    gmp_randseed_ui(randstate,time(NULL));

    auto sk = Paillier_priv::keygen(randstate,128,2); //600 ,256 , 128
    Paillier_priv pp(sk,randstate);

    auto pk = pp.pubkey();
    mpz_class n = pk[0];
    Paillier p(pk,randstate);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Key Generation : "<<  ((double)t/1000000) <<"ms" << endl;
    cout << "key ready" << endl;

    //read image
    Mat src = imread( "cameraman.JPG", 0);
    imshow( "Original Image", src );

    int rows = src.rows;
    int cols = src.cols;
    vector < vector<double> > A(rows, vector<double>(cols));
    A = Mat2Double(src);

    //Encrypt image
    vector < vector<encnum> > A_enc;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        A_enc = p.encryptMatrix(A);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "public encryption: "<<  ((double)t/1000000)/n_iteration <<"ms per image" << endl;
    cout << "Image encrypted " << endl;
    //imshow( "Encrypted Image", encnum2Mat(A_enc) );

    //The client only can decrypt a matrix
    vector < vector<double> > A_dec;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        A_dec = pp.decryptMatrix(A_enc);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "private decryption: "<<  ((double)t/1000000)/n_iteration <<"ms per image" << endl;
    cout << "Image decrypted " << endl;
    Mat dst;
    dst = Double2Mat(A_dec);
    imshow( "Decrypted Image", dst );
    cout << "Root Mean Square Error in decryption: "<<getRMSE(src,dst)<<endl;

    //Create image to work in plain domain
    ImgP img1P;

    //Create image to work in encrypted domain
    Img img1(A_enc);
    img1.setPbKey(p);

    //Negate image in plain domain
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        dst = img1P.NegativeImage(src);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Negative Image (Plain): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    imshow( "Negative Image", dst );

    //Negate image in encrypted domain
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    vector < vector<encnum> > Neg_enc;
    for (size_t i = 0; i < n_iteration; i++) {
        Neg_enc = img1.NegativeImageH(A_enc);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Negative Image (Enc): "<<  ((double)t/1000000)/n_iteration <<"ms per image" << endl;
    //imshow( "Encrypted Negative Image", encnum2Mat(Neg_enc) );

    vector < vector<double> > Neg_dec = pp.decryptMatrix(Neg_enc);
    imshow( "Decrypted Negative Image", Double2Mat(Neg_dec) );
    cout << "Root Mean Square Error in Negative image: "<<getRMSE(dst,Double2Mat(Neg_dec))<<endl;

    //Test Brightness
    int value = 10;
    //Increase brightness in plain domain
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        dst = img1P.AdjustBrightness(src, value);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Brightness Image (Plain): "<<  ((double)t/1000000)/n_iteration <<"ms per image" << endl;
    imshow( "Brightness Image", dst );

    //Increase brightness in encrypted domain
    encnum value_enc = p.encrypt_f(value);
    vector < vector<encnum> > Bright_enc;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        Bright_enc = img1.AdjustBrightnessH(A_enc, value_enc);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Brightness Image (Enc): "<<  ((double)t/1000000)/n_iteration <<"ms per image" << endl;
    //imshow( "Encrypted Brightness Image", encnum2Mat(Bright_enc) );

    vector < vector<double> > Bright_dec = pp.decryptMatrix(Bright_enc);
    imshow( "Decrypted Brightness Image", Double2Mat(Bright_dec) );
    cout << "Root Mean Square Error in Brightness image: "<<getRMSE(dst,Double2Mat(Bright_dec))<<endl;
    //post-processing to fix the image
    //Bright_dec = FixRange(Bright_dec);
    //imshow( "Decrypted Brightness Image", Double2Mat(Bright_dec) );

    //Test convolution
    vector < vector<double> > filter;
    //filter = {{1,0,-1},{2,0,-2},{1,0,-1}};//Vertical edges
    filter = {{1,2,1},{0,0,0},{-1,-2,-1}};  //Horizontal edges
    vector < vector<encnum> > conv_enc;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        conv_enc = img1.convolutionH(A_enc, filter);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Convolution (Enc): "<<  ((double)t/1000000)/n_iteration <<"ms per image" << endl;
    //imshow( "Encrypted convoluted Image", encnum2Mat(conv_enc) );
    vector < vector<double> > conv_dec = pp.decryptMatrix(conv_enc);
    //Temp solution to the -ve value problem in edge detection
    vector < vector<double> > conv_dec_fixed;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        conv_dec_fixed = getAbsolute(conv_dec);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Convolution (Post Processing): "<<  ((double)t/1000000)/n_iteration <<"ms per image" << endl;
    imshow( "Decrypted convoluted Image", Double2Mat(conv_dec_fixed) );

    //Test convolution in Plain domain
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        dst = img1P.convolution(src, filter);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Convolution (Plain): "<<  ((double)t/1000000)/n_iteration <<"ms per image" << endl;
    //Take care of rounding and borders to reduce error
    imshow( "convoluted Image", dst );
    cout << "Root Mean Square Error in convoluted image: "<<getRMSE(dst,Double2Mat(conv_dec))<<endl;
    cout << "Root Mean Square Error in convoluted image (Absolute): "<<getRMSE(dst,Double2Mat(conv_dec_fixed))<<endl;

    //Test Average filter
    //filter = {{1.0/9,1.0/9,1.0/9},{1.0/9,1.0/9,1.0/9},{1.0/9,1.0/9,1.0/9}};
    filter = CreateBoxFilter(3);
    vector < vector<encnum> > Avg_enc;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        Avg_enc = img1.convolutionH(A_enc, filter);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Average (Enc): "<<  ((double)t/1000000)/n_iteration <<"ms per image" << endl;
    //imshow( "Encrypted averaged Image", encnum2Mat(Avg_enc) );
    vector < vector<double> > Avg_dec = pp.decryptMatrix(Avg_enc);
    //vector < vector<double> > Avg_dec_d = Mat
    imshow( "Decrypted averaged Image", Double2Mat(Avg_dec) );

    //Test average filter in plain domain
    //dst = img1P.AverageFilter(src,filter);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        dst = img1P.convolution(src, filter);
    }
    //vector < vector<double> >  aa = Mat2Double(dst);//Debug Line
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Average (Plain): "<<  ((double)t/1000000)/n_iteration <<"ms per image" << endl;
    imshow( "Averaged Image", dst );
    cout << "Root Mean Square Error in Averaged image: "<<getRMSE(dst,Double2Mat(Avg_dec))<<endl; //Error due to rounding

    //Test Gaussian Filtering
    //filter = CreateGaussianFilter(5,1);

    //Laplacian:Calculates the Laplacian of an image.
    //filter = {{0,1,0},{1,-4,1},{0,1,0}};

    //Test Histogram equalization
    //Pre-processing
    vector < vector<double> > ColorFreq;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        ColorFreq = countHist(Mat2Double(src));
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Histogram Equalization (Enc_Pre): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;

    vector < vector<encnum> > ColorFreq_enc = p.encryptMatrix(ColorFreq);

    vector < vector<encnum> > newColorFreq_enc;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        newColorFreq_enc = img1.equalHistH(ColorFreq_enc, src.rows, src.cols);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Histogram Equalization (Enc): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;

    vector < vector<double> > newColorFreq_dec = pp.decryptMatrix(newColorFreq_enc);

    //Post-processing
    Mat equalizedSrc;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        equalizedSrc = updateHist(newColorFreq_dec, src);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Histogram Equalization (Enc_post): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    imshow( "Decrypted Equalized Image", equalizedSrc );
    //OpenCV --> TODO
    //imshow( "Equalized Image Open CV", srcB );
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        dst = img1P.equalHist(src);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Histogram Equalization (Plain): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    imshow( "Equalized Image ", dst );
    cout << "Root Mean Square Error in Equalized image: "<<getRMSE(dst,equalizedSrc)<<endl;
    //To eliminate error here --> use precision = 0.00000001 with 128-bit key

    //Test Edge filters
    //In plain domain
    char direction = 'H'; //'H' for horizontal and 'V' for vertical
    char type = 'S'; //'R' Roberts, 'P' Prewitt, 'S' Sobel, 'R' Robinson, 'K' Kirsch
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        dst = img1P.EdgeDetectionFilter(src, type, direction);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Edge filter in H (Plain): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    imshow( "Edge Image H", dst );
    //In encrypted domain
    vector < vector<encnum> > Edge_enc;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        Edge_enc = img1.EdgeDetectionFilterH(A_enc, type, direction);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Edge filter H(Enc): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    imshow( "Encrypted Edge Image H", encnum2Mat(Edge_enc) );
    vector < vector<double> > Edge_dec = pp.decryptMatrix(Edge_enc);
    imshow( "Decrypted Edge Image H", Double2Mat(Edge_dec) );
    cout << "Root Mean Square Error in Edge image H: "<<getRMSE(dst,Double2Mat(Edge_dec))<<endl;
    vector < vector<double> > Edge_dec_fixed;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        Edge_dec_fixed = getAbsolute(Edge_dec);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Edge filter in H(Enc_post): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    imshow( "Decrypted Edge Image H(Absolute)", Double2Mat(Edge_dec_fixed) );
    cout << "Root Mean Square Error in Edge image H(Absolute): "<<getRMSE(dst,Double2Mat(Edge_dec_fixed))<<endl;

    //In vertical direction
    direction = 'V'; //'H' for horizontal and 'V' for vertical
    type = 'S'; //'R' Roberts, 'P' Prewitt, 'S' Sobel, 'R' Robinson, 'K' Kirsch
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        dst = img1P.EdgeDetectionFilter(src, type, direction);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Edge filter in V (Plain): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    imshow( "Edge Image V", dst );
    //In encrypted domain
    //vector < vector<encnum> > Edge_enc;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        Edge_enc = img1.EdgeDetectionFilterH(A_enc, type, direction);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Edge filter V(Enc): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    imshow( "Encrypted Edge Image V", encnum2Mat(Edge_enc) );
    Edge_dec = pp.decryptMatrix(Edge_enc);
    imshow( "Decrypted Edge Image V", Double2Mat(Edge_dec) );
    cout << "Root Mean Square Error in Edge image V: "<<getRMSE(dst,Double2Mat(Edge_dec))<<endl;
    vector < vector<double> > Edge_dec_fixedV;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        Edge_dec_fixedV = getAbsolute(Edge_dec);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Edge filter in V(Enc_post): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    imshow( "Decrypted Edge Image V(Absolute)", Double2Mat(Edge_dec_fixedV) );
    cout << "Root Mean Square Error in Edge image V(Absolute): "<<getRMSE(dst,Double2Mat(Edge_dec_fixedV))<<endl;

	vector < vector<double> > EdgeTotal;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
	  for (size_t i = 0; i < n_iteration; i++) {
        EdgeTotal = AddAbsolute(Edge_dec_fixed,Edge_dec_fixedV);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Edge filter (Add abs_post): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    imshow( "Decrypted Edge Image Total ", Double2Mat(EdgeTotal) );

    //Test morphological operations
    // Load an image from file
    Mat srcB = imread( "butterfly-11.jpg", 0); //0 --> gray, 1 --> RGB image
    srcB = srcB > 128; // Convert gray to binary
    //show the loaded image
    imshow( "Original Binary Image", srcB );

    Mat dstB;
    dstB = srcB.clone(); //Copy of src as initial value

    int element_shape = MORPH_RECT;
    Mat element = getStructuringElement(element_shape, Size(3, 3), Point(-1, -1) );
    vector < vector<double> > element_d = Mat2Double(element);

    int rowsB = srcB.rows;
    int colsB = srcB.cols;
    vector < vector<double> > B(rowsB, vector<double>(colsB));
    B = Mat2Double(srcB);
    //Encrypt image
    vector < vector<encnum> > B_enc = p.encryptMatrix(B);
    //imshow( "Encrypted Binary Image", encnum2Mat(B_enc) );

    vector < vector<encnum> > morph_enc;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        morph_enc = img1.morphH(B_enc, element_d);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Morph (Enc): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    //imshow( "Encrypted Morphological Image", encnum2Mat(morph_enc) );
    vector < vector<double> > morph_dec = pp.decryptMatrix(morph_enc);
    //Post processing for erosion/dilation
    vector < vector<double> > morph_dec_erode;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        morph_dec_erode = erodePost(morph_dec);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Morph (Enc_post_Erode): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    imshow( "Decrypted eroded Image", Double2Mat(morph_dec_erode) );

    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        erode(srcB, dstB, element);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Morph (Plain_Erode): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    imshow("Erode OpenCV",dstB);
    cout << "Root Mean Square Error in eroded image: "<<getRMSE(dstB,Double2Mat(morph_dec_erode))<<endl;

    vector < vector<double> > morph_dec_dilate;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        morph_dec_dilate = dilatePost(morph_dec);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Morph (Enc_post_Dilate): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    imshow( "Decrypted dilated Image", Double2Mat(morph_dec_dilate) );
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t0);
    for (size_t i = 0; i < n_iteration; i++) {
        dilate(srcB, dstB, element);
    }
    clock_gettime(CLOCK_THREAD_CPUTIME_ID,&t1);
    t = (((uint64_t)t1.tv_sec) - ((uint64_t)t0.tv_sec) )* 1000000000 + (t1.tv_nsec - t0.tv_nsec);
    cerr << "Morph (Plain_Dilate): "<<  ((double)t/1000000)/n_iteration <<"ms" << endl;
    imshow("Dilate OpenCV",dstB);
    cout << "Root Mean Square Error in dilated image: "<<getRMSE(dstB,Double2Mat(morph_dec_dilate))<<endl;

    //wait for a key press infinitely
    waitKey(0);
}

/*
 * Testing New Floating point Paillier with Images
 */
int main(int argc, char** argv) {

    testImagePaillier(); /*For generating images*/
    //testImagePaillier_N(10); /*N = 10 to measure time*/
    return 0;
}


// The contents of this file are in the public domain. See LICENSE_FOR_EXAMPLE_PROGRAMS.txt
/*

    This is an example illustrating the use of the linear_manifold_regularizer
    and empirical_kernel_map from the dlib C++ Library.

    This example program assumes you are familiar with some general elements of
    the library.  In particular, you should have at least read the svm_ex.cpp
    and matrix_ex.cpp examples.  You should also have read the empirical_kernel_map_ex.cpp
    example program as the present example builds upon it.

    This program shows an example of what is called semi-supervised learning.
    That is, a small amount of labeled data is augmented with a large amount
    of unlabeled data.  A learning algorithm is then run on all the data
    and the hope is that by including the unlabeled data we will end up with
    a better result.

    In this particular example we will generate 200,000 sample points of
    unlabeled data along with 2 samples of labeled data.  The sample points
    will be drawn randomly from two concentric circles.  One labeled data
    point will be drawn from each circle.  The goal is to learn to
    correctly separate the two circles using only the 2 labeled points
    and the unlabeled data.

    To do this we will first run an approximate form of k nearest neighbors
    to determine which of the unlabeled samples are closest together.  We will
    then make the manifold assumption, that is, we will assume that points close
    to each other should share the same classification label.

    Once we have determined which points are near neighbors we will use the
    empirical_kernel_map and linear_manifold_regularizer to transform all the
    data points into a new vector space where any linear rule will have similar
    output for points which we have decided are near neighbors.

    Finally, we will classify all the unlabeled data according to which of
    the two labeled points are nearest.  Normally this would not work but by
    using the manifold assumption we will be able to successfully classify
    all the unlabeled data.

    For further information on this subject you should begin with the following
    paper as it discusses a very similar application of manifold regularization.

        Beyond the Point Cloud: from Transductive to Semi-supervised Learning
        by Vikas Sindhwani, Partha Niyogi, and Mikhail Belkin

                    ******** SAMPLE PROGRAM OUTPUT ********

    Testing manifold regularization with an intrinsic_regularization_strength of 0.
    number of edges generated: 49998
    Running simple test...
    error: 0.37022
    error: 0.44036
    error: 0.376715
    error: 0.307545
    error: 0.463455
    error: 0.426065
    error: 0.416155
    error: 0.288295
    error: 0.400115
    error: 0.46347

    Testing manifold regularization with an intrinsic_regularization_strength of 10000.
    number of edges generated: 49998
    Running simple test...
    error: 0
    error: 0
    error: 0
    error: 0
    error: 0
    error: 0
    error: 0
    error: 0
    error: 0
    error: 0

*/

#include <dlib/manifold_regularization.h>
#include <dlib/svm.h>
#include <dlib/rand.h>
#include <dlib/statistics.h>
#include <iostream>
#include <vector>
#include <ctime>

using namespace std;
using namespace dlib;

// ----------------------------------------------------------------------------------------

// First let's make a typedef for the kind of samples we will be using.
typedef matrix<double, 0, 1> sample_type;

// We will be using the radial_basis_kernel in this example program.
typedef radial_basis_kernel<sample_type> kernel_type;

// ----------------------------------------------------------------------------------------

void generate_circle (
    std::vector<sample_type>& samples,
    double radius,
    const long num
);
/*!
    requires
        - num > 0
        - radius > 0
    ensures
        - generates num points centered at (0,0) with the given radius.  Adds these
          points into the given samples vector.
!*/

// ----------------------------------------------------------------------------------------

void test_manifold_regularization (
    const double intrinsic_regularization_strength
);
/*!
    ensures
        - Runs an example test using the linear_manifold_regularizer with the given
          intrinsic_regularization_strength.
!*/

// ----------------------------------------------------------------------------------------

int main()
{
    // Run the test without any manifold regularization.
    test_manifold_regularization(0);

    // Run the test with manifold regularization.  You can think of this number as
    // a measure of how much we trust the manifold assumption.  So if you are really
    // confident that you can select neighboring points which should have the same
    // classification then make this number big.
    test_manifold_regularization(10000.0);
}

// ----------------------------------------------------------------------------------------

void test_manifold_regularization (
    const double intrinsic_regularization_strength
)
{
    cout << "Testing manifold regularization with an intrinsic_regularization_strength of "
         << intrinsic_regularization_strength << ".\n";

    std::vector<sample_type> samples;

    // Declare an instance of the kernel we will be using.
    const kernel_type kern(0.1);

    const unsigned long num_points = 100000;

    // create a large dataset with two concentric circles.  There will be 100000 points on each circle
    // for a total of 200000 samples.
    generate_circle(samples, 2, num_points);  // circle of radius 2
    generate_circle(samples, 4, num_points);  // circle of radius 4

    // Create a set of sample_pairs that tells us which samples are "close" and should thus
    // be classified similarly.  These edges will be used to define the manifold regularizer.
    // To find these edges we use a simple function that samples point pairs randomly and
    // returns the top 5% with the shortest edges.
    std::vector<sample_pair> edges;
    find_percent_shortest_edges_randomly(samples, squared_euclidean_distance(), 0.05, 1000000, time(0), edges);

    cout << "number of edges generated: " << edges.size() << endl;

    empirical_kernel_map<kernel_type> ekm;

    // Since the circles are not linearly separable we will use an empirical kernel map to
    // map them into a space where they are separable.  We create an empirical_kernel_map
    // using a random subset of our data samples as basis samples.  Note, however, that even
    // though the circles are linearly separable in this new space given by the empirical_kernel_map
    // we still won't be able to correctly classify all the points given just the 2 labeled examples.
    // We will need to make use of the nearest neighbor information stored in edges.  To do that
    // we will use the linear_manifold_regularizer.
    ekm.load(kern, randomly_subsample(samples, 50));

    // Project all the samples into the span of our 50 basis samples
    for (unsigned long i = 0; i < samples.size(); ++i)
        samples[i] = ekm.project(samples[i]);

    // Now create the manifold regularizer.  The result is a transformation matrix that
    // embodies the manifold assumption discussed above.
    linear_manifold_regularizer<sample_type> lmr;
    // use_gaussian_weights is a function object that tells lmr how to weight each edge.  In this
    // case we let the weight decay as edges get longer.  So shorter edges are more important than
    // longer edges.
    lmr.build(samples, edges, use_gaussian_weights(0.1));
    const matrix<double> T = lmr.get_transformation_matrix(intrinsic_regularization_strength);

    // Apply the transformation generated by the linear_manifold_regularizer to
    // all our samples.
    for (unsigned long i = 0; i < samples.size(); ++i)
        samples[i] = T*samples[i];

    // For convenience, generate a projection_function and merge the transformation
    // matrix T into it.  That is, we will have: proj(x) == T*ekm.project(x).
    projection_function<kernel_type> proj = ekm.get_projection_function();
    proj.weights = T*proj.weights;

    cout << "Running simple test..." << endl;

    // Pick 2 different labeled points.  One on the inner circle and another on the outer.
    // For each of these test points we will see if using the single plane that separates
    // them is a good way to separate the concentric circles.  We also do this a bunch
    // of times with different randomly chosen points so we can see how robust the result is.
    for (int itr = 0; itr < 10; ++itr)
    {
        std::vector<sample_type> test_points;
        // generate a random point from the radius 2 circle
        generate_circle(test_points, 2, 1);
        // generate a random point from the radius 4 circle
        generate_circle(test_points, 4, 1);

        // project the two test points into kernel space.  Recall that this projection_function
        // has the manifold regularizer incorporated into it.
        const sample_type class1_point = proj(test_points[0]);
        const sample_type class2_point = proj(test_points[1]);

        double num_wrong = 0;

        // Now attempt to classify all the data samples according to which point
        // they are closest to.  The output of this program shows that without manifold
        // regularization this test will fail but with it it will perfectly classify
        // all the points.
        for (unsigned long i = 0; i < samples.size(); ++i)
        {
            double distance_to_class1 = length(samples[i] - class1_point);
            double distance_to_class2 = length(samples[i] - class2_point);

            bool predicted_as_class_1 = (distance_to_class1 < distance_to_class2);

            bool really_is_class_1 = (i < num_points);

            // now count how many times we make a mistake
            if (predicted_as_class_1 != really_is_class_1)
                ++num_wrong;
        }

        cout << "error: "<< num_wrong/samples.size() << endl;
    }

    cout << endl;
}

// ----------------------------------------------------------------------------------------

dlib::rand rnd;

void generate_circle (
    std::vector<sample_type>& samples,
    double radius,
    const long num
)
{
    sample_type m(2,1);

    for (long i = 0; i < num; ++i)
    {
        double sign = 1;
        if (rnd.get_random_double() < 0.5)
            sign = -1;
        m(0) = 2*radius*rnd.get_random_double()-radius;
        m(1) = sign*sqrt(radius*radius - m(0)*m(0));

        samples.push_back(m);
    }
}

// ----------------------------------------------------------------------------------------


//==================================================================================================
/*!
  @file

    @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_IFREXP_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_IFREXP_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

  /*!
    @ingroup group-ieee
    This function object computes a mantissa and an exponent pair for the input

    @par Header <boost/simd/function/ifrexp.hpp>

    @par Semantic:

    For every parameter of floating type @c T

    @code
    std::tie(m, e)= ifrexp(x);
    @endcode

    is similar to:

    @code
    as_integer_t<T> e = exponent(x)+1;
    T m = mantissa(x)/2;
    @endcode

    @par Note:

    if you need floating type exponent (unlike the standard)  use @ref frexp

    This function splits a floating point value \f$x\f$ in a signed mantissa \f$m\f$ and
    an exponent \f$e\f$ so that:  \f$x = m\times 2^e\f$,
    with absolute value of \f$m \in [0.5, 1[\f$ (except for \f$x = 0\f$)

    Without the pedantic_ decorator  @ref Nan or @ref Inf are not handled properly.

    @warningbox{Take care that these results differ from the returns of the functions @ref mantissa
    and @ref exponent}

    @par Decorators

     - pedantic_ slower, but special values as @ref Nan or @ref Inf are handled properly.

     - std_ transmits the call to @c std::frexp.

    @see exponent, mantissa, frexp

    @par Example:

      @snippet ifrexp.cpp ifrexp

    @par Possible output:

      @snippet ifrexp.txt ifrexp

  **/
  std::pair<IEEEValue, as_integer_t<IEEEValue>> ifrexp(IEEEValue const& x);
} }
#endif

#include <boost/simd/function/scalar/ifrexp.hpp>
#include <boost/simd/function/simd/ifrexp.hpp>

#endif


/**
 * Exercise 8 : Read in a gph-file, interpretes it as a Steiner-problem and solves it.
 *
 * @author FirstSanny
 */

#include <iostream>
#include <iomanip>
#include <fstream>
#include <utility>
#include <boost/program_options.hpp>
#include <boost/timer/timer.hpp>
#include "Steiner.h"
#include "GraphChecker.h"

// Constants
namespace {
	const char* FILEEND = ".gph";
	const int MAX_FOR_DEFAULT_TERMINALS = 100;
	const int DEFAULT_THREAD_NUMBER = 8;
}

// declaring print
using std::cout;
using std::endl;
using std::flush;
using std::cerr;
using std::string;

// declaring types
namespace po = boost::program_options;
using BoostTimer = boost::timer::cpu_timer;
using BoostTimes = boost::timer::cpu_times;
using Primes = std::vector<int >;

/**
 * Computes all primes less than vertexCount
 * by beginning with the prime 2 and trying to divide all numbers smaller than vertexCount
 * and try do divide by all found primes.
 */
Primes getPrimes(unsigned int vertexCount) {
	Primes primes = Primes();
	primes.push_back(2);
	for (unsigned int i = 3; i < vertexCount; i++) {
		char isPrime = true;
		for (int prime : primes) {
			if (i % prime == 0) {
				isPrime = false;
			}
		}
		if (isPrime) {
			primes.push_back(i);
		}
	}
	return primes;
}

/** Parsing the arguments given via command line */
po::variables_map parseCommandLine(po::options_description desc, int argn,
		char* argv[]) {
	desc.add_options()//
			("help,h", "produce help message")//
			("start_nodes,s", po::value<std::vector<int >>(),"nodes, where to start")//
			("thread_number,t", po::value<int>(), "number of threads for parallelization")//
			("input_file,i", po::value<string>(), "input file");
	po::positional_options_description p;
	p.add("input_file", -1);
	po::variables_map vm;
	po::store(
			po::command_line_parser(argn, argv).options(desc).positional(p).run(),
			vm);
	po::notify(vm);
	return vm;
}

/** Reading in a Graphfile, computes the Steiner */
int main(int argn, char *argv[]) {
	BoostTimer timerProgram;
	if (argn <= 1) {
		cerr << "ERROR : There was no filename" << endl;
		return 1;
	}

	po::options_description desc("Allowed options");
	po::variables_map vm = parseCommandLine(desc, argn, argv);

	if (vm.count("help")) {
	    cout << desc << "\n";
	    return 1;
	}

	std::ifstream fileStream;
	if(vm.count("input_file") == 0){
		cerr << "No input-file was given!" << endl;
		return 1;
	}

	string filename = vm["input_file"].as<string >();
	if(filename.find(FILEEND) == std::string::npos){
		filename += FILEEND;
	}
	cout << "Going to parse the file " << filename << endl;
	fileStream.open(filename.c_str(), std::ios::in);

	if ( (fileStream.rdstate()) != 0 ){
		std::perror("ERROR : Encoutered Problem opening file");
		return 1;
	}

	std::vector<int > startnodes;
	if(vm.count("start_nodes") == 0){
		cout << "Using default startnodes (first 100 primes)" << endl;
	} else {
		startnodes = vm["start_node"].as<std::vector<int >>();
	}

	int threadNumber;
	if(vm.count("thread_number") == 0){
		cout << "Using default number of threads (8)" << endl;
		threadNumber = DEFAULT_THREAD_NUMBER;
	} else {
		threadNumber = vm["thread_number"].as<int >();
	}

	cout << endl;

	string line;

	unsigned int edgeCount;
	unsigned int vertexCount;

	if(std::getline(fileStream, line)){
		sscanf(line.c_str(), "%d %d", &vertexCount, &edgeCount);
		cout << "Vertexcount: " << vertexCount << endl;
		cout << "Edgecount: " << edgeCount << endl;
		line.clear();
		vertexCount++;
	} else {
		cerr << "ERROR : File was empty" << endl;
		return 1;
	}

	Edges* edges = new Edges(edgeCount);
	Weights* weights = new Weights(edgeCount);

	cout << "Reading edges..." << flush;
	int i = 0;
	while (getline(fileStream, line)) {
		int start;
		int end;
		double weight;
		int count = sscanf(line.c_str(), "%d %d %lf", &start, &end, &weight);
		if (count != 3) {
			line.clear();
			continue;
		}
		edges->at(i) = std::make_pair(start, end);
		weights->at(i) = weight;
		i++;
		line.clear();
	}
	cout << "done" << endl << endl;

	Primes terminals = getPrimes(vertexCount);
	if(startnodes.empty()){
		for (unsigned int i = 0;
				i < MAX_FOR_DEFAULT_TERMINALS && i < terminals.size(); i++) {
			startnodes.push_back(terminals[i]);
		}
	}

	cout << "Solves Steiner-tree for " << startnodes.size() << " startnodes..." << flush;

	BoostTimer timerSteiner;
	Steiner** steiners = new Steiner*[startnodes.size()];
	#pragma omp parallel for num_threads(threadNumber)
	for(unsigned int i = 0; i < startnodes.size(); i++){
		steiners[i] = new Steiner();
		steiners[i]->computeSteinerTree(vertexCount, edges, *weights, terminals, startnodes[i]);
	}
	timerSteiner.stop();
	BoostTimes timesSteiner = timerSteiner.elapsed();
	cout << "done" << endl;

	Steiner* s = steiners[0];
	int node = startnodes[0];
	int weight = s->getWeight();
	cout << "Searching the one with least weight..." << flush;
	for(unsigned int i = 0; i < startnodes.size(); i++){
		if(weight > steiners[i]->getWeight()){
			s = steiners[i];
			node = startnodes[i];
			weight = steiners[i]->getWeight();
		}
	}
	cout << "done" << endl;
	cout << "It's the one with startnode " << node << endl << endl;

	cout << "Checking for cycle..." << flush;
	Edges steinerEdges = s->getEdges();
	GraphChecker* checker = new GraphChecker(steinerEdges, s->getNodes(), terminals);
	if(checker->hasCycle()){
		cout << "failed" << endl;
		return 1;
	}
	cout << "passed" << endl;

	cout << "Checking if graph is connected..." << flush;
	if(!checker->isConnected()){
		cout << "failed" << endl;
		return 1;
	}
	cout << "passed" << endl;

	cout << "Checking if all terminals are included..." << flush;
	if(!checker->containsAllTerminals()){
		cout << "failed" << endl;
		return 1;
	}
	cout << "passed" << endl << endl;

	cout << "TLEN: " << s->getWeight() << endl;

	cout << "TREE:";
	for(unsigned int i = 0; i < steinerEdges.size(); i++){
		Edge edge = steinerEdges[i];
		cout << " (" << edge.first << ", " << edge.second << ")";
	}
	cout << endl;

	timerProgram.stop();
	BoostTimes timesProgram = timerProgram.elapsed();
	cout << "TIME: " << boost::timer::format(timesProgram, 4, "%t") << endl;
	cout << "WALL: " << boost::timer::format(timesSteiner, 4, "%w") << endl;

	delete edges;
	delete weights;
	delete checker;
	for(unsigned int i = 0; i < startnodes.size(); i++){
		delete steiners[i];
	}
	delete [] steiners;

	return 0;
}


/**
 * @file tests/main_tests/hmm_train_test.cpp
 * @author Daivik Nema
 *
 * Test mlpackMain() of hmm_train_main.cpp.
 *
 * mlpack is free software; you may redistribute it and/or modify it under the
 * terms of the 3-clause BSD license.  You should have received a copy of the
 * 3-clause BSD license along with mlpack.  If not, see
 * http://www.opensource.org/licenses/BSD-3-Clause for more information.
 */
#include <string>
#include <fstream>

#define BINDING_TYPE BINDING_TYPE_TEST
static const std::string testName = "HMMTrain";

#include <mlpack/core.hpp>
#include <mlpack/core/util/mlpack_main.hpp>
#include "test_helper.hpp"
#include <mlpack/methods/hmm/hmm_train_main.cpp>
#include <mlpack/methods/hmm/hmm_model.hpp>

#include "../catch.hpp"
#include "../test_catch_tools.hpp"

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

using namespace mlpack;

struct HMMTrainMainTestFixture
{
 public:
  HMMTrainMainTestFixture()
  {
    // Cache in the options for this program.
    IO::RestoreSettings(testName);
  }

  ~HMMTrainMainTestFixture()
  {
    // Clear the settings.
    bindings::tests::CleanMemory();
    IO::ClearSettings();
  }
};

inline void FileExists(std::string fileName)
{
  ifstream ifp(fileName);
  if (!ifp.good())
    FAIL("Bad stream " + fileName);
  ifp.close();
}

inline void CheckMatricesDiffer(arma::mat& a, arma::mat& b, double tolerance)
{
  bool dimsEqual = (a.n_rows == b.n_rows)
    && (a.n_cols == b.n_cols)
    && (a.n_elem == b.n_elem);
  bool valsEqual = true;
  if (dimsEqual)
  {
    for (size_t i = 0; i < a.n_elem; ++i)
    {
      if (std::abs(a[i]) < tolerance / 2)
        valsEqual = valsEqual && (std::abs(b[i]) < tolerance / 2);
      else
        valsEqual = valsEqual && (std::abs(a[i] - b[i]) < tolerance);
    }
  }
  REQUIRE(!(dimsEqual && valsEqual));
}

inline void ApproximatelyEqual(HMMModel& h1,
                               HMMModel& h2,
                               double tolerance = 1.0)
{
  REQUIRE(h1.Type() == h2.Type());
  HMMType  hmmType = h1.Type();
  if (hmmType ==  DiscreteHMM)
  {
    CheckMatrices(
        h1.DiscreteHMM()->Transition()*100,
        h2.DiscreteHMM()->Transition()*100,
        tolerance);
    CheckMatrices(
        h1.DiscreteHMM()->Transition()*100,
        h2.DiscreteHMM()->Transition()*100,
        tolerance);

    // Check if emission dists are equal
    std::vector<distribution::DiscreteDistribution> d1 =
        h1.DiscreteHMM()->Emission();
    std::vector<distribution::DiscreteDistribution> d2 =
        h2.DiscreteHMM()->Emission();

    REQUIRE(d1.size() == d2.size());

    size_t states = d1.size();
    for (size_t i = 0; i < states; ++i)
      for (size_t j = 0; j < d1[i].Dimensionality(); ++j)
        CheckMatrices(d1[i].Probabilities(j)*100,
            d2[i].Probabilities(j)*100,
            tolerance);
  }
  else if (hmmType == GaussianHMM)
  {
    CheckMatrices(
        h1.GaussianHMM()->Transition()*100,
        h2.GaussianHMM()->Transition()*100,
        tolerance);
    CheckMatrices(
        h1.GaussianHMM()->Initial()*100,
        h2.GaussianHMM()->Initial()*100,
        tolerance);
    // Check if emission dists are equal by comparing the mean and coviariance
    std::vector<distribution::GaussianDistribution> d1 =
        h1.GaussianHMM()->Emission();
    std::vector<distribution::GaussianDistribution> d2 =
        h2.GaussianHMM()->Emission();

    REQUIRE(d1.size() == d2.size());

    size_t states = d1.size();
    for (size_t i=0; i < states; ++i)
    {
      CheckMatrices(d1[i].Mean()*100, d2[i].Mean()*100, tolerance);
      CheckMatrices(d1[i].Covariance()*100, d2[i].Covariance()*100, tolerance);
    }
  }
  else if (hmmType == GaussianMixtureModelHMM)
  {
    CheckMatrices(
        h1.GMMHMM()->Transition()*100,
        h2.GMMHMM()->Transition()*100,
        tolerance);
    CheckMatrices(
        h1.GMMHMM()->Initial()*100,
        h2.GMMHMM()->Initial()*100,
        tolerance);
    // Check if emission dists are equal
    std::vector<gmm::GMM> d1 = h1.GMMHMM()->Emission();
    std::vector<gmm::GMM> d2 = h2.GMMHMM()->Emission();

    REQUIRE(d1.size() == d2.size());

    size_t states = d1.size();
    for (size_t i=0; i < states; ++i)
    {
      REQUIRE(d1[i].Gaussians() == d2[i].Gaussians());
      size_t gaussians = d1[i].Gaussians();
      for (size_t j=0; j<gaussians; ++j)
      {
        CheckMatrices(d1[i].Component(j).Mean()*100,
            d2[i].Component(j).Mean()*100,
            tolerance);
        CheckMatrices(d1[i].Component(j).Covariance()*100,
            d2[i].Component(j).Covariance()*100,
            tolerance);
      }
      CheckMatrices(d1[i].Weights()*100, d2[i].Weights()*100, tolerance);
    }
  }
  else if (hmmType == DiagonalGaussianMixtureModelHMM)
  {
    CheckMatrices(
        h1.DiagGMMHMM()->Transition()*100,
        h2.DiagGMMHMM()->Transition()*100,
        tolerance);
    CheckMatrices(
        h1.DiagGMMHMM()->Initial()*100,
        h2.DiagGMMHMM()->Initial()*100,
        tolerance);
    // Check if emission dists are equal.
    std::vector<gmm::DiagonalGMM> d1 = h1.DiagGMMHMM()->Emission();
    std::vector<gmm::DiagonalGMM> d2 = h2.DiagGMMHMM()->Emission();

    REQUIRE(d1.size() == d2.size());

    // Check if gaussian, mean, covariance and weights are equal.
    size_t states = d1.size();
    for (size_t i = 0; i < states; ++i)
    {
      REQUIRE(d1[i].Gaussians() == d2[i].Gaussians());
      size_t gaussians = d1[i].Gaussians();
      for (size_t j = 0; j < gaussians; ++j)
      {
        CheckMatrices(d1[i].Component(j).Mean()*100,
            d2[i].Component(j).Mean()*100,
            tolerance);
        CheckMatrices(d1[i].Component(j).Covariance()*100,
            d2[i].Component(j).Covariance()*100,
            tolerance);
      }
      CheckMatrices(d1[i].Weights()*100, d2[i].Weights()*100, tolerance);
    }
  }
}

// Make sure that the number of states cannot be negative
TEST_CASE_METHOD(HMMTrainMainTestFixture, "HMMTrainStatesTest",
                 "[HMMTrainMainTest][BindingTests]")
{
  std::string inputFileName = "hmm_train_obs.csv";
  int states = -3;  // Invalid!
  std::string hmmType = "discrete";

  FileExists(inputFileName);
  SetInputParam("input_file", std::move(inputFileName));
  SetInputParam("states", states);
  SetInputParam("type", std::move(hmmType));

  Log::Fatal.ignoreInput = true;
  REQUIRE_THROWS_AS(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

// Make sure that tolerance is non negative
TEST_CASE_METHOD(HMMTrainMainTestFixture, "HMMTrainToleranceNonNegative",
                 "[HMMTrainMainTest][BindingTests]")
{
  std::string inputFileName = "hmm_train_obs.csv";
  int states = 3;
  std::string hmmType = "gaussian";
  double tol = - 100;  // Invalid

  FileExists(inputFileName);
  SetInputParam("input_file", std::move(inputFileName));
  SetInputParam("states", states);
  SetInputParam("type", std::move(hmmType));
  SetInputParam("tolerance", tol);

  Log::Fatal.ignoreInput = true;
  REQUIRE_THROWS_AS(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

// Make sure an error is thrown if type is something other than
// "discrete", "gaussian" or "gmm"
TEST_CASE_METHOD(HMMTrainMainTestFixture, "HMMTrainTypeTest",
                 "[HMMTrainMainTest][BindingTests]")
{
  std::string inputFileName = "hmm_train_obs.csv";
  int states = 3;
  std::string hmmType = "some-not-supported-possibly-non-type";

  FileExists(inputFileName);
  SetInputParam("input_file", std::move(inputFileName));
  SetInputParam("states", states);
  SetInputParam("type", std::move(hmmType));

  Log::Fatal.ignoreInput = true;
  REQUIRE_THROWS_AS(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

// Make sure that the number of gaussians cannot be less than 0
TEST_CASE_METHOD(HMMTrainMainTestFixture, "HMMTrainGaussianTest",
                 "[HMMTrainMainTest][BindingTests]")
{
  std::string inputFileName = "hmm_train_obs.csv";
  int states = 3;
  std::string hmmType = "gmm";
  int gaussians = -2;

  FileExists(inputFileName);
  SetInputParam("input_file", std::move(inputFileName));
  SetInputParam("states", states);
  SetInputParam("type", std::move(hmmType));
  SetInputParam("gaussians", gaussians);

  Log::Fatal.ignoreInput = true;
  REQUIRE_THROWS_AS(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

// Make sure that the number of Gaussians cannot be less than 0.
TEST_CASE_METHOD(HMMTrainMainTestFixture, "HMMTrainDiagonalGaussianTest",
                 "[HMMTrainMainTest][BindingTests]")
{
  std::string inputFileName = "hmm_train_obs.csv";
  int states = 3;
  std::string hmmType = "diag_gmm";
  int gaussians = -2;

  FileExists(inputFileName);
  SetInputParam("input_file", std::move(inputFileName));
  SetInputParam("states", states);
  SetInputParam("type", std::move(hmmType));
  SetInputParam("gaussians", gaussians);

  Log::Fatal.ignoreInput = true;
  REQUIRE_THROWS_AS(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

// Make sure that model reuse is possible and work properly
TEST_CASE_METHOD(HMMTrainMainTestFixture, "HMMTrainReuseDiscreteModelTest",
                 "[HMMTrainMainTest][BindingTests]")
{
  std::string inputObsFileName = "hmm_train_obs.csv";
  std::string inputLabFileName = "hmm_train_lab.csv";
  std::string hmmType = "discrete";
  int states = 3;

  FileExists(inputObsFileName);
  FileExists(inputLabFileName);
  // Make sure that the size of the
  // training seq, and training labels is same
  arma::mat trainObs, trainLab;
  data::Load(inputObsFileName, trainObs);
  data::Load(inputLabFileName, trainLab);
  REQUIRE(trainObs.n_rows == trainLab.n_rows);

  SetInputParam("input_file", std::move(inputObsFileName));
  SetInputParam("labels_file", std::move(inputLabFileName));
  SetInputParam("type", std::move(hmmType));
  SetInputParam("states", states);

  mlpackMain();

  HMMModel h1 = *(IO::GetParam<HMMModel*>("output_model"));

  SetInputParam("input_model", IO::GetParam<HMMModel*>("output_model"));

  IO::GetSingleton().Parameters()["type"].wasPassed = false;
  IO::GetSingleton().Parameters()["states"].wasPassed = false;

  mlpackMain();

  HMMModel h2 = *(IO::GetParam<HMMModel*>("output_model"));

  ApproximatelyEqual(h1, h2);
}

// Make sure that model reuse is possible and work properly
TEST_CASE_METHOD(HMMTrainMainTestFixture, "HMMTrainReuseGaussianModelTest",
                 "[HMMTrainMainTest][BindingTests]")
{
  std::string inputObsFileName = "hmm_train_obs.csv";
  std::string hmmType = "gaussian";
  int states = 3;

  FileExists(inputObsFileName);
  // Make sure that the size of the
  // training seq, and training labels is same
  arma::mat trainObs;
  data::Load(inputObsFileName, trainObs);

  SetInputParam("input_file", std::move(inputObsFileName));
  SetInputParam("type", std::move(hmmType));
  SetInputParam("states", states);

  mlpackMain();

  HMMModel h1 = *(IO::GetParam<HMMModel*>("output_model"));

  SetInputParam("input_model", IO::GetParam<HMMModel*>("output_model"));
  SetInputParam("tolerance", 1e10);

  IO::GetSingleton().Parameters()["type"].wasPassed = false;
  IO::GetSingleton().Parameters()["states"].wasPassed = false;

  mlpackMain();

  HMMModel h2 = *(IO::GetParam<HMMModel*>("output_model"));

  ApproximatelyEqual(h1, h2);
}

TEST_CASE_METHOD(HMMTrainMainTestFixture, "HMMTrainNoLabelsReuseModelTest",
                 "[HMMTrainMainTest][BindingTests]")
{
  std::string inputObsFileName = "hmm_train_obs.csv";
  std::string hmmType = "discrete";
  int states = 3;
  int seed = 0;

  FileExists(inputObsFileName);
  SetInputParam("input_file", std::move(inputObsFileName));
  SetInputParam("states", states);
  SetInputParam("type", std::move(hmmType));
  SetInputParam("seed", seed);

  // This call will train HMM using Baum-Welch training
  mlpackMain();

  HMMModel h1 = *(IO::GetParam<HMMModel*>("output_model"));

  SetInputParam("input_model", IO::GetParam<HMMModel*>("output_model"));

  IO::GetSingleton().Parameters()["type"].wasPassed = false;
  IO::GetSingleton().Parameters()["states"].wasPassed = false;

  // Train again using Baum Welch
  mlpackMain();

  HMMModel h2 = *(IO::GetParam<HMMModel*>("output_model"));

  ApproximatelyEqual(h1, h2);
}

// Test batch mode
TEST_CASE_METHOD(HMMTrainMainTestFixture, "HMMTrainBatchModeTest",
                 "[HMMTrainMainTest][BindingTests]")
{
  std::string observationsFileName = "observations.txt";
  std::string labelsFileName = "labels.txt";
  std::string hmmType = "discrete";
  int states = 2;

  SetInputParam("input_file", std::move(observationsFileName));
  SetInputParam("labels_file", std::move(labelsFileName));

  Log::Fatal.ignoreInput = true;
  REQUIRE_THROWS_AS(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;

  SetInputParam("states", states);
  SetInputParam("type", std::move(hmmType));
  SetInputParam("batch", (bool) true);

  mlpackMain();

  // Now pass an observations file with extra non-existent filenames
  observationsFileName = "corrupt-observations-1.txt";
  SetInputParam("input_file", std::move(observationsFileName));

  Log::Fatal.ignoreInput = true;
  REQUIRE_THROWS_AS(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;

  // Now a mismatch between #observation files and #label files
  observationsFileName = "corrupt-observations-2.txt";
  SetInputParam("input_file", std::move(observationsFileName));

  Log::Fatal.ignoreInput = true;
  REQUIRE_THROWS_AS(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

TEST_CASE_METHOD(HMMTrainMainTestFixture, "HMMTrainRetrainTest1",
                 "[HMMTrainMainTest][BindingTests]")
{
  std::string inputObsFile1 = "obs1.csv";
  std::string type = "discrete";
  int states = 2;
  int seed = 0;

  FileExists(inputObsFile1);
  SetInputParam("input_file", std::move(inputObsFile1));
  SetInputParam("type", std::move(type));
  SetInputParam("states", states);
  SetInputParam("seed", seed);

  mlpackMain();

  HMMModel h1 = *(IO::GetParam<HMMModel*>("output_model"));

  std::string inputObsFile2 = "obs4.csv";

  IO::GetSingleton().Parameters()["input_file"].wasPassed = false;
  IO::GetSingleton().Parameters()["type"].wasPassed = false;
  IO::GetSingleton().Parameters()["states"].wasPassed = false;

  FileExists(inputObsFile2);
  SetInputParam("input_file", std::move(inputObsFile2));
  SetInputParam("input_model", IO::GetParam<HMMModel*>("output_model"));

  mlpackMain();

  HMMModel h2 = *(IO::GetParam<HMMModel*>("output_model"));

  REQUIRE(h1.Type() == h2.Type());
  // Since we know that type of HMMs is discrete
  CheckMatricesDiffer(h1.DiscreteHMM()->Transition(),
      h2.DiscreteHMM()->Transition(), 1e-50);
}

// Attempt to retrain but increase states the second time round
TEST_CASE_METHOD(HMMTrainMainTestFixture, "HMMTrainRetrainTest2",
                 "[HMMTrainMainTest][BindingTests]")
{
  // Provide no labels file
  std::string inputObsFile1 = "obs1.csv";
  std::string type = "discrete";
  int states = 2;

  SetInputParam("input_file", std::move(inputObsFile1));
  SetInputParam("type", std::move(type));
  SetInputParam("states", states);

  mlpackMain();

  HMMModel h1 = *(IO::GetParam<HMMModel*>("output_model"));

  std::string inputObsFile2 = "obs3.csv";
  std::string inputLabFile2 = "lab1_corrupt.csv";

  SetInputParam("input_file", std::move(inputObsFile2));
  // Provide a labels file with more states than initially specified
  SetInputParam("labels_file", std::move(inputLabFile2));
  SetInputParam("input_model", IO::GetParam<HMMModel*>("output_model"));

  IO::GetSingleton().Parameters()["type"].wasPassed = false;
  IO::GetSingleton().Parameters()["states"].wasPassed = false;

  Log::Fatal.ignoreInput = true;
  REQUIRE_THROWS_AS(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

// Attempt to retrain but change the emission distribution type
TEST_CASE_METHOD(HMMTrainMainTestFixture, "HMMTrainRetrainTest3",
                 "[HMMTrainMainTest][BindingTests]")
{
  // Provide no labels file
  std::string inputObsFile1 = "obs1.csv";
  std::string type = "discrete";
  int states = 2;

  SetInputParam("input_file", std::move(inputObsFile1));
  SetInputParam("type", std::move(type));
  SetInputParam("states", states);

  mlpackMain();

  HMMModel h1 = *(IO::GetParam<HMMModel*>("output_model"));

  std::string inputObsFile2 = "obs2.csv";
  std::string inputLabFile2 = "lab2.csv";
  type = "gaussian";

  SetInputParam("input_file", std::move(inputObsFile2));
  SetInputParam("labels_file", std::move(inputLabFile2));
  SetInputParam("type", std::move(type));
  SetInputParam("input_model", IO::GetParam<HMMModel*>("output_model"));

  IO::GetSingleton().Parameters()["states"].wasPassed = false;

  mlpackMain();
  // Note that when emission type is changed -- like in this test, a warning
  // is printed stating that the new type is being ignored (no error is raised)

  HMMModel h2 = *(IO::GetParam<HMMModel*>("output_model"));

  REQUIRE(h1.Type() == DiscreteHMM);
  REQUIRE(h2.Type() == DiscreteHMM);
  REQUIRE(h2.Type() != GaussianHMM);
}


// Boost.Geometry (aka GGL, Generic Geometry Library)
// Unit Test

// Copyright (c) 2020 Digvijay Janartha, Hamirpur, India.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <iostream>

#include <geometry_test_common.hpp>

#include <boost/core/ignore_unused.hpp>
#include <boost/geometry/algorithms/make.hpp>
#include <boost/geometry/algorithms/append.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/multi_point.hpp>
#include <boost/geometry/geometries/concepts/multi_point_concept.hpp>
#include <boost/geometry/geometries/adapted/c_array.hpp>
#include <boost/geometry/geometries/adapted/boost_tuple.hpp>
#include <boost/geometry/io/dsv/write.hpp>

#include <test_common/test_point.hpp>

BOOST_GEOMETRY_REGISTER_C_ARRAY_CS(cs::cartesian)
BOOST_GEOMETRY_REGISTER_BOOST_TUPLE_CS(cs::cartesian)

#ifdef BOOST_NO_CXX11_HDR_INITIALIZER_LIST
#include <initializer_list>
#endif//BOOST_NO_CXX11_HDR_INITIALIZER_LIST

template <typename P>
bg::model::multi_point<P> create_multi_point()
{
    bg::model::multi_point<P> mp1;
    P p1;
    bg::assign_values(p1, 1, 2, 3);
    bg::append(mp1, p1);
    return mp1;
}

template <typename L, typename T>
void check_multi_point(L& to_check, T x, T y, T z)
{
    BOOST_CHECK_EQUAL(bg::get<0>(to_check[0]), x);
    BOOST_CHECK_EQUAL(bg::get<1>(to_check[0]), y);
    BOOST_CHECK_EQUAL(bg::get<2>(to_check[0]), z);
}

template <typename P>
void test_default_constructor()
{
    bg::model::multi_point<P> mp1(create_multi_point<P>());
    check_multi_point(mp1, 1, 2, 3);
}

template <typename P>
void test_copy_constructor()
{
    bg::model::multi_point<P> mp1 = create_multi_point<P>();
    check_multi_point(mp1, 1, 2, 3);
}

template <typename P>
void test_copy_assignment()
{
    bg::model::multi_point<P> mp1(create_multi_point<P>()), mp2;
    mp2 = mp1;
    check_multi_point(mp2, 1, 2, 3);
}

template <typename P>
void test_concept()
{
    typedef bg::model::multi_point<P> MP;

    BOOST_CONCEPT_ASSERT( (bg::concepts::ConstMultiPoint<MP>) );
    BOOST_CONCEPT_ASSERT( (bg::concepts::MultiPoint<MP>) );

    typedef typename bg::coordinate_type<MP>::type T;
    typedef typename bg::point_type<MP>::type MPP;
    boost::ignore_unused<T, MPP>();
}

template <typename P>
void test_all()
{
    test_default_constructor<P>();
    test_copy_constructor<P>();
    test_copy_assignment<P>();
    test_concept<P>();
}

template <typename P>
void test_custom_multi_point(std::initializer_list<P> IL)
{
    bg::model::multi_point<P> mp1(IL);
    std::ostringstream out;
    out << bg::dsv(mp1);
    BOOST_CHECK_EQUAL(out.str(), "((0, 0), (1, 2), (2, 0))");
}

template <typename P>
void test_custom()
{
#ifdef BOOST_NO_CXX11_HDR_INITIALIZER_LIST
    std::initializer_list<P> IL = {P(0, 0), P(1, 2), P(2, 0)};
    test_custom_multi_point<P>(IL);
#endif//BOOST_NO_CXX11_HDR_INITIALIZER_LIST
}

template <typename CS>
void test_cs()
{
    test_all<bg::model::point<int, 3, CS> >();
    test_all<bg::model::point<float, 3, CS> >();
    test_all<bg::model::point<double, 3, CS> >();

    test_custom<bg::model::point<double, 2, CS> >();
}

int test_main(int, char* [])
{
    test_cs<bg::cs::cartesian>();
    test_cs<bg::cs::spherical<bg::degree> >();
    test_cs<bg::cs::spherical_equatorial<bg::degree> >();
    test_cs<bg::cs::geographic<bg::degree> >();

    test_custom<bg::model::d2::point_xy<double> >();

    return 0;
}


/*
 * Copyright Andrey Semashev 2020
 *
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or copy at
 * https://www.boost.org/LICENSE_1_0.txt)
 */
/*!
 * \file counting.hpp
 *
 * This header includes all algorithms for counting bits.
 */

#ifndef BOOST_BIT_OPS_COUNTING_HPP_INCLUDED_
#define BOOST_BIT_OPS_COUNTING_HPP_INCLUDED_

#include <boost/bit_ops/counting/countl_zero.hpp>
#include <boost/bit_ops/counting/countl_one.hpp>
#include <boost/bit_ops/counting/countr_zero.hpp>
#include <boost/bit_ops/counting/countr_one.hpp>
#include <boost/bit_ops/counting/popcount.hpp>

#endif // BOOST_BIT_OPS_COUNTING_HPP_INCLUDED_


//============================================================================
// Name        : LBFGS_MPI.cpp
// Author      : Yaser
// Version     :
// Copyright   :
// Description : LBFGS MPI implementation
//============================================================================

/*
 ** consider quadratic function, f(x) = 0.5*\|Ax-b\|^2
 ** Implement LBFGS algorithm as is explained in
 ** https://en.wikipedia.org/wiki/Limited-memory_BFGS
 **
 */

#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <cstdlib>  /* rand, srand */
#include <vector>

#include <time.h>
#include <boost/mpi.hpp>
namespace mpi = boost::mpi;

#include <boost/timer/timer.hpp>

#include "solvers/OptimizationParameters.h"
#include "solvers/ProblemData.h"
#include "solvers/QuasiNewtonSolver.h"
#include "solvers/LBFGSSolver.h"

using namespace std;

int main(int argc, char* argv[]) {
	mpi::environment env(argc, argv);
	mpi::communicator world;
	srand(1);
	ProblemData problemData;
	OptimizationParameters optParams;
	optParams.m = 1000;
	optParams.n = 2000;
	optParams.num_iterations = 10000;//100 initially
	optParams.lbfgs_memory = 100; //10 initially
	optParams.stepsize = 10 / (0.0 + optParams.n);
	if (world.rank() == 0) { //Generate data on node 0
		int nnz = optParams.m * optParams.n;
		problemData.A.resize(nnz);
		for (int i = 0; i < optParams.n; i++) {
			double tmp = 0;
			for (int j = 0; j < optParams.m; j++) {
				double r = rand() / (RAND_MAX + 0.0);
				problemData.A[i * optParams.m + j] = r;
				tmp += r * r;
			}
			tmp = 1 / sqrt(tmp);
			for (int j = 0; j < optParams.m; j++) {
				problemData.A[i * optParams.m + j] = problemData.A[i * optParams.m + j] * tmp;
			}

		}
		problemData.b.resize(optParams.m);
		for (int i = 0; i < optParams.m; i++) {
			problemData.b[i] = rand() / (RAND_MAX + 0.0);
		}
		problemData.x.resize(optParams.n, 0);
	}

/*
	if (world.rank() == 0) {
		cout<<"Amatrix: "<<endl;
		for (int i = 0; i < optParams.n; i++) {
			for (int j = 0; j < optParams.m; j++) {
				cout<< std::setprecision(17)<<problemData.A[i*optParams.m+j]<<"     ";
			}
			cout<<endl;
		}

		cout<<"endA"<<endl;
		cout<<"bvec"<<endl;

		for (int i = 0; i < optParams.m; i++) {
			cout<< std::setprecision(17)<<problemData.b[i]<<"     ";
		}
		cout<<"bvecEnd"<<endl;
	}
*/

	boost::timer::cpu_timer timer;
	LBFGSSolver solver(problemData, optParams);
	boost::timer::cpu_times elapsedInInitialization = timer.elapsed();
	if (world.rank() == 0) { // Writes total running time
		std::cout << "Initialization of class took: " << (elapsedInInitialization.user + elapsedInInitialization.system) / 1e9 << " (sec)" << "  Wallclock time: "
				<< elapsedInInitialization.wall / 1e9 << " (sec)" << std::endl;
	}

	/* this function computes and takes the step to obtain a new iterate */
	double objVal = solver.computeCurrentObjectiveValue();
	if (world.rank() == 0) {
		cout << "Initial Obj Value is " << objVal << endl;
	}

	for (int it = 0; it < optParams.num_iterations; it++) {

		timer.start();
		solver.computeAndTakeStep(); // this function computes and takes the step to obtain a new iterate
		boost::timer::cpu_times computingStep = timer.elapsed();

		timer.start();
		objVal = solver.computeCurrentObjectiveValue(); // this function computes and takes the step to obtain a new iterate
		boost::timer::cpu_times objValTime = timer.elapsed();

		if (world.rank() == 0) { // Writes total running time
			std::cout << "Iteration: " << it << " ObjVal: " << objVal << " Times: " << (computingStep.user + computingStep.system) / 1e9 << " (sec)"
					<< "  Wall clock time: " << computingStep.wall / 1e9 << " (sec)" << " " << (objValTime.user + objValTime.system) / 1e9 << " (sec)"
					<< "  Wall clock time: " << objValTime.wall / 1e9 << " (sec)" << std::endl;
		}

	}

	return 0;
}


/*
MIT License

Copyright (c) 2016 Vernam Group

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include "../include/fntru.h"
#include <sys/time.h>
#include <NTL/GF2X.h>
#include <NTL/mat_ZZ.h>

using namespace std;

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//And and XOR operations for test preparation
int AND(int b0, int b1){
	return(b0*b1)%2;
}

int XOR(int b0, int b1){
	return(b0+b1)%2;
}

//Convert message in ZZX to int. If not bit print -1
int ConvMessPoly(ZZX in){
	ZZX zero, one;
	one = 1;
	zero = 0;

	if(in == 0)
		return 0;
	else if(in == 1)
		return 1;
	else
		return -1;
}

//Check if all the values of boolean circuit is equal
bool CheckBooleanCircuitResult(int midResult[], ZZX messmidResult[], int opSize){
	bool isEqual = true;

	for(int i=0; i<opSize+1; i++)
		if(midResult[i] != ConvMessPoly(messmidResult[i])){
			isEqual = false;
			break;
		}

	return isEqual;
}

//Print all the values of the Boolean Circuit (mid results)
void PrintBooleanCircuitValues(int midResult[], ZZX messmidResult[], int opSize){
	for(int i=0; i<opSize+1; i++){
		cout << "Op\t" << i << "\t" << midResult[i] << "\t" << ConvMessPoly(messmidResult[i]) << "\t" << endl;
	}
}

void RandomBooleanTest(){

	int opSize = 16; 	// Set boolean circuit operation size
	int b[opSize+1]; 	// Number of bits
	int op[opSize];		// Operations. 0 is AND, 1 is XOR

	//Create Random Bits and operations
	srand(time(NULL));
	for(int i=0; i<opSize; i++)
			op[i] = rand()%2;
	for(int i=0; i<opSize+1; i++)
			b[i] = rand()%2;

	//Compute the random boolean function and store the mid-results
	int opResult, midResult[opSize+1];
	opResult = b[0];
	for(int i=0; i<opSize; i++){
		midResult[i] = opResult;
		if(op[i] == 0)
			opResult = AND(opResult, b[i+1]);
		else
			opResult = XOR(opResult, b[i+1]);
	}
	//Store the last result
	midResult[opSize] = opResult;

	// Set fntru parameters and create ciphertexts to encrypt the bits of boolean function
	int M 			= M_;
	int radix		= 16;
	int bitSize 	= Dif_Prime;
	ZZ p 			= to_ZZ("2");

	fntru g(M, bitSize, radix,  p);
	ZZX message, messaget, m, r0, r1;
	fntru_cipher gResult, gb[opSize+1];

	// Initialize the ciphertexts
	g.InitializeCipher(gResult);
	for(int i=0; i<opSize+1; i++)
		g.InitializeCipher(gb[i]);

	// Encrypt the boolean bits
	for(int i=0; i<opSize+1; i++){
		message = b[i];
		g.Encrypt(gb[i], message);
	}

	// Compute the boolean function and store the mid-results by decrypting the result
	ZZX messResult, messb[opSize+1], messmidResult[opSize+1];
	gResult = gb[0];
	for(int i=0; i<opSize; i++){

		g.Decrypt(messb[i], gb[i]);
		g.Decrypt(messResult, gResult);

		messmidResult[i] = messResult;
		if(op[i] == 0)
			g.AND(gResult, gResult, gb[i+1], 0);
		else
			g.XOR(gResult, gResult, gb[i+1], 0);
	}
	// Decrypt the last result
	g.Decrypt(messResult, gResult);
	messmidResult[opSize] = messResult;

	cout << "Boolean Equal:\t" << CheckBooleanCircuitResult(midResult, messmidResult, opSize) << endl;
	PrintBooleanCircuitValues(midResult, messmidResult, opSize);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void TestAND(fntru &f){

	fntru_cipher a, b, c;

	f.InitializeCipher(a);
	f.InitializeCipher(b);
	f.InitializeCipher(c);

	ZZX one, zero, result;
	one 	= 1;
	zero 	= 0;

	cout << "\\\\\\\\\\\\ AND \\\\\\\\\\\\" << endl;
	cout << "a\t" << "b\t" << "c\t" << endl << endl;

	f.Encrypt(a, zero);
	f.Encrypt(b, zero);
	f.AND(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "0\t" << "0\t" << ConvMessPoly(result) << endl;

	f.Encrypt(a, zero);
	f.Encrypt(b, one);
	f.AND(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "0\t" << "1\t" << ConvMessPoly(result) << endl;

	f.Encrypt(a, one);
	f.Encrypt(b, zero);
	f.AND(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "1\t" << "0\t" << ConvMessPoly(result) << endl;

	f.Encrypt(a, one);
	f.Encrypt(b, one);
	f.AND(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "1\t" << "1\t" << ConvMessPoly(result) << endl;

}

void TestNAND(fntru &f){

	fntru_cipher a, b, c;

	f.InitializeCipher(a);
	f.InitializeCipher(b);
	f.InitializeCipher(c);

	ZZX one, zero, result;
	one 	= 1;
	zero 	= 0;

	cout << "\\\\\\\\\\\\ NAND \\\\\\\\\\\\" << endl;
	cout << "a\t" << "b\t" << "c\t" << endl << endl;

	f.Encrypt(a, zero);
	f.Encrypt(b, zero);
	f.NAND(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "0\t" << "0\t" << ConvMessPoly(result) << endl;

	f.Encrypt(a, zero);
	f.Encrypt(b, one);
	f.NAND(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "0\t" << "1\t" << ConvMessPoly(result) << endl;

	f.Encrypt(a, one);
	f.Encrypt(b, zero);
	f.NAND(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "1\t" << "0\t" << ConvMessPoly(result) << endl;

	f.Encrypt(a, one);
	f.Encrypt(b, one);
	f.NAND(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "1\t" << "1\t" << ConvMessPoly(result) << endl;

}

void TestOR(fntru &f){

	fntru_cipher a, b, c;

	f.InitializeCipher(a);
	f.InitializeCipher(b);
	f.InitializeCipher(c);

	ZZX one, zero, result;
	one 	= 1;
	zero 	= 0;

	cout << "\\\\\\\\\\\\ OR \\\\\\\\\\\\" << endl;
	cout << "a\t" << "b\t" << "c\t" << endl << endl;

	f.Encrypt(a, zero);
	f.Encrypt(b, zero);
	f.OR(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "0\t" << "0\t" << ConvMessPoly(result) << endl;

	f.Encrypt(a, zero);
	f.Encrypt(b, one);
	f.OR(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "0\t" << "1\t" << ConvMessPoly(result) << endl;

	f.Encrypt(a, one);
	f.Encrypt(b, zero);
	f.OR(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "1\t" << "0\t" << ConvMessPoly(result) << endl;

	f.Encrypt(a, one);
	f.Encrypt(b, one);
	f.OR(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "1\t" << "1\t" << ConvMessPoly(result) << endl;

}

void TestXOR(fntru &f){

	fntru_cipher a, b, c;

	f.InitializeCipher(a);
	f.InitializeCipher(b);
	f.InitializeCipher(c);

	ZZX one, zero, result;
	one 	= 1;
	zero 	= 0;

	cout << "\\\\\\\\\\\\ XOR \\\\\\\\\\\\" << endl;
	cout << "a\t" << "b\t" << "c\t" << endl << endl;

	f.Encrypt(a, zero);
	f.Encrypt(b, zero);
	f.XOR(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "0\t" << "0\t" << ConvMessPoly(result) << endl;

	f.Encrypt(a, zero);
	f.Encrypt(b, one);
	f.XOR(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "0\t" << "1\t" << ConvMessPoly(result) << endl;

	f.Encrypt(a, one);
	f.Encrypt(b, zero);
	f.XOR(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "1\t" << "0\t" << ConvMessPoly(result) << endl;

	f.Encrypt(a, one);
	f.Encrypt(b, one);
	f.XOR(c, a, b, 1);
	f.Decrypt(result, c);
	cout << "1\t" << "1\t" << ConvMessPoly(result) << endl;
}

void TestNOT(fntru &f){

	fntru_cipher a, c;

	f.InitializeCipher(a);
	f.InitializeCipher(c);

	ZZX one, zero, result;
	one 	= 1;
	zero 	= 0;

	cout << "\\\\\\\\\\\\ NOT \\\\\\\\\\\\" << endl;
	cout << "a\t" << "c\t" << endl << endl;

	f.Encrypt(a, zero);
	f.NOT(c, a, 1);
	f.Decrypt(result, c);
	cout << "0\t" << ConvMessPoly(result) << endl;

	f.Encrypt(a, one);
	f.NOT(c, a, 1);
	f.Decrypt(result, c);
	cout << "1\t" << ConvMessPoly(result) << endl;
}

void GateTest(){
	int M 			= M_;
	int radix		= 16;
	int bitSize 	= Dif_Prime;
	ZZ p 			= to_ZZ("2");

	fntru f(M, bitSize, radix,  p);

	TestNOT(f);
	TestXOR(f);
	TestOR(f);
	TestAND(f);
	TestNAND(f);
}

int main(){

	int threadNum = 4;
	SetNumThreads(threadNum);

	// Create random boolean circuit and compute
	RandomBooleanTest();
	// Test if the gate functions work correctly
	GateTest();

	return 0;
}