#include "processor.h"
#include "histogramutility.h"
#include <math.h>
#include <algorithm>
#include <iostream>
#include "boost/pending/disjoint_sets.hpp"
#include <boost/unordered/unordered_set.hpp>

using namespace cv;
using namespace std;

Processor::Processor()
{
}

Mat Processor::invert(Mat &img){
    Mat res(img.rows, img.cols, img.type());

    for (int i = 0; i < img.rows; i++){
        for (int j = 0; j < img.cols; j++){
            Vec3b& color = res.at<Vec3b>(Point(j, i));
            Vec3b& color_org = img.at<Vec3b>(Point(j, i));

            color[0] = 255 - color_org[0];
            color[1] = 255 - color_org[1];
            color[2] = 255 - color_org[2];
        }
    }
    return res;
}

Mat* Processor::kirsch(Mat& img){

    Mat* results = new Mat[5];

    Mat res_1(img.rows, img.cols, img.type());
    Mat res_2(img.rows, img.cols, img.type());
    Mat res_3(img.rows, img.cols, img.type());
    Mat res_4(img.rows, img.cols, img.type());

    Point anchor(1, 1);
    double delta = 0;
    int ddepth = -1;
    const int filter_width = 3;

    float elements_1[filter_width * filter_width] = {-1, 0, 1, -1, 0, 1, -1, 0, 1};
    float elements_2[filter_width * filter_width] = {1, 1, 1, 0, 0, 0, -1, -1, -1};
    float elements_3[filter_width * filter_width] = {0, 1, 1, -1, 0, 1, -1, -1, 0};
    float elements_4[filter_width * filter_width] = {1, 1, 0, 1, 0, -1, 0, -1, -1};

    Mat kernel_1(Size(filter_width, filter_width), CV_32F, elements_1);
    filter2D(img, res_1, ddepth, kernel_1, anchor, delta, BORDER_DEFAULT);

    Mat kernel_2(Size(filter_width, filter_width), CV_32F, elements_2);
    filter2D(img, res_2, ddepth, kernel_2, anchor, delta, BORDER_DEFAULT);

    Mat kernel_3(Size(filter_width, filter_width), CV_32F, elements_3);
    filter2D(img, res_3, ddepth, kernel_3, anchor, delta, BORDER_DEFAULT);

    Mat kernel_4(Size(filter_width, filter_width), CV_32F, elements_4);
    filter2D(img, res_4, ddepth, kernel_4, anchor, delta, BORDER_DEFAULT);

    Mat res(img.rows, img.cols, img.type());
    for(int i = 0; i < img.cols; i++){
        for(int j = 0; j < img.rows; j++){
            Vec3b& color1 = res_1.at<Vec3b>(Point(i, j));
            Vec3b& color2 = res_2.at<Vec3b>(Point(i, j));
            Vec3b& color3 = res_3.at<Vec3b>(Point(i, j));
            Vec3b& color4 = res_4.at<Vec3b>(Point(i, j));

            Vec3b& color_res = res.at<Vec3b>(Point(i, j));

            int max = color1[0] > color2[0] ? color1[0] : color2[0];
            max = max > color3[0] ? max : color3[0];
            max = max > color4[0] ? max : color4[0];

            color_res[0] = max;
            color_res[1] = max;
            color_res[2] = max;
        }
    }

    results[0] = res_1;
    results[1] = res_2;
    results[2] = res_3;
    results[3] = res_4;
    results[4] = res;

    return results;
}

Mat Processor::prewitt(Mat &img){
    Mat res_x(img.rows, img.cols, img.type());
    Mat res_y(img.rows, img.cols, img.type());

    Point anchor(1, 1);
    double delta = 0;
    int ddepth = -1;
    const int filter_width = 3;

    float elements_x[filter_width * filter_width] = {-1, 0, 1, -1, 0, 1, -1, 0, 1};
    float elements_y[filter_width * filter_width] = {-1, -1, -1, 0, 0, 0, 1, 1, 1};

    Mat kernel_x(Size(filter_width, filter_width), CV_32F, elements_x);
    filter2D(img, res_x, ddepth, kernel_x, anchor, delta, BORDER_DEFAULT);

    Mat kernel_y(Size(filter_width, filter_width), CV_32F, elements_y);
    filter2D(img, res_y, ddepth, kernel_y, anchor, delta, BORDER_DEFAULT);

    Mat res(img.rows, img.cols, img.type());
    for(int i = 0; i < img.cols; i++){
        for(int j = 0; j < img.rows; j++){
            Vec3b& color1 = res_x.at<Vec3b>(Point(i, j));
            Vec3b& color2 = res_y.at<Vec3b>(Point(i, j));

            Vec3b& color_res = res.at<Vec3b>(Point(i, j));

            int sum = sqrt(color1[0] * color1[0] + color2[0] * color2[0]);

            sum = sum > 255 ? 255:sum;
            sum = sum < 0 ? 0 : sum;

            color_res[0] = sum;
            color_res[1] = sum;
            color_res[2] = sum;
        }
    }

    return res;
}

Mat Processor::gaussian(Mat &img){
    Mat res(img.rows, img.cols, img.type());

    Point anchor(1, 1);
    double delta = 0;
    int ddepth = -1;
    const int filter_width = 7;

    float elements[filter_width * filter_width] = {0.00000067, 0.00002292, 0.00019117, 0.00038771, 0.00019117, 0.00002292, 0.00000067,
                                                  0.00002292, 0.00078634, 0.00655965, 0.01330373, 0.00655965, 0.00078633, 0.00002292,
                                                  0.00019117, 0.00655965, 0.05472157, 0.11098164, 0.05472157, 0.00655965, 0.00019117,
                                                  0.00038771, 0.01330373, 0.11098164, 0.22508352, 0.11098164, 0.01330373, 0.00038771,
                                                  0.00019117, 0.00655965, 0.05472157, 0.11098164, 0.05472157, 0.00655965, 0.00019117,
                                                  0.00002292, 0.00078634, 0.00655965, 0.01330373, 0.00655965, 0.00078633, 0.00002292,
                                                  0.00000067, 0.00002292, 0.00019117, 0.00038771, 0.00019117, 0.00002292, 0.00000067
                                                  };

    Mat kernel(Size(filter_width, filter_width), CV_32F, elements);
    filter2D(img, res, ddepth, kernel, anchor, delta, BORDER_DEFAULT);

    return res;
}

Mat Processor::laplacian(Mat &img){
    Mat res(img.rows, img.cols, img.type());

    Point anchor(1, 1);
    double delta = 0;
    int ddepth = -1;
    const int filter_width = 3;

    float elements[filter_width * filter_width] = {0, 1, 0, 1, -4, 1, 0, 1, 0};

    Mat kernel(Size(filter_width, filter_width), CV_32F, elements);
    filter2D(img, res, ddepth, kernel, anchor, delta, BORDER_DEFAULT);

    int filterWidth = 3;

//    double **filter;
//    filter = new double *[filterWidth];
//    for(int i = 0; i < filterWidth; i++)
//        filter[i] = new double[filterWidth];

//    filter[0][0] = 0;
//    filter[0][1] = -1;
//    filter[0][2] = 0;

//    filter[1][0] = -1;
//    filter[1][1] = 4;
//    filter[1][2] = -1;

//    filter[2][0] = 0;
//    filter[2][1] = -1;
//    filter[2][2] = 0;

//    res = apply_filter(img, filter, filterWidth);

//    int min = 10000;
//    int max = -10000;
//    for(int i = 0; i < res.cols; i++){
//        for(int j = 0; j < res.rows; j++){
//            Vec3b& color_res = res.at<Vec3b>(Point(i, j));

//            if (color_res[0] < min){
//                min = color_res[0];
//            }

//            if (color_res[0] > max){
//                max = color_res[0];
//            }
//        }
//    }

//    for(int i = 0; i < res.cols; i++){
//        for(int j = 0; j < res.rows; j++){
//            Vec3b& color_res = res.at<Vec3b>(Point(i, j));

//            int sum = ((double)(color_res[0] - min) / (double)(max - min)) * 255;

//            color_res[0] = sum;
//            color_res[1] = sum;
//            color_res[2] = sum;
//        }
//    }

//    cout << "Max: " << max << endl;
//    cout << "Min: " << min << endl;

    for(int i = 0; i < res.cols; i++){
        for(int j = 0; j < res.rows; j++){
            Vec3b& color_res = res.at<Vec3b>(Point(i, j));

            int sum = color_res[0] + 128;

            color_res[0] = sum;
            color_res[1] = sum;
            color_res[2] = sum;
        }
    }

    return res;
}

Mat Processor::sobel(Mat &img){
    Mat res_x(img.rows, img.cols, img.type());
    Mat res_y(img.rows, img.cols, img.type());

    Point anchor(1, 1);
    double delta = 0;
    int ddepth = -1;
    const int filter_width = 3;

    float elements_x[filter_width * filter_width] = {-1, 0, 1, -2, 0, 2, -1, 0, 1};
    float elements_y[filter_width * filter_width] = {-1, -2, -1, 0, 0, 0, 1, 2, 1};

    Mat kernel_x(Size(filter_width, filter_width), CV_32F, elements_x);
    filter2D(img, res_x, ddepth, kernel_x, anchor, delta, BORDER_DEFAULT);

    Mat kernel_y(Size(filter_width, filter_width), CV_32F, elements_y);
    filter2D(img, res_y, ddepth, kernel_y, anchor, delta, BORDER_DEFAULT);

    Mat res(img.rows, img.cols, img.type());
    for(int i = 0; i < img.cols; i++){
        for(int j = 0; j < img.rows; j++){
            Vec3b& color1 = res_x.at<Vec3b>(Point(i, j));
            Vec3b& color2 = res_y.at<Vec3b>(Point(i, j));

            Vec3b& color_res = res.at<Vec3b>(Point(i, j));

            int sum = sqrt(color1[0] * color1[0] + color2[0] * color2[0]);

            sum = sum > 255 ? 255:sum;
            sum = sum < 0 ? 0 : sum;

            color_res[0] = sum;
            color_res[1] = sum;
            color_res[2] = sum;
        }
    }

    return res;
}

Mat Processor::roberts(Mat &img){
    Mat res_x(img.rows, img.cols, img.type());
    Mat res_y(img.rows, img.cols, img.type());

    Point anchor(0, 1);
    double delta = 0;
    int ddepth = -1;
    const int filter_width = 2;

    float elements_x[filter_width * filter_width] = {1.0, 0.0, 0.0, -1.0};
    float elements_y[filter_width * filter_width] = {0.0, 1.0, -1.0, 0.0};

    Mat kernel_x(Size(filter_width, filter_width), CV_32F, elements_x);
    filter2D(img, res_x, ddepth, kernel_x, anchor, delta, BORDER_DEFAULT);

    Mat kernel_y(Size(filter_width, filter_width), CV_32F, elements_y);
    filter2D(img, res_y, ddepth, kernel_y, anchor, delta, BORDER_DEFAULT);

    Mat res(img.rows, img.cols, img.type());
    for(int i = 0; i < img.cols; i++){
        for(int j = 0; j < img.rows; j++){
            Vec3b& color1 = res_x.at<Vec3b>(Point(i, j));
            Vec3b& color2 = res_y.at<Vec3b>(Point(i, j));

            Vec3b& color_res = res.at<Vec3b>(Point(i, j));

            int sum = sqrt(color1[0] * color1[0] + color2[0] * color2[0]);

            sum = sum > 255 ? 255:sum;
            sum = sum < 0 ? 0 : sum;

            color_res[0] = sum;
            color_res[1] = sum;
            color_res[2] = sum;
        }
    }

    return res;
}

Mat Processor::component_labeling(Mat &img, int white, int black){

    img = iterative_thresholding(img);

    int current_label = 0;

    Mat res(img.rows, img.cols, img.type());

    for (int i = 0; i < img.rows; i++){
        for (int j = 0; j < img.cols; j++){
            Vec3b& color = res.at<Vec3b>(Point(j, i));

            color[0] = 0;
        }
    }

    vector<int> rank(2000);
    vector<int> parent(2000);
    boost::disjoint_sets<int*, int*> ds(&rank[0], &parent[0]);
    boost::unordered_set<int> labels;

    for (int i = 0; i < img.rows; i++){
        for (int j = 0; j < img.cols; j++){

            Vec3b& color = img.at<Vec3b>(Point(j, i));
            int pixel = color[0];

            if (pixel == white){

                int pixel_top = black;
                if (i - 1 >= 0){
                    pixel_top = img.at<Vec3b>(Point(j, i - 1))[0];
                }

                int pixel_left = black;
                if (j - 1 >= 0){
                    pixel_left = img.at<Vec3b>(Point(j - 1, i))[0];
                }

                Vec3b& res_color = res.at<Vec3b>(Point(j, i));
                int result_v = -1;

                if (pixel_left == black && pixel_top == black){
                    current_label++;

                    cout << current_label << endl;

                    ds.make_set(current_label);
                    labels.insert(current_label);

                    result_v = current_label;
                }else if (pixel_left == black && pixel_top == white){

                    result_v = res.at<Vec3b>(Point(j, i - 1))[0];

                }else if (pixel_left == white && pixel_top == black){

                    result_v = res.at<Vec3b>(Point(j - 1, i))[0];

                }else if (pixel_left == white && pixel_top == white){
                    int left_label = res.at<Vec3b>(Point(j - 1, i))[0];
                    int top_label = res.at<Vec3b>(Point(j, i - 1))[0];

                    if (left_label != top_label){
                        ds.union_set(left_label, top_label);
                    }

                    result_v = left_label < top_label ? left_label : top_label;

                }

                if (result_v == -1){
                    cout << "ERROR" << endl;
                    exit(-1);
                }

                res_color[0] = result_v;
                res_color[1] = result_v;
                res_color[2] = result_v;
            }

        }
    }

    ds.normalize_sets(labels.begin(), labels.end());
    cout << "Number of components: " << ds.count_sets(labels.begin(), labels.end()) << endl;

    int r[] = {0, 150, 175, 200, 225, 250, 25, 50, 75, 100, 125};
    int g[] = {125, 150, 175, 200, 225, 250, 0, 25, 50, 75, 100};
    int b[] = {0, 25, 50, 175, 200, 225, 150, 250, 75, 100, 125};

    map<int, int> final_eq;
    int index = 0;
    int area = 0;
    for (int i = 0; i < img.rows; i++){
        for (int j = 0; j < img.cols; j++){
            Vec3b& color = res.at<Vec3b>(Point(j, i));
            int temp = (int)color[0];

            temp = ds.find_set(temp);

            if (color[0] != 0){
                if (final_eq[temp] == 0){
                    index++;
                    final_eq[temp] = index;
                }

                int index = (final_eq[temp] * 13) % 11;

                color[0] = r[index];
                color[1] = g[index];
                color[2] = b[index];

                area++;
            }else{
                color[0] = black;
                color[1] = black;
                color[2] = black;
            }
        }
    }

    cout << "Number of components: " << index << endl;
    cout << "Average area of the components: " << (double)(area) / index << endl;

    return res;

}

void Processor::convert_to_grayscale(Mat &img){
    for(int i = 0; i < img.cols; i++)
    {
        for(int j = 0; j < img.rows; j++)
        {
            Vec3b& color = img.at<Vec3b>(Point(i, j));
            int r = color[0];
            int g = color[1];
            int b = color[2];
            int gray_scale = 0.21 * r + 0.72 * g + 0.07 * b;

            color[0] = gray_scale;
            color[1] = gray_scale;
            color[2] = gray_scale;
        }
    }
}

Mat Processor::median_filter(Mat &img, int filter_w){
    int redu = filter_w / 2;

    Mat res(img.rows - redu * 2, img.cols - redu * 2, img.type());

    for(int i = redu; i < res.cols - redu; i++){
        for(int j = redu; j < res.rows - redu; j++){

            vector<int> values;
            for (int ii = 0; ii < filter_w; ii++){
                for (int jj = 0; jj < filter_w; jj++){
                    Vec3b& color = img.at<Vec3b>(Point(i + ii - redu, j + jj - redu));
                    values.push_back(color[0]);
                }
            }
            sort(values.begin(), values.end());

            Vec3b& color = res.at<Vec3b>(Point(i, j));

            int new_val = values[values.size() / 2 + 1];

            color[0] = new_val;
            color[1] = new_val;
            color[2] = new_val;
        }
    }

    return res;
}

Mat Processor::apply_even_filter(Mat& img, double** filter, int filter_w){
    Mat res(img.rows, img.cols, img.type());

    for(int i = 0; i < res.cols; i++){
        for(int j = 0; j < res.rows; j++){

            double sum = 0;
            for (int ii = 0; ii < filter_w; ii++){
                for (int jj = 0; jj < filter_w; jj++){
                    Vec3b& color = img.at<Vec3b>(Point(i + ii, j + jj));
                    sum += (color[0] * filter[ii][jj]);
                }
            }

            Vec3b& color = res.at<Vec3b>(Point(i, j));

            color[0] = sum;
            color[1] = sum;
            color[2] = sum;
        }
    }

    return res;
}

Mat Processor::apply_filter(Mat& img, double** filter, int filter_w, int threshold){
    int redu = filter_w / 2;

    Mat res(img.rows - redu * 2, img.cols - redu * 2, img.type());

    for(int i = redu; i < res.cols - redu; i++){
        for(int j = redu; j < res.rows - redu; j++){

            double sum = 0;
            for (int ii = 0; ii < filter_w; ii++){
                for (int jj = 0; jj < filter_w; jj++){
                    Vec3b& color = img.at<Vec3b>(Point(i + ii - redu, j + jj - redu));
                    sum += (color[0] * filter[ii][jj]);
                }
            }

            Vec3b& color = res.at<Vec3b>(Point(i, j));

            if (abs(sum - img.at<Vec3b>(Point(i, j))[0]) < threshold) {
                color[0] = sum;
                color[1] = sum;
                color[2] = sum;
            }else{
                color[1] = color[0];
                color[2] = color[0];
            }
        }
    }

    return res;
}

Mat Processor::contrast_enhance(Mat& img, int lower , int higher){

    int CLT[256];

    for (int i = 0; i < 256; i++){
        if (i < lower){
            CLT[i] = 0;
        }else if(i > higher){
            CLT[i] = 255;
        }else{
            CLT[i] = (int)(((double)i - (double)lower) / ((double)higher - (double)lower) * 255);
        }
    }

    Mat res(img.size(), img.type());
    for(int i = 0; i < res.cols; i++){
        for(int j = 0; j < res.rows; j++){
            int r = img.at<Vec3b>(Point(i, j))[0];
            Vec3b& color = res.at<Vec3b>(Point(i, j));

            color[0] = CLT[r];
            color[1] = CLT[r];
            color[2] = CLT[r];
        }
    }

    return res;
}

Mat Processor::adaptive_thresholding(Mat& img, int num_tiles){
    Mat res(img.size(), img.type());

    for (int i = 0; i < num_tiles; i++){
        for (int j = 0; j < num_tiles; j++){

            Range rows_r(i * img.rows / num_tiles, (i + 1) * img.rows / num_tiles);
            Range cols_r(j * img.cols / num_tiles, (j + 1) * img.cols / num_tiles);

            Mat small = img(rows_r, cols_r);
            small = iterative_thresholding(small);
            Mat small_part = res(rows_r, cols_r);

            small.copyTo(small_part);
        }
    }

    return res;
}

Mat Processor::p_tile_thresholding(Mat& img, int p_tile){
    int* hist = new int[256];
    HistogramUtility histUtil;
    histUtil.getHistogram(img, hist);

    int pixel_count = (int)((100 - p_tile) / 100.0 * img.cols * img.rows);

    int count = 0;
    int index = 0;
    while(count < pixel_count){
        count += hist[index];
        index++;
    }

    Mat res = threshold_binary(img, index);
    return res;
}

Mat Processor::iterative_thresholding(Mat& img, int init_threshold, double diff_threshold, int max_iterations){

    double diff = 256;
    int counter = 0;
    int current_threshold = init_threshold;
    while (diff > diff_threshold && counter < max_iterations){
        init_threshold = current_threshold;

        double sum1 = 0;
        double sum2 = 0;
        int count1 = 0;
        int count2 = 0;

        for(int i = 0; i < img.cols; i++){
            for(int j = 0; j < img.rows; j++){
                int r = img.at<Vec3b>(Point(i, j))[0];

                if (r < init_threshold){
                    sum1 += r;
                    count1++;
                }else{
                    sum2 += r;
                    count2++;
                }
            }
        }

        if (count1 != 0 && count2 != 0){
            sum1 /= count1;
            sum2 /= count2;
        }else{
            if (count1 == 0){
                sum1 = 0;
                sum2 /= count2;
            }else{
                sum1 /= count1;
                sum2 = 0;
            }
        }

        current_threshold = (int) ((1.0 / 2.0) * (sum1 + sum2));
        diff = abs(current_threshold - init_threshold);
        counter++;
    }

    Mat res = threshold_binary(img, current_threshold);
    return res;
}

Mat Processor::threshold_binary(Mat &img, int threshold){
    Mat res(img.size(), img.type());

    for(int i = 0; i < res.cols; i++){
        for(int j = 0; j < res.rows; j++){
            int r = img.at<Vec3b>(Point(i, j))[0];

            Vec3b& color = res.at<Vec3b>(Point(i, j));
            if (r < threshold){
                color[0] = 0;
                color[1] = 0;
                color[2] = 0;
            }else{
                color[0] = 255;
                color[1] = 255;
                color[2] = 255;
            }
        }
    }

    return res;
}

Mat* Processor::slicing_threshold(Mat& img, int number_of_slices){
    HistogramUtility histUtil;
    int* hist = new int[256];
    histUtil.getHistogram(img, hist);

    Mat* results = new Mat[number_of_slices];
    for (int i = 0; i < number_of_slices; i++){
        int range_start = (int)(i * (256.0 / number_of_slices));
        int range_end = (int)((i + 1) * (256.0 / number_of_slices));

        Mat res(img.size(), img.type());

        for(int i = 0; i < res.cols; i++){
            for(int j = 0; j < res.rows; j++){
                int r = img.at<Vec3b>(Point(i, j))[0];

                Vec3b& color = res.at<Vec3b>(Point(i, j));
                if (r > range_start && r <= range_end){
                    color[0] = 255;
                    color[1] = 255;
                    color[2] = 255;
                }else{
                    color[0] = 0;
                    color[1] = 0;
                    color[2] = 0;
                }
            }
        }

        results[i] = res;

    }

    return results;
}

Mat Processor::add_noise(Mat &img, int percent){
    Mat res(img.size(), img.type());
    img.copyTo(res);

    srand (time(NULL));

    for(int i = 0; i < res.cols; i++){
        for(int j = 0; j < res.rows; j++){
            int r = rand() % 100;

            if (r < percent){
                Vec3b& color = res.at<Vec3b>(Point(i, j));

                if (color[0] == color[1] && color[1] == color[2]){
                    r = rand() % 256;
                    //color[0] += r;
                    //color[1] += r;
                    //color[2] += r;
                    color[0] = color[1] = color[2] = r;
                }else{
                    //color[0] += rand() % 256;
                    //color[1] += rand() % 256;
                    //color[2] += rand() % 256;
                    color[0] = rand() % 256;
                    color[1] = rand() % 256;
                    color[2] = rand() % 256;
                }
            }
        }
    }

    return res;
}


/*
 * 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;
}


//============================================================================
// 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;
}


#include "stdafx.h"
#include "DepthFunction.h"

#include "mmcore/param/EnumParam.h"
#include "mmcore/param/IntParam.h"
#include "mmcore/param/StringParam.h"

#include <Eigen/LU>
#include "HD/HD.h"

using namespace megamol;
using namespace megamol::infovis;
using namespace Eigen;

enum DepthType { HALFSPACE_DEPTH = 0, FUNCTIONAL_DEPTH, MAHALANOBIS_DEPTH, SIMPLICAL_DEPTH };

Eigen::VectorXd DepthFunction::halfSpaceDepth(Eigen::MatrixXd dataMatrix) {
    Eigen::VectorXd result = Eigen::VectorXd(dataMatrix.rows());

    int nPoints = dataMatrix.rows();
    int nDims = dataMatrix.cols();

    double** x;
    x = new double*[nPoints];

    for (int i = 0; i < nPoints; i++) {
        x[i] = new double[nDims];
        for (int j = 0; j < nDims; j++) x[i][j] = dataMatrix(i, j);
    }

    double* z;
    z = new double[dataMatrix.cols()];

    for (int pIndex = 0; pIndex < nPoints; pIndex++) {
        for (int j = 0; j < dataMatrix.cols(); j++) {
            z[j] = dataMatrix(pIndex, j);
        }
        result(pIndex) = HalfspaceDepth::HD_Comb2(z, x, nPoints, nDims);
    }

    for (int k = 0; k < nPoints; k++) delete[] x[k];
    delete[] x;
    delete[] z;

    return result;
}

double megamol::infovis::DepthFunction::binomialCoeff(int n, int k) {
    if (k == 0 || k == n) return 1;

    double result = 1.0;

    for (int i = 0; i < k; i++) {
        result *= (n + 1.0 - i) / i;
    }

    return result;
}

Eigen::VectorXd DepthFunction::functionalDepth(
    Eigen::MatrixXd dataMatrix, int samplesCount, int samplesLength, unsigned int seed) {
    srand(seed);

    Eigen::VectorXd result = Eigen::VectorXd(dataMatrix.rows());
    double binom = megamol::infovis::DepthFunction::binomialCoeff(dataMatrix.rows(), samplesCount);

    // count the hits for every data point (= row in the data matrix)
    for (int row = 0; row < dataMatrix.rows(); row++) {
        Eigen::RowVectorXd dataPoint = dataMatrix.row(row);

        int hitsCount = 0;

        // generate random samples of length samplesLength
        for (int sampleIndex = 0; sampleIndex < samplesCount; sampleIndex++) {
            Eigen::VectorXd rndIndices =
                (Eigen::VectorXd::Random(samplesLength).array() + 1.0) / 2.0 * (dataMatrix.rows() - 1.0);

            bool minMaxFound = true;

            // for every property of the data point (= columns)
            for (int dim = 0; dim < dataMatrix.cols(); dim++) {

                double dataPointValue = dataPoint(dim);

                bool dim_maxFound = false;
                bool dim_minFound = false;

                // for every data point in the random set, check if its value
                // is <= or >= than the current data point value
                // (equiv. to: is the data point contained in a random set of points?)
                for (int rndIndexCounter = 0; rndIndexCounter < samplesLength; rndIndexCounter++) {
                    int rndIndex = rndIndices(rndIndexCounter);

                    if (dataMatrix(rndIndex, dim) <= dataPointValue) dim_minFound = true;
                    if (dataMatrix(rndIndex, dim) >= dataPointValue) dim_maxFound = true;

                    if (dim_minFound && dim_maxFound) break;
                }

                // if a dimension was found where neither a min nor a max was found,
                // this datapoint isn't in the random set/sample
                if (!(dim_minFound && dim_maxFound)) {
                    minMaxFound = false;
                    break;
                }
            }

            if (minMaxFound) hitsCount++;
        }

        // result = 1 / binom(n, j) * hitsCount
        // where n = samplesLength and j = samplesCount
        result(row) = (double)hitsCount / (double)binom;
    }

    return result;
}

bool insideSimplexCheck(Eigen::VectorXd p, Eigen::MatrixXd x) {
    Eigen::MatrixXd tmp(x.rows() + 1, x.cols());
    tmp << x.transpose(), Eigen::MatrixXd::Ones(x.cols(), 1);
    tmp.transposeInPlace();
    double nominator = tmp.determinant();

    tmp.block(0, 0, x.rows(), 1) = p;

    double sum = 0;

    for (int pointIndex = 0; pointIndex < x.cols(); pointIndex++) {

        if (pointIndex != 0)
            tmp.block(0, pointIndex, x.rows(), 1) = x.block(0, pointIndex - 1, x.rows(), 1); // update matrix

        double sign = ((pointIndex % 2) * (-2.0) + 1.0);     // pow(-1,pointIndex)
        double alpha = sign * tmp.determinant() / nominator; // barycentric coordinate

        sum += alpha;
        if (alpha > 1.0 || alpha < 0.0) return false;
    }

    return true;
}

Eigen::VectorXd DepthFunction::simplicalDepth(Eigen::MatrixXd dataMatrix, int samplesCount, unsigned int seed) {
    srand(seed);

    int nPoints = dataMatrix.rows();
    int nDims = dataMatrix.cols();

    Eigen::VectorXd result = Eigen::VectorXd(dataMatrix.rows());

    for (int row = 0; row < dataMatrix.rows(); row++) {
        Eigen::VectorXd dataPoint = dataMatrix.row(row).transpose();

        int hitsCount = 0;

        for (int sampleIndex = 0; sampleIndex < samplesCount; sampleIndex++) {
            Eigen::VectorXd rndIndices =
                (Eigen::VectorXd::Random(nDims + 1).array() + 1.0) / 2.0 * (dataMatrix.rows() - 1.0);

            Eigen::MatrixXd vertices(nDims, nDims + 1);

            for (int rndIndexCounter = 0; rndIndexCounter < nDims + 1; rndIndexCounter++) {
                int rndIndex = rndIndices(rndIndexCounter);

                vertices.block(0, rndIndexCounter, nDims, 1) = dataMatrix.row(rndIndex).transpose();
            }

            if (insideSimplexCheck(dataPoint, vertices)) hitsCount++;
        }

        result(row) = (double)hitsCount / (double)samplesCount;
    }

    return result;
}

Eigen::MatrixXd DepthFunction::mahalanobisDepth(Eigen::MatrixXd dataMatrix) {
    int nPoints = dataMatrix.rows();

    Eigen::MatrixXd mahalaDepth = Eigen::MatrixXd::Zero(nPoints, 1);
    Eigen::MatrixXd invS =
        (1.0 / (nPoints - 1.0) * dataMatrix.transpose() *
            (Eigen::MatrixXd::Identity(nPoints, nPoints) - 1.0 / nPoints * Eigen::MatrixXd::Ones(nPoints, nPoints)) *
            dataMatrix)
            .inverse(); // inverse covariance Matrix

    Eigen::MatrixXd mu = 1.0 / nPoints * dataMatrix.transpose() * Eigen::MatrixXd::Ones(nPoints, 1);
    Eigen::MatrixXd xi;

    //#pragma omp parallel for //doesn't work
    for (int index = 0; index < nPoints; index++) {
        xi = dataMatrix.row(index).transpose();
        mahalaDepth(index, 0) = 1.0 / (1.0 + ((xi - mu).transpose() * invS * (xi - mu))(0, 0));
    }
    return mahalaDepth;
}

std::vector<std::string> split(std::string str, std::string token) {
    std::vector<std::string> result;
    while (str.size()) {
        int index = str.find(token);
        if (index != std::string::npos) {
            result.push_back(str.substr(0, index));
            str = str.substr(index + token.size());
            if (str.size() == 0) result.push_back(str);
        } else {
            result.push_back(str);
            str = "";
        }
    }
    return result;
}

/// Parse string a with format columnIndex1;columnIndex2,columnIndex3,...;...
std::vector<std::vector<int>> parseColumnGroups(std::string& columnGroupString) {
    std::vector<std::vector<int>> columnGroups;
    auto groupStrings = split(columnGroupString, ";");
    for (auto groupString : groupStrings) {
        auto columnStrings = split(groupString, ",");
        std::vector<int> columns;
        for (auto columnString : columnStrings) {
            columns.push_back(std::stoi(columnString));
        }
        columnGroups.push_back(columns);
    }
    return columnGroups;
}

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

DepthFunction::DepthFunction(void)
    : megamol::core::Module()
    , dataOutSlot("dataOut", "Ouput")
    , dataInSlot("dataIn", "Input")
    , datahash(0)
    , dataInHash(0)
    , columnInfos()
    , columnGroupsSlot("columnGroups", "Semicolon-separated groups of comma-separated column indices to compute "
                                       "the data depth for. Defaults to one group, all columns.")
    , depthType("depthType", "The depth function to use for computing the depth statistics")
    , sampleCount("sampleCount", "The number of samples, that will be drawn (functional depth only)")
    , sampleLength("sampleLength", "The length of one sample (functional depth only)")
    , randomSeed("randomSeed", "The random seed (functional depth only)") {

    // Data input slot
    this->dataInSlot.SetCompatibleCall<megamol::stdplugin::datatools::table::TableDataCallDescription>();
    this->MakeSlotAvailable(&this->dataInSlot);

    // Data output slot
    this->dataOutSlot.SetCallback(megamol::stdplugin::datatools::table::TableDataCall::ClassName(),
        megamol::stdplugin::datatools::table::TableDataCall::FunctionName(0), &DepthFunction::getDataCallback);
    this->dataOutSlot.SetCallback(megamol::stdplugin::datatools::table::TableDataCall::ClassName(),
        megamol::stdplugin::datatools::table::TableDataCall::FunctionName(1), &DepthFunction::getHashCallback);
    this->MakeSlotAvailable(&this->dataOutSlot);

    // Parameters
    columnGroupsSlot << new ::megamol::core::param::StringParam("");
    this->MakeSlotAvailable(&columnGroupsSlot);

    auto types = new ::megamol::core::param::EnumParam(1);
    types->SetTypePair(HALFSPACE_DEPTH, "Halfspace Depth");
    types->SetTypePair(FUNCTIONAL_DEPTH, "Functional Band Depth");
    types->SetTypePair(MAHALANOBIS_DEPTH, "Mahalanobis Depth");
    types->SetTypePair(SIMPLICAL_DEPTH, "Simplical Depth");
    depthType << types;
    this->MakeSlotAvailable(&depthType);

    sampleCount << new ::megamol::core::param::IntParam(20000);
    this->MakeSlotAvailable(&sampleCount);

    sampleLength << new ::megamol::core::param::IntParam(10);
    this->MakeSlotAvailable(&sampleLength);

    randomSeed << new ::megamol::core::param::IntParam(1337);
    this->MakeSlotAvailable(&randomSeed);

    // Add all parameters to common-parameter-handling list
    params.push_back(&columnGroupsSlot);
    params.push_back(&depthType);
    params.push_back(&sampleCount);
    params.push_back(&sampleLength);
    params.push_back(&randomSeed);
}

DepthFunction::~DepthFunction(void) { this->Release(); }

bool DepthFunction::create(void) { return true; }

void DepthFunction::release(void) {}

bool DepthFunction::getDataCallback(core::Call& c) {
    try {
        megamol::stdplugin::datatools::table::TableDataCall* outCall =
            dynamic_cast<megamol::stdplugin::datatools::table::TableDataCall*>(&c);
        if (outCall == NULL) return false;

        megamol::stdplugin::datatools::table::TableDataCall* inCall =
            this->dataInSlot.CallAs<megamol::stdplugin::datatools::table::TableDataCall>();
        if (inCall == NULL) return false;

        inCall->SetFrameID(outCall->GetFrameID()); // inCall->Set? not outCall???
        if (!(*inCall)()) return false;

        bool finished = apply(inCall);
        if (finished == false) return false;

        outCall->SetFrameCount(inCall->GetFrameCount());
        outCall->SetDataHash(this->datahash);

        // set outCall
        if (this->columnInfos.size() != 0) {
            outCall->Set(this->columnInfos.size(), this->data.size() / this->columnInfos.size(),
                this->columnInfos.data(), this->data.data());
        } else {
            outCall->Set(0, 0, NULL, NULL);
        }

    } catch (...) {
        vislib::sys::Log::DefaultLog.WriteError(_T("Failed to execute %hs::processData\n"), ClassName());
        return false;
    }

    return true;
}

bool DepthFunction::getHashCallback(core::Call& c) {
    try {
        megamol::stdplugin::datatools::table::TableDataCall* outCall =
            dynamic_cast<megamol::stdplugin::datatools::table::TableDataCall*>(&c);
        if (outCall == NULL) return false;

        megamol::stdplugin::datatools::table::TableDataCall* inCall =
            this->dataInSlot.CallAs<megamol::stdplugin::datatools::table::TableDataCall>();
        if (inCall == NULL) return false;

        inCall->SetFrameID(outCall->GetFrameID());
        if (!(*inCall)(1)) return false;

        outCall->SetFrameCount(inCall->GetFrameCount());
        outCall->SetDataHash(this->datahash);
    } catch (...) {
        vislib::sys::Log::DefaultLog.WriteError(_T("Failed to execute %hs::getHashCallback\n"), ClassName());
        return false;
    }

    return true;
}

void megamol::infovis::DepthFunction::assertData(megamol::stdplugin::datatools::table::TableDataCall* inCall) {
    auto columnCountIn = inCall->GetColumnsCount();
    auto columnCountOut = inCall->GetColumnsCount();
    auto rowsCount = inCall->GetRowsCount();
    auto inData = inCall->GetData();

    // Load data in a Matrix
    inDataMat = Eigen::MatrixXd(rowsCount, columnCountOut);
    for (int row = 0; row < rowsCount; row++) {
        for (int col = 0; col < columnCountIn; col++) {
            inDataMat(row, col) = inData[row * columnCountIn + col];
        }
    }
}

bool megamol::infovis::DepthFunction::paramsIsDirty() {
    for (auto param : params) {
        if (param->IsDirty()) return true;
    }

    return false;
}

void megamol::infovis::DepthFunction::paramsResetDirty() {
    for (auto param : params) {
        param->ResetDirty();
    }
}

bool megamol::infovis::DepthFunction::apply(megamol::stdplugin::datatools::table::TableDataCall* inCall) {
    // Test if input data or parameters have changed.
    if (this->dataInHash == inCall->DataHash() && !paramsIsDirty()) {
        // Do nothing since parameters are the same as before
        return true;
    }

    assertData(inCall);

    std::string columnGroupsString(this->columnGroupsSlot.Param<core::param::StringParam>()->Value().PeekBuffer());
    std::vector<std::vector<int>> columnGroups = parseColumnGroups(columnGroupsString);

    // Default to one group, containing all columns, if empty.
    if (columnGroups.empty()) {
        std::vector<int> columns;
        for (int i = 0; i < inDataMat.cols(); ++i) {
            columns.push_back(i);
        }
        columnGroups.push_back(columns);
    }

    // Compute depths for all column groups.
    Eigen::MatrixXd groupDepths = Eigen::MatrixXd::Zero(inDataMat.rows(), columnGroups.size());
    for (int group = 0; group < columnGroups.size(); group++) {
        auto columns = columnGroups[group];

        // Copy the columns together.
        Eigen::MatrixXd columnGroupMat = Eigen::MatrixXd::Zero(inDataMat.rows(), columns.size());
        for (int i = 0; i < columns.size(); i++) {
            auto column = columns[i];
            columnGroupMat.block(0, i, inDataMat.rows(), 1) = inDataMat.block(0, column, inDataMat.rows(), 1);
        }

        // Compute depth function.
        switch (this->depthType.Param<core::param::EnumParam>()->Value()) {
        case HALFSPACE_DEPTH:
            groupDepths.block(0, group, inDataMat.rows(), 1) = halfSpaceDepth(columnGroupMat);
            break;
        case FUNCTIONAL_DEPTH:
            groupDepths.block(0, group, inDataMat.rows(), 1) =
                functionalDepth(columnGroupMat, this->sampleCount.Param<core::param::IntParam>()->Value(),
                    this->sampleLength.Param<core::param::IntParam>()->Value(),
                    this->randomSeed.Param<core::param::IntParam>()->Value());
            break;
        case MAHALANOBIS_DEPTH:
            groupDepths.block(0, group, inDataMat.rows(), 1) = mahalanobisDepth(columnGroupMat);
            break;
        case SIMPLICAL_DEPTH:
            groupDepths.block(0, group, inDataMat.rows(), 1) =
                simplicalDepth(inDataMat, this->sampleCount.Param<core::param::IntParam>()->Value(),
                    this->randomSeed.Param<core::param::IntParam>()->Value());
            break;
        }
    }

    // Generate new column infos.
    this->columnInfos.clear();
    this->columnInfos.resize(groupDepths.cols());

    for (int group = 0; group < groupDepths.cols(); group++) {
        auto depths = groupDepths.col(group);
        std::string name = "Depth";
        for (int i = 0; i < columnGroups[group].size(); ++i) {
            name += " " + inCall->GetColumnsInfos()[columnGroups[group][i]].Name();
        }
        this->columnInfos[group]
            .SetName(name)
            .SetType(megamol::stdplugin::datatools::table::TableDataCall::ColumnType::QUANTITATIVE)
            .SetMinimumValue(depths.minCoeff())
            .SetMaximumValue(depths.maxCoeff());
    }

    // Set matrix as output.
    this->data.clear();
    this->data.reserve(groupDepths.rows() * groupDepths.cols());
    for (size_t row = 0; row < groupDepths.rows(); row++) {
        for (int group = 0; group < groupDepths.cols(); group++) {
            this->data.push_back(groupDepths(row, group));
        }
    }

    // Update hash.
    this->dataInHash = inCall->DataHash();
    this->datahash++;

    // Reset parameters.
    paramsResetDirty();

    return true;
}