#include "test_knowledge_of_valid_opening.h"
#include <utils/utils.h>
#include "test_params.h"
#include "test_functions.h"
#include <hamming/knowledge_of_valid_opening/knowledge_of_valid_opening.h>
#include <NTL/vec_GF2.h>
#include <hamming/jain_commitment/jain_commitment.h>

static void generate_keys(
        ::hamming_metric::knowledge_of_valid_opening::revealed_values_t *revealed_values,
        ::hamming_metric::knowledge_of_valid_opening::public_key_t *public_key,
        ::hamming_metric::knowledge_of_valid_opening::private_key_t *private_key) {

    ::hamming_metric::knowledge_of_valid_opening::generate_revealed_values(
            revealed_values);

    ::hamming_metric::knowledge_of_valid_opening::generate_private_key(
            private_key);

    ::hamming_metric::knowledge_of_valid_opening::generate_public_key(
            public_key,
            private_key);

}

static void generate_commitments(
        ::hamming_metric::knowledge_of_valid_opening::responses_t *responses,
        ::hamming_metric::knowledge_of_valid_opening::commitments_t *commitments,
        ::hamming_metric::knowledge_of_valid_opening::revealed_values_t *revealed_values,
        ::hamming_metric::knowledge_of_valid_opening::public_key_t *public_key,
        ::hamming_metric::knowledge_of_valid_opening::private_key_t *private_key) {

    ::hamming_metric::knowledge_of_valid_opening::random_values_t random_values;

    ::hamming_metric::knowledge_of_valid_opening::generate_random_values(
            &random_values);

    ::hamming_metric::knowledge_of_valid_opening::generate_commitments_and_responses(
            responses,
            commitments,
            &random_values,
            revealed_values,
            public_key,
            private_key);
}

int test::hamming_metric::knowledge_of_valid_opening::test_verify_0() {

    ::hamming_metric::knowledge_of_valid_opening::responses_t responses;
    ::hamming_metric::knowledge_of_valid_opening::commitments_t commitments;
    ::hamming_metric::knowledge_of_valid_opening::public_key_t public_key;
    ::hamming_metric::knowledge_of_valid_opening::private_key_t private_key;
    ::hamming_metric::knowledge_of_valid_opening::revealed_values_t revealed_values;

    generate_keys(
            &revealed_values,
            &public_key,
            &private_key);

    generate_commitments(
            &responses,
            &commitments,
            &revealed_values,
            &public_key,
            &private_key);

    if (::hamming_metric::knowledge_of_valid_opening::verify_0(
            commitments.c0,
            commitments.r0,
            responses.t0,
            commitments.c1,
            commitments.r1,
            responses.t1,
            revealed_values.P,
            &public_key) != 0) {
        std::cout << "Error: test_verify_0" << std::endl;
        return 1;
    }

    return 0;
}

int test::hamming_metric::knowledge_of_valid_opening::test_verify_1() {
    ::hamming_metric::knowledge_of_valid_opening::responses_t responses;
    ::hamming_metric::knowledge_of_valid_opening::commitments_t commitments;
    ::hamming_metric::knowledge_of_valid_opening::public_key_t public_key;
    ::hamming_metric::knowledge_of_valid_opening::private_key_t private_key;
    ::hamming_metric::knowledge_of_valid_opening::revealed_values_t revealed_values;

    generate_keys(
            &revealed_values,
            &public_key,
            &private_key);

    generate_commitments(
            &responses,
            &commitments,
            &revealed_values,
            &public_key,
            &private_key);

    if (::hamming_metric::knowledge_of_valid_opening::verify_1(
            commitments.c0,
            commitments.r0,
            responses.t0,
            commitments.c2,
            commitments.r2,
            responses.t2,
            revealed_values.P,
            &public_key) != 0) {
        std::cout << "Error: test_verify_1" << std::endl;
        return 1;
    }

    return 0;
}

int test::hamming_metric::knowledge_of_valid_opening::test_verify_2() {
    ::hamming_metric::knowledge_of_valid_opening::responses_t responses;
    ::hamming_metric::knowledge_of_valid_opening::commitments_t commitments;
    ::hamming_metric::knowledge_of_valid_opening::public_key_t public_key;
    ::hamming_metric::knowledge_of_valid_opening::private_key_t private_key;
    ::hamming_metric::knowledge_of_valid_opening::revealed_values_t revealed_values;

    generate_keys(
            &revealed_values,
            &public_key,
            &private_key);

    generate_commitments(
            &responses,
            &commitments,
            &revealed_values,
            &public_key,
            &private_key);

    if (::hamming_metric::knowledge_of_valid_opening::verify_2(
            commitments.c1,
            commitments.r1,
            responses.t1,
            commitments.c2,
            commitments.r2,
            responses.t2,
            &public_key) != 0) {
        std::cout << "Error: test_verify_2" << std::endl;
        return 1;
    }

    return 0;
}

static void test_perf_random_matrices() {

    ::hamming_metric::knowledge_of_valid_opening::revealed_values_t revealed_values;

    clock_t start, lap, total_time;
    total_time = 0;
    int i;

    uint iterations = 0x100;
    int total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            ::hamming_metric::knowledge_of_valid_opening::generate_revealed_values(
                    &revealed_values);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    double total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    double operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_random_matrices",
            total_time_in_seconds,
            operation_per_second);
}

static void test_perf_response_encode_commit_0() {

    ::hamming_metric::knowledge_of_valid_opening::responses_t responses;
    ::hamming_metric::knowledge_of_valid_opening::commitments_t commitments;
    ::hamming_metric::knowledge_of_valid_opening::public_key_t public_key;
    ::hamming_metric::knowledge_of_valid_opening::private_key_t private_key;
    ::hamming_metric::knowledge_of_valid_opening::revealed_values_t revealed_values;
    ::hamming_metric::knowledge_of_valid_opening::random_values_t random_values;

    ::hamming_metric::knowledge_of_valid_opening::generate_private_key(
            &private_key);

    ::hamming_metric::knowledge_of_valid_opening::generate_public_key(
            &public_key,
            &private_key);

    ::hamming_metric::knowledge_of_valid_opening::generate_revealed_values(
            &revealed_values);

    ::hamming_metric::knowledge_of_valid_opening::generate_random_values(
            &random_values);

    clock_t start, lap, total_time;
    total_time = 0;
    int i;

    uint iterations = 0x100;
    int total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            ::hamming_metric::knowledge_of_valid_opening::generate_response_0(
                    responses.t0,
                    random_values.v,
                    public_key.A,
                    random_values.f);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    double total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    double operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_response_0",
            total_time_in_seconds,
            operation_per_second);

    total_time = 0;
    iterations = 0x100;
    total_iterations = iterations;
    NTL::vec_GF2 encoded;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            ::hamming_metric::knowledge_of_valid_opening::encode(
                    encoded,
                    responses.t0);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_encode_0",
            total_time_in_seconds,
            operation_per_second);

    total_time = 0;
    iterations = 0x100;
    total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            ::hamming_metric::knowledge_of_valid_opening::commit(
                    commitments.c0,
                    commitments.r0,
                    encoded,
                    public_key.A);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_commit_0",
            total_time_in_seconds,
            operation_per_second);
}

static void test_perf_response_encode_commit_1() {

    ::hamming_metric::knowledge_of_valid_opening::responses_t responses;
    ::hamming_metric::knowledge_of_valid_opening::commitments_t commitments;
    ::hamming_metric::knowledge_of_valid_opening::public_key_t public_key;
    ::hamming_metric::knowledge_of_valid_opening::private_key_t private_key;
    ::hamming_metric::knowledge_of_valid_opening::revealed_values_t revealed_values;
    ::hamming_metric::knowledge_of_valid_opening::random_values_t random_values;

    ::hamming_metric::knowledge_of_valid_opening::generate_private_key(
            &private_key);

    ::hamming_metric::knowledge_of_valid_opening::generate_public_key(
            &public_key,
            &private_key);

    ::hamming_metric::knowledge_of_valid_opening::generate_revealed_values(
            &revealed_values);

    ::hamming_metric::knowledge_of_valid_opening::generate_random_values(
            &random_values);

    clock_t start, lap, total_time;
    total_time = 0;
    int i;

    uint iterations = 0x100;
    int total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            ::hamming_metric::knowledge_of_valid_opening::generate_response_1(
                    responses.t1,
                    revealed_values.P,
                    random_values.f);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    double total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    double operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_response_1",
            total_time_in_seconds,
            operation_per_second);

    total_time = 0;
    iterations = 0x100;
    total_iterations = iterations;
    NTL::vec_GF2 encoded;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            ::hamming_metric::knowledge_of_valid_opening::encode(
                    encoded,
                    responses.t1);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_encode_1",
            total_time_in_seconds,
            operation_per_second);

    total_time = 0;
    iterations = 0x100;
    total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            ::hamming_metric::knowledge_of_valid_opening::commit(
                    commitments.c1,
                    commitments.r1,
                    encoded,
                    public_key.A);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_commit_1",
            total_time_in_seconds,
            operation_per_second);
}

static void test_perf_response_encode_commit_2() {

    ::hamming_metric::knowledge_of_valid_opening::responses_t responses;
    ::hamming_metric::knowledge_of_valid_opening::commitments_t commitments;
    ::hamming_metric::knowledge_of_valid_opening::public_key_t public_key;
    ::hamming_metric::knowledge_of_valid_opening::private_key_t private_key;
    ::hamming_metric::knowledge_of_valid_opening::revealed_values_t revealed_values;
    ::hamming_metric::knowledge_of_valid_opening::random_values_t random_values;

    ::hamming_metric::knowledge_of_valid_opening::generate_private_key(
            &private_key);

    ::hamming_metric::knowledge_of_valid_opening::generate_public_key(
            &public_key,
            &private_key);

    ::hamming_metric::knowledge_of_valid_opening::generate_revealed_values(
            &revealed_values);

    ::hamming_metric::knowledge_of_valid_opening::generate_random_values(
            &random_values);

    clock_t start, lap, total_time;
    total_time = 0;
    int i;

    uint iterations = 0x100;
    int total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            ::hamming_metric::knowledge_of_valid_opening::generate_response_2(
                    responses.t2,
                    revealed_values.P,
                    random_values.f,
                    private_key.e);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    double total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    double operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_response_2",
            total_time_in_seconds,
            operation_per_second);

    total_time = 0;
    iterations = 0x100;
    total_iterations = iterations;
    NTL::vec_GF2 encoded;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            ::hamming_metric::knowledge_of_valid_opening::encode(
                    encoded,
                    responses.t2);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_encode_2",
            total_time_in_seconds,
            operation_per_second);

    total_time = 0;
    iterations = 0x100;
    total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            ::hamming_metric::knowledge_of_valid_opening::commit(
                    commitments.c2,
                    commitments.r2,
                    encoded,
                    public_key.A);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_commit_2",
            total_time_in_seconds,
            operation_per_second);
}

static void test_perf_verify_0() {

    ::hamming_metric::knowledge_of_valid_opening::responses_t responses;
    ::hamming_metric::knowledge_of_valid_opening::commitments_t commitments;
    ::hamming_metric::knowledge_of_valid_opening::public_key_t public_key;
    ::hamming_metric::knowledge_of_valid_opening::private_key_t private_key;
    ::hamming_metric::knowledge_of_valid_opening::revealed_values_t revealed_values;
    ::hamming_metric::knowledge_of_valid_opening::random_values_t random_values;

    ::hamming_metric::knowledge_of_valid_opening::generate_private_key(
            &private_key);

    ::hamming_metric::knowledge_of_valid_opening::generate_public_key(
            &public_key,
            &private_key);

    ::hamming_metric::knowledge_of_valid_opening::generate_revealed_values(
            &revealed_values);

    ::hamming_metric::knowledge_of_valid_opening::generate_random_values(
            &random_values);

    ::hamming_metric::knowledge_of_valid_opening::generate_commitments_and_responses(
            &responses,
            &commitments,
            &random_values,
            &revealed_values,
            &public_key,
            &private_key);

    clock_t start, lap, total_time;
    total_time = 0;
    int i;

    uint iterations = 0x100;
    int total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            auto _t0 = utils::encode_binary_vector(responses.t0, JAIN_V);
            ::hamming_metric::commitment::verify(
                    commitments.c0,
                    public_key.A,
                    commitments.r0,
                    _t0);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    double total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    double operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_verify_0 verification 1",
            total_time_in_seconds,
            operation_per_second);

    total_time = 0;
    iterations = 0x100;
    total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            auto _t1 = utils::encode_binary_vector(responses.t1, JAIN_V);
            ::hamming_metric::commitment::verify(
                    commitments.c1,
                    public_key.A,
                    commitments.r1,
                    _t1);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_verify_0 verification 2",
            total_time_in_seconds,
            operation_per_second);

    total_time = 0;
    iterations = 0x100;
    total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            auto sum = responses.t0 + (NTL::inv(revealed_values.P) * responses.t1);
            NTL::vec_GF2 result;
            ::utils::solve_equation(
                    result,
                    public_key.A,
                    sum);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_verify_0 verification 3",
            total_time_in_seconds,
            operation_per_second);
}

static void test_perf_verify_1() {

    ::hamming_metric::knowledge_of_valid_opening::responses_t responses;
    ::hamming_metric::knowledge_of_valid_opening::commitments_t commitments;
    ::hamming_metric::knowledge_of_valid_opening::public_key_t public_key;
    ::hamming_metric::knowledge_of_valid_opening::private_key_t private_key;
    ::hamming_metric::knowledge_of_valid_opening::revealed_values_t revealed_values;
    ::hamming_metric::knowledge_of_valid_opening::random_values_t random_values;

    ::hamming_metric::knowledge_of_valid_opening::generate_private_key(
            &private_key);

    ::hamming_metric::knowledge_of_valid_opening::generate_public_key(
            &public_key,
            &private_key);

    ::hamming_metric::knowledge_of_valid_opening::generate_revealed_values(
            &revealed_values);

    ::hamming_metric::knowledge_of_valid_opening::generate_random_values(
            &random_values);

    ::hamming_metric::knowledge_of_valid_opening::generate_commitments_and_responses(
            &responses,
            &commitments,
            &random_values,
            &revealed_values,
            &public_key,
            &private_key);

    clock_t start, lap, total_time;
    total_time = 0;
    int i;

    uint iterations = 0x100;
    int total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            auto _t0 = utils::encode_binary_vector(responses.t0, JAIN_V);
            ::hamming_metric::commitment::verify(
                    commitments.c0,
                    public_key.A,
                    commitments.r0,
                    _t0);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    double total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    double operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_verify_1 verification 1",
            total_time_in_seconds,
            operation_per_second);

    total_time = 0;
    iterations = 0x100;
    total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            auto _t2 = utils::encode_binary_vector(responses.t2, JAIN_V);
            ::hamming_metric::commitment::verify(
                    commitments.c2,
                    public_key.A,
                    commitments.r2,
                    _t2);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_verify_1 verification 2",
            total_time_in_seconds,
            operation_per_second);

    total_time = 0;
    iterations = 0x100;
    total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            auto sum = responses.t0 + public_key.commitment + (NTL::inv(revealed_values.P) * responses.t2);
            NTL::vec_GF2 result;
            ::utils::solve_equation(
                    result,
                    public_key.A,
                    sum);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_verify_1 verification 3",
            total_time_in_seconds,
            operation_per_second);
}

static void test_perf_verify_2() {

    ::hamming_metric::knowledge_of_valid_opening::responses_t responses;
    ::hamming_metric::knowledge_of_valid_opening::commitments_t commitments;
    ::hamming_metric::knowledge_of_valid_opening::public_key_t public_key;
    ::hamming_metric::knowledge_of_valid_opening::private_key_t private_key;
    ::hamming_metric::knowledge_of_valid_opening::revealed_values_t revealed_values;
    ::hamming_metric::knowledge_of_valid_opening::random_values_t random_values;

    ::hamming_metric::knowledge_of_valid_opening::generate_private_key(
            &private_key);

    ::hamming_metric::knowledge_of_valid_opening::generate_public_key(
            &public_key,
            &private_key);

    ::hamming_metric::knowledge_of_valid_opening::generate_revealed_values(
            &revealed_values);

    ::hamming_metric::knowledge_of_valid_opening::generate_random_values(
            &random_values);

    ::hamming_metric::knowledge_of_valid_opening::generate_commitments_and_responses(
            &responses,
            &commitments,
            &random_values,
            &revealed_values,
            &public_key,
            &private_key);

    clock_t start, lap, total_time;
    total_time = 0;
    int i;

    uint iterations = 0x100;
    int total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            auto _t1 = utils::encode_binary_vector(responses.t1, JAIN_V);
            ::hamming_metric::commitment::verify(
                    commitments.c1,
                    public_key.A,
                    commitments.r1,
                    _t1);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    double total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    double operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_verify_2 verification 1",
            total_time_in_seconds,
            operation_per_second);

    total_time = 0;
    iterations = 0x100;
    total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            auto _t2 = utils::encode_binary_vector(responses.t2, JAIN_V);
            ::hamming_metric::commitment::verify(
                    commitments.c2,
                    public_key.A,
                    commitments.r2,
                    _t2);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_verify_2 verification 2",
            total_time_in_seconds,
            operation_per_second);

    total_time = 0;
    iterations = 0x100;
    total_iterations = iterations;

    do {
        start = clock();

        for (i = 0; i < iterations; i++) {
            NTL::weight(responses.t1 + responses.t2);
        }

        lap = clock() - start;
        total_time += lap;
        total_iterations += iterations;

        iterations <<= 1;

    } while (lap < TESTRUN_LEN * CLOCKS_PER_SEC);

    total_time_in_seconds = (double) total_time / (double) CLOCKS_PER_SEC;
    operation_per_second = total_time_in_seconds / total_iterations;

    test::test_functions::print_ops(
            "Hamming",
            "KVO test_perf_verify_2 verification 3",
            total_time_in_seconds,
            operation_per_second);
}

void test::hamming_metric::knowledge_of_valid_opening::test_perf() {
    test_perf_random_matrices();
    test_perf_response_encode_commit_0();
    test_perf_response_encode_commit_1();
    test_perf_response_encode_commit_2();
    test_perf_verify_0();
    test_perf_verify_1();
    test_perf_verify_2();
}


#include <iostream>
#include <array>
#include <cmath>
#include <boost/proto/proto.hpp>

using namespace std;
using namespace boost;

template< typename I >  struct placeholder        { using arity = I; };
template< typename T >  struct placeholder_arity  { using type = typename T::arity; };

namespace transforms {

    using namespace proto;

    template< typename X >
    struct max_fn : callable {
        using result_type = X;
        X operator()( X const & x , X const & y ) const { return std::max(x,y); }
    };

    template< typename X >
    struct min_fn : callable {
        using result_type = X;
        X operator()( X const & x , X const & y ) const { return std::min(x,y); }
    };

    template< typename X >
    struct zero_fn : callable {
        using result_type = X;
        X operator()() const { return {}; }
    };

    struct max_fn_int : max_fn<int> {};

    struct max_delay : or_
        <   when
            <   terminal<_>
            ,   call< zero_fn<int>() >
            >
        ,   when
            <   subscript< terminal<placeholder<mpl::int_<0>>> , terminal<int> >
            ,   _value(_right)      // TODO max_delay(right) + grammar: _[x]: x must be convertible to int !
            >
        ,   when
            <   unary_expr< _, max_delay >
            ,   max_delay(_child)
            >
        ,   when
            <   binary_expr< _, max_delay, max_delay >
            ,   call< min_fn<int>( max_delay(_left), max_delay(_right) ) >
            >
        >
    {};

    struct static_max_delay : or_
        <   when
            <   terminal<_>
            ,   mpl::int_<0>()
            >
        ,   when
            <   subscript< terminal<placeholder<mpl::int_<0>>> , terminal<placeholder<_>> >  // TODO prohibit _ (plac<0>) as arg
            ,   placeholder_arity< _value(_right) >()
            >
        ,   when
            <   unary_expr< _ , static_max_delay>
            ,   static_max_delay( _child )
            >
        ,   when
            <   binary_expr< _ , static_max_delay, static_max_delay >
            ,   mpl::max< static_max_delay(_left), static_max_delay(_right) >()
            >
        >
    {};

}

using transforms::max_delay;
using transforms::static_max_delay;

template< typename Expr >
using static_max_delay_int = typename boost::result_of<static_max_delay(Expr)>::type;

const proto::terminal< placeholder< mpl::int_<0> >>::type   _   = {{}};
const proto::terminal< placeholder< mpl::int_<1> >>::type   _1  = {{}};
const proto::terminal< placeholder< mpl::int_<2> >>::type   _2  = {{}};
const proto::terminal< placeholder< mpl::int_<3> >>::type   _3  = {{}};
const proto::terminal< placeholder< mpl::int_<4> >>::type   _4  = {{}};
const proto::terminal< placeholder< mpl::int_<1337> >>::type   _1337  = {{}};

int main()
{
    auto delay_times_1  =  _[_1] + 1.5 * _1  +  12 * _[-3];
    auto delay_times_2  =  _[-1337] + 1.5 * _1  +  12 * _[-3] + _[_3] + _[_4];
    auto static_delay_1 =  _[_3] + _[_1337];

    //proto::display_expr( delay_times_2 );

    //std::array< float, max_delay{}(delay_times_2) >

    //std::cout << max_delay{}(delay_times_2) << std::endl;
    std::cout << static_max_delay{}(delay_times_2) << std::endl;
    std::cout << static_max_delay{}(static_delay_1) << std::endl;

    std::cout << static_max_delay_int<decltype(static_delay_1)>::value << std::endl;

    using state_t = std::array< float, static_max_delay_int<decltype(static_delay_1)>::value >;
    state_t s;
}


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

  @copyright 2012-2015 NumScale SAS

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

#include <boost/simd/config.hpp>
#include <boost/simd/detail/brigand.hpp>
#include <boost/simd/detail/dispatch.hpp>
#include <boost/simd/detail/constant_traits.hpp>
#include <boost/dispatch/function/make_callable.hpp>
#include <boost/dispatch/hierarchy/functions.hpp>
#include <boost/dispatch/as.hpp>

namespace boost { namespace simd
{
  namespace tag
  {
    struct expx2c1_ : boost::dispatch::constant_value_<expx2c1_>
    {
      BOOST_DISPATCH_MAKE_CALLABLE(ext,expx2c1_,boost::dispatch::constant_value_<expx2c1_>);
      BOOST_SIMD_REGISTER_CONSTANT(0, 0x42000000UL, 0x4060000000000000ull);
    };
  }

  namespace ext
  {
    BOOST_DISPATCH_FUNCTION_DECLARATION(tag,expx2c1_);
  }

  namespace detail
  {
    BOOST_DISPATCH_CALLABLE_DEFINITION(tag::expx2c1_,expx2c1);
  }

  template<typename T> BOOST_FORCEINLINE auto Expx2c1()
  BOOST_NOEXCEPT_DECLTYPE(detail::expx2c1( boost::dispatch::as_<T>{}))
  {
    return detail::expx2c1( boost::dispatch::as_<T>{} );
  }
} }

#endif


////////////////////////////////////////////////////////////////////////////////
// Name:      eval.cpp
// Purpose:   Implementation of class wex::evaluator
// Author:    Anton van Wezenbeek
// Copyright: (c) 2021-2022 Anton van Wezenbeek
////////////////////////////////////////////////////////////////////////////////

#include <list>
#include <numeric>
#include <regex>

#include <boost/config/warning_disable.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>

#include <wex/factory/stc.h>
#include <wex/vi/ex-stream.h>
#include <wex/vi/ex.h>

#include "eval.h"
#include "util.h"

namespace x3 = boost::spirit::x3;

namespace wex
{
namespace ast
{
struct ex;
struct nil
{
};
struct program;
struct signed_;

struct operand
  : x3::variant<
      nil,
      unsigned int,
      x3::forward_ast<ex>,
      x3::forward_ast<signed_>,
      x3::forward_ast<program>>
{
  using base_type::base_type;
  using base_type::operator=;
};

struct ex
{
  std::string token;
};

struct signed_
{
  char    sign;
  operand operand_;
};

struct operation
{
  char    operator_;
  operand operand_;
};

struct program
{
  operand              first;
  std::list<operation> rest;
};

struct eval
{
  explicit eval(const wex::ex* ex)
    : m_ex(ex)
  {
    ;
  };

  auto operator()(nil) const
  {
    BOOST_ASSERT(0);
    return 0;
  }

  auto operator()(ex const& e) const
  {
    if (e.token == ".")
    {
      return m_ex->get_command().get_stc()->get_current_line() + 1;
    }
    else if (e.token == "$")
    {
      if (m_ex->visual() == wex::ex::EX)
      {
        return m_ex->ex_stream()->get_line_count_request();
      }
      else
      {
        return m_ex->get_command().get_stc()->get_line_count_request();
      }
    }
    else if (e.token[0] == '\'' && e.token.size() == 2)
    {
      const int line = m_ex->marker_line(e.token[1]);
      if (line == -1)
        throw std::overflow_error("no marker");
      return line + 1;
    }

    BOOST_ASSERT(0);
    return 0;
  }

  auto operator()(int n) const { return n; }

  int operator()(int lhs, operation const& x) const
  {
    switch (const auto rhs = boost::apply_visitor(*this, x.operand_);
            x.operator_)
    {
      case '+':
        return lhs + rhs;
      case '-':
        return lhs - rhs;
      case '*':
        return lhs * rhs;
      case '%':
        return lhs % rhs;
      case '&':
        return lhs & rhs;
      case '|':
        return lhs | rhs;
      case '^':
        return lhs ^ rhs;
      case '<':
        return lhs << rhs;
      case '>':
        return lhs >> rhs;
      case '/':
        if (rhs == 0)
          throw std::overflow_error("divide by zero");
        return lhs / rhs;
    }
    BOOST_ASSERT(0);
    return 0;
  }

  int operator()(signed_ const& x) const
  {
    int rhs = boost::apply_visitor(*this, x.operand_);
    switch (x.sign)
    {
      case '~':
        return ~rhs;
      case '-':
        return -rhs;
      case '+':
        return +rhs;
    }
    BOOST_ASSERT(0);
    return 0;
  }

  int operator()(program const& x) const
  {
    return std::accumulate(
      x.rest.begin(),
      x.rest.end(),
      boost::apply_visitor(*this, x.first),
      *this);
  }

  const wex::ex* m_ex;
};
} // namespace ast

namespace calculator_grammar
{
using x3::alnum;
using x3::char_;
using x3::string;
using x3::uint_;

x3::rule<class expression, ast::program> const expression("expression");
x3::rule<class term, ast::program> const       term("term");
x3::rule<class factor, ast::operand> const     factor("factor");
x3::rule<class identifier, ast::ex> const      identifier("identifier");

auto const expression_def = term >>
                            *((char_('+') >> term) | (char_('-') >> term));

auto const term_def = factor >>
                      *((char_('*') >> factor) | (char_('/') >> factor) |
                        (char_('%') >> factor) | (char_('&') >> factor) |
                        (char_('|') >> factor) | (char_('^') >> factor) |
                        (char_('<') >> factor) | (char_('>') >> factor));

auto const factor_def = uint_ | identifier | '(' >> expression >> ')' |
                        (char_('~') >> factor) | (char_('-') >> factor) |
                        (char_('+') >> factor);

auto const identifier_def = string(".") | string("$") |
                            (string("'") >> (alnum | char_('<') | char_('>')));

BOOST_SPIRIT_DEFINE(expression, term, factor, identifier);

auto calculator = expression;
} // namespace calculator_grammar

using calculator_grammar::calculator;

class evaluator_imp
{
public:
  std::tuple<int, std::string> eval(const wex::ex* ex, const std::string& text)
  {
    try
    {
      auto&        calc = wex::calculator;
      ast::program program;
      ast::eval    eval(ex);

      auto                                 iter = text.begin();
      boost::spirit::x3::ascii::space_type space;
      const bool r = phrase_parse(iter, text.end(), calc, space, program);

      if (r && iter == text.end())
      {
        return {eval(program), std::string()};
      }
      else
      {
        return {0, std::string(iter, text.end())};
      }
    }
    catch (std::exception& e)
    {
      return {0, e.what()};
    }
  };
};
}; // namespace wex

BOOST_FUSION_ADAPT_STRUCT(wex::ast::ex, (std::string, token))
BOOST_FUSION_ADAPT_STRUCT(wex::ast::operation, (char, operator_), operand_)
BOOST_FUSION_ADAPT_STRUCT(wex::ast::program, first, rest)
BOOST_FUSION_ADAPT_STRUCT(wex::ast::signed_, (char, sign), operand_)

wex::evaluator::evaluator()
  : m_eval(new wex::evaluator_imp)
{
}

wex::evaluator::~evaluator()
{
  delete m_eval;
}

std::tuple<int, std::string>
wex::evaluator::eval(const wex::ex* ex, const std::string& text) const
{
  std::string expanded(text);
  marker_and_register_expansion(ex, expanded);
  return m_eval->eval(ex, expanded);
}


// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2015 Alec Jacobson <alecjacobson@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.
#include "planarize_quad_mesh.h"
#include "quad_planarity.h"
#include <Eigen/Sparse>
#include <Eigen/Eigenvalues>
#include <iostream>

namespace igl
{
  template <typename DerivedV, typename DerivedF>
  class PlanarizerShapeUp
  {
  protected:
    // number of faces, number of vertices
    long numV, numF;
    // references to the input faces and vertices
    const Eigen::MatrixBase<DerivedV> &Vin;
    const Eigen::MatrixBase<DerivedF> &Fin;

    // vector consisting of the vertex positions stacked: [x;y;z;x;y;z...]
    // vector consisting of a weight per face (currently all set to 1)
    // vector consisting of the projected face vertices (might be different for the same vertex belonging to different faces)
    Eigen::Matrix<typename DerivedV::Scalar, Eigen::Dynamic, 1> Vv, weightsSqrt, P;

    // Matrices as in the paper
    // Q: lhs matrix
    // Ni: matrix that subtracts the mean of a face from the 4 vertices of a face
    Eigen::SparseMatrix<typename DerivedV::Scalar > Q, Ni;
    Eigen::SimplicialLDLT<Eigen::SparseMatrix<typename DerivedV::Scalar > > solver;

    int maxIter;
    double threshold;
    const int ni = 4;

    // Matrix assemblers
    inline void assembleQ();
    inline void assembleP();
    inline void assembleNi();

    // Selects out of Vv the 4 vertices belonging to face fi
    inline void assembleSelector(int fi,
                          Eigen::SparseMatrix<typename DerivedV::Scalar > &S);

  public:
    // Init - assemble stacked vector and lhs matrix, factorize
    inline PlanarizerShapeUp(const Eigen::MatrixBase<DerivedV> &V_,
                             const Eigen::MatrixBase<DerivedF> &F_,
                             const int maxIter_,
                             const double &threshold_);
    // Planarization - output to Vout
    inline void planarize(Eigen::PlainObjectBase<DerivedV> &Vout);
  };
}

//Implementation

template <typename DerivedV, typename DerivedF>
inline igl::PlanarizerShapeUp<DerivedV, DerivedF>::PlanarizerShapeUp(const Eigen::MatrixBase<DerivedV> &V_,
                                                                     const Eigen::MatrixBase<DerivedF> &F_,
                                                                     const int maxIter_,
                                                                     const double &threshold_):
numV(V_.rows()),
numF(F_.rows()),
Vin(V_),
Fin(F_),
weightsSqrt(Eigen::Matrix<typename DerivedV::Scalar, Eigen::Dynamic, 1>::Ones(numF,1)),
maxIter(maxIter_),
threshold(threshold_)
{
  // assemble stacked vertex position vector
  Vv.setZero(3*numV,1);
  for (int i =0;i<numV;++i)
    Vv.segment(3*i,3) = Vin.row(i);
  // assemble and factorize lhs matrix
  assembleQ();
};

template <typename DerivedV, typename DerivedF>
inline void igl::PlanarizerShapeUp<DerivedV, DerivedF>::assembleQ()
{
  std::vector<Eigen::Triplet<typename DerivedV::Scalar> > tripletList;

  // assemble the Ni matrix
  assembleNi();

  for (int fi = 0; fi< numF; fi++)
  {
    Eigen::SparseMatrix<typename DerivedV::Scalar > Sfi;
    assembleSelector(fi, Sfi);

    // the final matrix per face
    Eigen::SparseMatrix<typename DerivedV::Scalar > Qi = weightsSqrt(fi)*Ni*Sfi;
    // put it in the correct block of Q
    // todo: this can be made faster by omitting the selector matrix
    for (int k=0; k<Qi.outerSize(); ++k)
      for (typename Eigen::SparseMatrix<typename DerivedV::Scalar >::InnerIterator it(Qi,k); it; ++it)
      {
        typename DerivedV::Scalar val = it.value();
        int row = it.row();
        int col = it.col();
        tripletList.push_back(Eigen::Triplet<typename DerivedV::Scalar>(row+3*ni*fi,col,val));
      }
  }

  Q.resize(3*ni*numF,3*numV);
  Q.setFromTriplets(tripletList.begin(), tripletList.end());
  // the actual lhs matrix is Q'*Q
  // prefactor that matrix
  solver.compute(Q.transpose()*Q);
  if(solver.info()!=Eigen::Success)
  {
    std::cerr << "Cholesky failed - PlanarizerShapeUp.cpp" << std::endl;
    assert(0);
  }
}

template <typename DerivedV, typename DerivedF>
inline void igl::PlanarizerShapeUp<DerivedV, DerivedF>::assembleNi()
{
  std::vector<Eigen::Triplet<typename DerivedV::Scalar>> tripletList;
  for (int ii = 0; ii< ni; ii++)
  {
    for (int jj = 0; jj< ni; jj++)
    {
      tripletList.push_back(Eigen::Triplet<typename DerivedV::Scalar>(3*ii+0,3*jj+0,-1./ni));
      tripletList.push_back(Eigen::Triplet<typename DerivedV::Scalar>(3*ii+1,3*jj+1,-1./ni));
      tripletList.push_back(Eigen::Triplet<typename DerivedV::Scalar>(3*ii+2,3*jj+2,-1./ni));
    }
    tripletList.push_back(Eigen::Triplet<typename DerivedV::Scalar>(3*ii+0,3*ii+0,1.));
    tripletList.push_back(Eigen::Triplet<typename DerivedV::Scalar>(3*ii+1,3*ii+1,1.));
    tripletList.push_back(Eigen::Triplet<typename DerivedV::Scalar>(3*ii+2,3*ii+2,1.));
  }
  Ni.resize(3*ni,3*ni);
  Ni.setFromTriplets(tripletList.begin(), tripletList.end());
}

//assumes V stacked [x;y;z;x;y;z...];
template <typename DerivedV, typename DerivedF>
inline void igl::PlanarizerShapeUp<DerivedV, DerivedF>::assembleSelector(int fi,
                                                                            Eigen::SparseMatrix<typename DerivedV::Scalar > &S)
{

  std::vector<Eigen::Triplet<typename DerivedV::Scalar>> tripletList;
  for (int fvi = 0; fvi< ni; fvi++)
  {
    int vi = Fin(fi,fvi);
    tripletList.push_back(Eigen::Triplet<typename DerivedV::Scalar>(3*fvi+0,3*vi+0,1.));
    tripletList.push_back(Eigen::Triplet<typename DerivedV::Scalar>(3*fvi+1,3*vi+1,1.));
    tripletList.push_back(Eigen::Triplet<typename DerivedV::Scalar>(3*fvi+2,3*vi+2,1.));
  }

  S.resize(3*ni,3*numV);
  S.setFromTriplets(tripletList.begin(), tripletList.end());

}

//project all faces to their closest planar face
template <typename DerivedV, typename DerivedF>
inline void igl::PlanarizerShapeUp<DerivedV, DerivedF>::assembleP()
{
  P.setZero(3*ni*numF);
  for (int fi = 0; fi< numF; fi++)
  {
    // todo: this can be made faster by omitting the selector matrix
    Eigen::SparseMatrix<typename DerivedV::Scalar > Sfi;
    assembleSelector(fi, Sfi);
    Eigen::SparseMatrix<typename DerivedV::Scalar > NSi = Ni*Sfi;

    Eigen::Matrix<typename DerivedV::Scalar, Eigen::Dynamic, 1> Vi = NSi*Vv;
    Eigen::Matrix<typename DerivedV::Scalar, Eigen::Dynamic, Eigen::Dynamic> CC(3,ni);
    for (int i = 0; i <ni; ++i)
      CC.col(i) = Vi.segment(3*i, 3);
    Eigen::Matrix<typename DerivedV::Scalar, 3, 3> C = CC*CC.transpose();

    // Alec: Doesn't compile
    Eigen::EigenSolver<Eigen::Matrix<typename DerivedV::Scalar, 3, 3>> es(C);
    // the real() is for compilation purposes
    Eigen::Matrix<typename DerivedV::Scalar, 3, 1> lambda = es.eigenvalues().real();
    Eigen::Matrix<typename DerivedV::Scalar, 3, 3> U = es.eigenvectors().real();
    int min_i;
    lambda.cwiseAbs().minCoeff(&min_i);
    U.col(min_i).setZero();
    Eigen::Matrix<typename DerivedV::Scalar, Eigen::Dynamic, Eigen::Dynamic> PP = U*U.transpose()*CC;
    for (int i = 0; i <ni; ++i)
     P.segment(3*ni*fi+3*i, 3) =  weightsSqrt[fi]*PP.col(i);

  }
}

template <typename DerivedV, typename DerivedF>
inline void igl::PlanarizerShapeUp<DerivedV, DerivedF>::planarize(Eigen::PlainObjectBase<DerivedV> &Vout)
{
  Eigen::Matrix<typename DerivedV::Scalar, Eigen::Dynamic, 1> planarity;
  Vout = Vin;

  for (int iter =0; iter<maxIter; ++iter)
  {
    igl::quad_planarity(Vout, Fin, planarity);
    typename DerivedV::Scalar nonPlanarity = planarity.cwiseAbs().maxCoeff();
    //std::cerr<<"iter #"<<iter<<": max non-planarity: "<<nonPlanarity<<std::endl;
    if (nonPlanarity<threshold)
      break;
    assembleP();
    Vv = solver.solve(Q.transpose()*P);
    if(solver.info()!=Eigen::Success)
    {
      std::cerr << "Linear solve failed - PlanarizerShapeUp.cpp" << std::endl;
      assert(0);
    }
    for (int i =0;i<numV;++i)
      Vout.row(i) << Vv.segment(3*i,3).transpose();
  }
  // set the mean of Vout to the mean of Vin
  Eigen::Matrix<typename DerivedV::Scalar, 1, 3> oldMean, newMean;
  oldMean = Vin.colwise().mean();
  newMean = Vout.colwise().mean();
  Vout.rowwise() += (oldMean - newMean);

};

template <typename DerivedV, typename DerivedF>
IGL_INLINE void igl::planarize_quad_mesh(const Eigen::MatrixBase<DerivedV> &Vin,
                                    const Eigen::MatrixBase<DerivedF> &Fin,
                                    const int maxIter,
                                    const double &threshold,
                                    Eigen::PlainObjectBase<DerivedV> &Vout)
{
  PlanarizerShapeUp<DerivedV, DerivedF> planarizer(Vin, Fin, maxIter, threshold);
  planarizer.planarize(Vout);
}

#ifdef IGL_STATIC_LIBRARY
// Explicit template instantiation
template void igl::planarize_quad_mesh<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(Eigen::MatrixBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::MatrixBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, int, double const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
#endif


// Distributed under the MIT License.
// See LICENSE.txt for details.

#include "NumericalAlgorithms/Interpolation/BarycentricRationalSpanInterpolator.hpp"

#include <boost/math/interpolators/barycentric_rational.hpp>
#include <complex>
#include <cstddef>

#include "ErrorHandling/Assert.hpp"
#include "Utilities/Gsl.hpp"

namespace intrp {

BarycentricRationalSpanInterpolator::BarycentricRationalSpanInterpolator(
    size_t min_order, size_t max_order) noexcept
    : min_order_{min_order}, max_order_{max_order} {
  ASSERT(min_order <= max_order,
         "The minimum order for the Barycentric rational interpolator must be "
         "less than the maximum order.");
}

double BarycentricRationalSpanInterpolator::interpolate(
    const gsl::span<const double>& source_points,
    const gsl::span<const double>& values, const double target_point) const
    noexcept {
  if (UNLIKELY(source_points.size() < min_order_ + 1)) {
    ERROR("provided independent values for interpolation too small.");
  }
  boost::math::barycentric_rational<double> interpolant(
      source_points.data(), values.data(), source_points.size(),
      std::min(source_points.size() - 1, max_order_));
  return interpolant(target_point);
}

/// \cond
PUP::able::PUP_ID intrp::BarycentricRationalSpanInterpolator::my_PUP_ID = 0;
/// \endcond
}  // namespace intrp


//---------------------------------------------------------------------------//
// Copyright (c) 2021 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2021 Ilias Khairullin <ilias@nil.foundation>
//
// MIT License
//
// 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.
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_PUBKEY_ELGAMAL_VERIFIABLE_HPP
#define CRYPTO3_PUBKEY_ELGAMAL_VERIFIABLE_HPP

#include <tuple>
#include <type_traits>
#include <iterator>
#include <vector>

#include <boost/assert.hpp>

#include <nil/crypto3/algebra/algorithms/pair.hpp>

#include <nil/crypto3/zk/algorithms/prove.hpp>
#include <nil/crypto3/zk/algorithms/verify.hpp>

#include <nil/crypto3/zk/snark/systems/ppzksnark/r1cs_gg_ppzksnark.hpp>

#include <nil/crypto3/pubkey/keys/private_key.hpp>
#include <nil/crypto3/pubkey/keys/verification_key.hpp>
#include <nil/crypto3/pubkey/operations/generate_keypair_op.hpp>
#include <nil/crypto3/pubkey/operations/encrypt_op.hpp>
#include <nil/crypto3/pubkey/operations/decrypt_op.hpp>
#include <nil/crypto3/pubkey/operations/verify_encryption_op.hpp>
#include <nil/crypto3/pubkey/operations/verify_decryption_op.hpp>
#include <nil/crypto3/pubkey/operations/rerandomize_op.hpp>

namespace nil {
    namespace crypto3 {
        namespace pubkey {
            template<typename Curve, std::size_t BlockBits = 4>
            class elgamal_verifiable {
                typedef elgamal_verifiable<Curve, BlockBits> self_type;
                static_assert(BlockBits > 0);

            public:
                typedef Curve curve_type;
                static constexpr std::size_t block_bits = BlockBits;

                typedef public_key<self_type> public_key_type;
                typedef private_key<self_type> private_key_type;
                typedef verification_key<self_type> verification_key_type;
                typedef std::tuple<public_key_type, private_key_type, verification_key_type> keypair_type;

                typedef zk::snark::r1cs_gg_ppzksnark<
                    Curve, zk::snark::r1cs_gg_ppzksnark_generator<Curve, zk::snark::proving_mode::encrypted_input>,
                    zk::snark::r1cs_gg_ppzksnark_prover<Curve, zk::snark::proving_mode::encrypted_input>,
                    zk::snark::r1cs_gg_ppzksnark_verifier_strong_input_consistency<
                        Curve, zk::snark::proving_mode::encrypted_input>,
                    zk::snark::proving_mode::encrypted_input>
                    proof_system_type;

                typedef std::pair<std::vector<typename Curve::template g1_type<>::value_type>,
                                  typename proof_system_type::proof_type>
                    cipher_type;
                typedef std::pair<std::vector<typename Curve::scalar_field_type::value_type>,
                                  typename Curve::template g1_type<>::value_type>
                    decipher_type;
            };

            template<typename Curve, std::size_t BlockBits>
            struct verification_key<elgamal_verifiable<Curve, BlockBits>> {
                typedef elgamal_verifiable<Curve, BlockBits> scheme_type;
                typedef typename Curve::template g2_type<> g2_type;

                friend class decrypt_op<scheme_type>;
                friend class verify_decryption_op<scheme_type>;

                verification_key() = default;
                verification_key(const typename g2_type::value_type &rho_g2,
                                 const std::vector<typename g2_type::value_type> &rho_sv_g2,
                                 const std::vector<typename g2_type::value_type> &rho_rhov_g2) :
                    rho_g2(rho_g2),
                    rho_sv_g2(rho_sv_g2), rho_rhov_g2(rho_rhov_g2) {
                }
                verification_key(typename g2_type::value_type &&rho_g2,
                                 std::vector<typename g2_type::value_type> &&rho_sv_g2,
                                 std::vector<typename g2_type::value_type> &&rho_rhov_g2) :
                    rho_g2(std::move(rho_g2)),
                    rho_sv_g2(std::move(rho_sv_g2)), rho_rhov_g2(std::move(rho_rhov_g2)) {
                }

                // private:
                typename g2_type::value_type rho_g2;
                std::vector<typename g2_type::value_type> rho_sv_g2;
                std::vector<typename g2_type::value_type> rho_rhov_g2;
            };

            template<typename Curve, std::size_t BlockBits>
            struct public_key<elgamal_verifiable<Curve, BlockBits>> {
                typedef elgamal_verifiable<Curve, BlockBits> scheme_type;

                typedef typename Curve::template g1_type<> g1_type;
                typedef typename Curve::template g2_type<> g2_type;

                public_key() = default;
                public_key &operator=(const public_key &other) = default;
                public_key(const public_key &other) = default;
                public_key(public_key &&other) = default;
                public_key(const typename g1_type::value_type &delta_g1,
                           const std::vector<typename g1_type::value_type> &delta_s_g1,
                           const std::vector<typename g1_type::value_type> &t_g1,
                           const std::vector<typename g2_type::value_type> &t_g2,
                           const typename g1_type::value_type &delta_sum_s_g1,
                           const typename g1_type::value_type &gamma_inverse_sum_s_g1) :
                    delta_g1(delta_g1),
                    delta_s_g1(delta_s_g1), t_g1(t_g1), t_g2(t_g2), delta_sum_s_g1(delta_sum_s_g1),
                    gamma_inverse_sum_s_g1(gamma_inverse_sum_s_g1) {
                }
                public_key(typename g1_type::value_type &&delta_g1,
                           std::vector<typename g1_type::value_type> &&delta_s_g1,
                           std::vector<typename g1_type::value_type> &&t_g1,
                           std::vector<typename g2_type::value_type> &&t_g2,
                           typename g1_type::value_type &&delta_sum_s_g1,
                           typename g1_type::value_type &&gamma_inverse_sum_s_g1) :
                    delta_g1(std::move(delta_g1)),
                    delta_s_g1(std::move(delta_s_g1)), t_g1(std::move(t_g1)), t_g2(std::move(t_g2)),
                    delta_sum_s_g1(std::move(delta_sum_s_g1)),
                    gamma_inverse_sum_s_g1(std::move(gamma_inverse_sum_s_g1)) {
                }

                bool operator==(const public_key &other) const {
                    return delta_g1 == other.delta_g1 && delta_s_g1 == other.delta_s_g1 && t_g1 == other.t_g1 &&
                           t_g2 == other.t_g2 && delta_sum_s_g1 == other.delta_sum_s_g1 &&
                           gamma_inverse_sum_s_g1 == other.gamma_inverse_sum_s_g1;
                }

                typename g1_type::value_type delta_g1;
                std::vector<typename g1_type::value_type> delta_s_g1;
                std::vector<typename g1_type::value_type> t_g1;
                std::vector<typename g2_type::value_type> t_g2;
                typename g1_type::value_type delta_sum_s_g1;
                typename g1_type::value_type gamma_inverse_sum_s_g1;
            };

            template<typename Curve, std::size_t BlockBits>
            struct private_key<elgamal_verifiable<Curve, BlockBits>> {
                typedef elgamal_verifiable<Curve, BlockBits> scheme_type;
                typedef typename Curve::scalar_field_type scalar_field_type;

                friend class decrypt_op<scheme_type>;

                private_key() = default;
                private_key(const typename scalar_field_type::value_type &rho) : rho(rho) {
                }

                // private:
                typename scalar_field_type::value_type rho;
            };

            template<typename Curve, std::size_t BlockBits>
            struct generate_keypair_op<elgamal_verifiable<Curve, BlockBits>> {
                typedef elgamal_verifiable<Curve, BlockBits> scheme_type;
                typedef typename scheme_type::proof_system_type proof_system_type;

                typedef typename scheme_type::public_key_type public_key_type;
                typedef typename scheme_type::private_key_type private_key_type;
                typedef typename scheme_type::verification_key_type verification_key_type;
                typedef typename scheme_type::keypair_type keypair_type;

                typedef typename Curve::scalar_field_type scalar_field_type;
                typedef typename Curve::template g1_type<> g1_type;
                typedef typename Curve::template g2_type<> g2_type;

                struct init_params_type {
                    const typename proof_system_type::keypair_type &gg_keypair;
                    std::size_t msg_size;
                };
                struct internal_accumulator_type {
                    const typename proof_system_type::keypair_type &gg_keypair;
                    std::size_t msg_size;
                    std::vector<typename scalar_field_type::value_type> rnd;
                };
                typedef keypair_type result_type;

                static inline internal_accumulator_type init_accumulator(const init_params_type &init_params) {
                    // TODO: check
                    BOOST_ASSERT_MSG(init_params.gg_keypair.second.gamma_ABC_g1.rest.size() > init_params.msg_size,
                                     "Array of gammas in vk should be longer than the message.");
                    return {init_params.gg_keypair, init_params.msg_size,
                            std::vector<typename scalar_field_type::value_type> {}};
                }

                template<typename InputIterator>
                static inline void update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    std::move(first, last, std::back_inserter(acc.rnd));
                }

                template<typename InputRange>
                static inline void update(internal_accumulator_type &acc, InputRange range) {
                    update(acc, std::cbegin(range), std::cend(range));
                }

                static inline result_type process(internal_accumulator_type &acc) {
                    // TODO: check
                    BOOST_ASSERT_MSG(acc.rnd.size() >= 3 * acc.msg_size + 2,
                                     "Too few numbers in the source of randomness.");
                    auto rnd_iter = std::cbegin(acc.rnd);

                    typename scalar_field_type::value_type s_sum = scalar_field_type::value_type::zero();

                    std::vector<typename g1_type::value_type> delta_s_g1;
                    typename g1_type::value_type delta_sum_s_g1;
                    typename g1_type::value_type gamma_inverse_sum_s_g1 = acc.gg_keypair.second.gamma_g1;

                    typename scalar_field_type::value_type rho = *rnd_iter++;
                    typename g2_type::value_type rho_g2 = rho * g2_type::value_type::one();
                    std::vector<typename g2_type::value_type> rho_sv_g2;
                    std::vector<typename g2_type::value_type> rho_rhov_g2;

                    std::vector<typename g1_type::value_type> t_g1;
                    std::vector<typename g2_type::value_type> t_g2;

                    delta_s_g1.reserve(acc.msg_size);
                    rho_sv_g2.reserve(acc.msg_size);
                    rho_rhov_g2.reserve(acc.msg_size);
                    t_g1.reserve(acc.msg_size);
                    t_g2.reserve(acc.msg_size + 1);

                    typename scalar_field_type::value_type t = *rnd_iter++;
                    t_g2.emplace_back(t * g2_type::value_type::one());
                    delta_sum_s_g1 = t * acc.gg_keypair.second.delta_g1;

                    for (std::size_t i = 0; i < acc.msg_size; ++i) {
                        typename scalar_field_type::value_type s = *rnd_iter++;
                        typename scalar_field_type::value_type v = *rnd_iter++;
                        typename scalar_field_type::value_type sv = s * v;
                        t = *rnd_iter++;

                        delta_s_g1.emplace_back(s * acc.gg_keypair.second.delta_g1);
                        t_g1.emplace_back(t * acc.gg_keypair.second.gamma_ABC_g1.rest[i]);
                        t_g2.emplace_back(t * g2_type::value_type::one());
                        delta_sum_s_g1 = delta_sum_s_g1 + (s * t) * acc.gg_keypair.second.delta_g1;
                        gamma_inverse_sum_s_g1 = gamma_inverse_sum_s_g1 + s * acc.gg_keypair.second.gamma_g1;

                        rho_sv_g2.emplace_back(sv * g2_type::value_type::one());
                        rho_rhov_g2.emplace_back(v * rho_g2);
                    }
                    gamma_inverse_sum_s_g1 = -gamma_inverse_sum_s_g1;

                    public_key_type pk(acc.gg_keypair.second.delta_g1, delta_s_g1, t_g1, t_g2, delta_sum_s_g1,
                                       gamma_inverse_sum_s_g1);
                    private_key_type sk(rho);
                    verification_key_type vk(rho_g2, rho_sv_g2, rho_rhov_g2);

                    return {pk, sk, vk};
                }
            };

            template<typename Curve, std::size_t BlockBits>
            struct encrypt_op<elgamal_verifiable<Curve, BlockBits>> {
                typedef elgamal_verifiable<Curve, BlockBits> scheme_type;
                typedef typename scheme_type::proof_system_type proof_system_type;
                typedef typename scheme_type::public_key_type public_key_type;

                typedef typename Curve::scalar_field_type scalar_field_type;
                typedef typename Curve::template g1_type<> g1_type;

                struct init_params_type {
                    typename scalar_field_type::value_type r;
                    const public_key_type &pubkey;
                    const typename proof_system_type::keypair_type &gg_keypair;
                    // TODO: accumulate primary_input and auxiliary_input
                    const typename proof_system_type::primary_input_type &primary_input;
                    const typename proof_system_type::auxiliary_input_type &auxiliary_input;
                };
                struct internal_accumulator_type {
                    std::vector<typename scalar_field_type::value_type> plain_text;
                    typename scalar_field_type::value_type r;
                    const public_key_type &pubkey;
                    const typename proof_system_type::keypair_type &gg_keypair;
                    const typename proof_system_type::primary_input_type &primary_input;
                    const typename proof_system_type::auxiliary_input_type &auxiliary_input;
                };
                typedef typename scheme_type::cipher_type result_type;

                static inline internal_accumulator_type init_accumulator(const init_params_type &init_params) {
                    return {std::vector<typename scalar_field_type::value_type> {},
                            std::move(init_params.r),
                            init_params.pubkey,
                            init_params.gg_keypair,
                            init_params.primary_input,
                            init_params.auxiliary_input};
                }

                // TODO: process input data in place
                // TODO: use marshalling module instead of custom marshalling to process input data
                template<typename InputIterator>
                static inline void update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    std::copy(first, last, std::back_inserter(acc.plain_text));
                }

                template<typename InputRange>
                static inline void update(internal_accumulator_type &acc, InputRange range) {
                    update(acc, std::cbegin(range), std::cend(range));
                }

                static inline result_type process(internal_accumulator_type &acc) {
                    // TODO: check
                    BOOST_ASSERT_MSG(acc.gg_keypair.second.gamma_ABC_g1.rest.size() > acc.plain_text.size(),
                                     "Array of gammas in vk should be longer than the plain text.");
                    BOOST_ASSERT_MSG(acc.primary_input.size() > acc.plain_text.size(),
                                     "Primary input should be longer than plain text.");
                    BOOST_ASSERT_MSG(acc.gg_keypair.second.gamma_ABC_g1.rest.size() == acc.primary_input.size(),
                                     "Number of gammas should be equal to the primary input size.");
                    BOOST_ASSERT_MSG(acc.plain_text.size() == acc.pubkey.delta_s_g1.size(),
                                     "Plain text size should be equal to the delta_s array size from pk.");
                    BOOST_ASSERT_MSG(acc.plain_text.size() == acc.pubkey.t_g1.size(),
                                     "Plain text size should be equal to the t_g1 array size from pk.");
                    BOOST_ASSERT_MSG(acc.plain_text.size() == acc.pubkey.t_g2.size() - 1,
                                     "Plain text size should be equal to the t_g2 array size from pk.");
                    for (std::size_t i = 0; i < acc.plain_text.size(); ++i) {
                        BOOST_ASSERT_MSG(acc.primary_input[i] == acc.plain_text[i],
                                         "Plain text should be a prefix of primary input.");
                    }

                    typename result_type::first_type ct_g1;
                    ct_g1.reserve(acc.plain_text.size() + 2);
                    ct_g1.emplace_back(acc.r * acc.pubkey.delta_g1);

                    typename g1_type::value_type sum_tm_g1 = acc.r * acc.pubkey.delta_sum_s_g1;

                    for (std::size_t i = 0; i < acc.plain_text.size(); ++i) {
                        ct_g1.emplace_back(acc.r * acc.pubkey.delta_s_g1[i] +
                                           acc.plain_text[i] * acc.gg_keypair.second.gamma_ABC_g1.rest[i]);
                        sum_tm_g1 = sum_tm_g1 + acc.plain_text[i] * acc.pubkey.t_g1[i];
                    }
                    ct_g1.emplace_back(sum_tm_g1);
                    auto proof = zk::prove<proof_system_type>(acc.gg_keypair.first, acc.pubkey, acc.primary_input,
                                                              acc.auxiliary_input, acc.r);

                    return {ct_g1, proof};
                }
            };

            template<typename Curve, std::size_t BlockBits>
            struct decrypt_op<elgamal_verifiable<Curve, BlockBits>> {
                typedef elgamal_verifiable<Curve, BlockBits> scheme_type;
                typedef typename scheme_type::proof_system_type proof_system_type;
                typedef typename scheme_type::private_key_type private_key_type;
                typedef typename scheme_type::verification_key_type verification_key_type;

                typedef typename Curve::scalar_field_type scalar_field_type;
                typedef typename Curve::template g1_type<> g1_type;
                typedef typename Curve::gt_type gt_type;

                struct init_params_type {
                    const private_key_type &privkey;
                    const verification_key_type &vk;
                    const typename proof_system_type::keypair_type &gg_keypair;
                };
                struct internal_accumulator_type {
                    std::vector<typename g1_type::value_type> cipher_text;
                    const private_key_type &privkey;
                    const verification_key_type &vk;
                    const typename proof_system_type::keypair_type &gg_keypair;
                };
                typedef typename scheme_type::decipher_type result_type;

                static inline internal_accumulator_type init_accumulator(const init_params_type &init_params) {
                    return internal_accumulator_type {std::vector<typename g1_type::value_type> {}, init_params.privkey,
                                                      init_params.vk, init_params.gg_keypair};
                }

                // TODO: process input data in place
                // TODO: use marshalling module instead of custom marshalling to process input data
                template<typename InputIterator>
                static inline void update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    std::copy(first, last, std::back_inserter(acc.cipher_text));
                }

                template<typename InputRange>
                static inline void update(internal_accumulator_type &acc, InputRange range) {
                    update(acc, std::cbegin(range), std::cend(range));
                }

                static inline result_type process(internal_accumulator_type &acc) {
                    // TODO: check
                    BOOST_ASSERT_MSG(
                        acc.gg_keypair.second.gamma_ABC_g1.rest.size() > acc.cipher_text.size() - 2,
                        "Array of gammas in vk should be longer than the cipher text (exclusive of 2 element in CT).");
                    BOOST_ASSERT_MSG(acc.cipher_text.size() - 2 == acc.vk.rho_sv_g2.size(),
                                     "Cipher text size should be equal to the rho_sv_g2 array size from vk (exclusive "
                                     "of 2 element in CT).");
                    BOOST_ASSERT_MSG(acc.cipher_text.size() - 2 == acc.vk.rho_rhov_g2.size(),
                                     "Cipher text size should be equal to the rho_rhov_g2 array size from vk "
                                     "(exclusive of 2 element in CT).");
                    std::vector<typename scalar_field_type::value_type> m_new;
                    m_new.reserve(acc.cipher_text.size() - 2);

                    for (size_t j = 1; j < acc.cipher_text.size() - 1; ++j) {
                        typename gt_type::value_type ci_sk_i =
                            algebra::pair_reduced<Curve>(acc.cipher_text[j], acc.vk.rho_rhov_g2[j - 1]);
                        typename gt_type::value_type c0_sk_0 =
                            algebra::pair_reduced<Curve>(acc.cipher_text[0], acc.vk.rho_sv_g2[j - 1])
                                .pow(acc.privkey.rho.data);
                        typename gt_type::value_type dec_tmp = ci_sk_i * c0_sk_0.inversed();
                        auto discrete_log = gt_type::value_type::one();
                        typename gt_type::value_type bruteforce = algebra::pair_reduced<Curve>(
                            acc.gg_keypair.second.gamma_ABC_g1.rest[j - 1], acc.vk.rho_rhov_g2[j - 1]);
                        std::size_t exp = 0;
                        bool deciphered = false;
                        do {
                            if (dec_tmp == discrete_log) {
                                m_new.template emplace_back(exp);
                                deciphered = true;
                                break;
                            }
                            discrete_log = discrete_log * bruteforce;
                        } while (exp++ < 1 << scheme_type::block_bits);
                        BOOST_ASSERT_MSG(deciphered, "Decryption failed.");
                    }

                    typename g1_type::value_type verify_c0 = acc.privkey.rho * acc.cipher_text[0];

                    return {m_new, verify_c0};
                }
            };

            template<typename Curve, std::size_t BlockBits>
            struct verify_encryption_op<elgamal_verifiable<Curve, BlockBits>> {
                typedef elgamal_verifiable<Curve, BlockBits> scheme_type;
                typedef typename scheme_type::proof_system_type proof_system_type;
                typedef typename scheme_type::public_key_type public_key_type;

                typedef typename Curve::scalar_field_type scalar_field_type;
                typedef typename Curve::template g1_type<> g1_type;

                struct init_params_type {
                    const public_key_type &pubkey;
                    const typename proof_system_type::verification_key_type &gg_vk;
                    const typename proof_system_type::proof_type &proof;
                    const typename proof_system_type::primary_input_type &unencrypted_primary_input;
                };
                struct internal_accumulator_type {
                    const public_key_type &pubkey;
                    const typename proof_system_type::verification_key_type &gg_vk;
                    const typename proof_system_type::proof_type &proof;
                    const typename proof_system_type::primary_input_type &unencrypted_primary_input;
                    std::vector<typename g1_type::value_type> cipher_text;
                };
                typedef bool result_type;

                static inline internal_accumulator_type init_accumulator(const init_params_type &init_params) {
                    return internal_accumulator_type {init_params.pubkey, init_params.gg_vk, init_params.proof,
                                                      init_params.unencrypted_primary_input,
                                                      std::vector<typename g1_type::value_type> {}};
                }

                // TODO: process input data in place
                // TODO: use marshalling module instead of custom marshalling to process input data
                template<typename InputIterator>
                static inline void update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    std::copy(first, last, std::back_inserter(acc.cipher_text));
                }

                template<typename InputRange>
                static inline void update(internal_accumulator_type &acc, InputRange range) {
                    update(acc, std::cbegin(range), std::cend(range));
                }

                static inline result_type process(internal_accumulator_type &acc) {
                    return zk::verify<proof_system_type>(std::cbegin(acc.cipher_text), std::cend(acc.cipher_text),
                                                         acc.gg_vk, acc.pubkey, acc.unencrypted_primary_input,
                                                         acc.proof);
                }
            };

            template<typename Curve, std::size_t BlockBits>
            struct verify_decryption_op<elgamal_verifiable<Curve, BlockBits>> {
                typedef elgamal_verifiable<Curve, BlockBits> scheme_type;
                typedef typename scheme_type::proof_system_type proof_system_type;
                typedef typename scheme_type::public_key_type public_key_type;
                typedef typename scheme_type::verification_key_type verification_key_type;

                typedef typename Curve::scalar_field_type scalar_field_type;
                typedef typename Curve::template g1_type<> g1_type;
                typedef typename Curve::template g2_type<> g2_type;
                typedef typename Curve::gt_type gt_type;

                struct init_params_type {
                    const verification_key_type &vk;
                    const typename proof_system_type::keypair_type &gg_keypair;
                    const typename g1_type::value_type &proof;
                };
                struct internal_accumulator_type {
                    const verification_key_type &vk;
                    const typename proof_system_type::keypair_type &gg_keypair;
                    const typename g1_type::value_type &proof;
                    std::vector<typename scalar_field_type::value_type> plain_text;
                    std::vector<typename g1_type::value_type> cipher_text;
                };
                typedef bool result_type;

                static inline internal_accumulator_type init_accumulator(const init_params_type &init_params) {
                    return internal_accumulator_type {init_params.vk, init_params.gg_keypair, init_params.proof,
                                                      std::vector<typename scalar_field_type::value_type> {},
                                                      std::vector<typename g1_type::value_type> {}};
                }

                // TODO: process input data in place
                // TODO: use marshalling module instead of custom marshalling to process input data
                template<typename InputIterator>
                static inline typename std::enable_if<
                    std::is_same<typename scalar_field_type::value_type,
                                 typename std::iterator_traits<InputIterator>::value_type>::value>::type
                    update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    std::copy(first, last, std::back_inserter(acc.plain_text));
                }

                template<typename InputIterator>
                static inline typename std::enable_if<
                    std::is_same<typename g1_type::value_type,
                                 typename std::iterator_traits<InputIterator>::value_type>::value>::type
                    update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    std::copy(first, last, std::back_inserter(acc.cipher_text));
                }

                template<typename InputRange>
                static inline void update(internal_accumulator_type &acc, InputRange range) {
                    update(acc, std::cbegin(range), std::cend(range));
                }

                static inline result_type process(internal_accumulator_type &acc) {
                    BOOST_ASSERT_MSG(
                        acc.plain_text.size() + 2 == acc.cipher_text.size(),
                        "Cipher text size should be equal to the plain text size (exclusive of 2 element in CT).");
                    BOOST_ASSERT_MSG(acc.gg_keypair.second.gamma_ABC_g1.rest.size() > acc.plain_text.size(),
                                     "Array of gammas in vk should be longer than the plain text.");
                    typename gt_type::value_type vm_gt =
                        algebra::pair_reduced<Curve>(acc.proof, g2_type::value_type::one());
                    typename gt_type::value_type new_c0_v0_gt =
                        algebra::pair_reduced<Curve>(acc.cipher_text[0], acc.vk.rho_g2);
                    bool ans = (vm_gt == new_c0_v0_gt);

                    for (size_t i = 1; i < acc.cipher_text.size() - 1; ++i) {
                        typename gt_type::value_type ci_v_nj_gt =
                            algebra::pair_reduced<Curve>(acc.cipher_text[i], acc.vk.rho_rhov_g2[i - 1]);
                        typename gt_type::value_type v_vj_gt =
                            algebra::pair_reduced<Curve>(acc.proof, acc.vk.rho_sv_g2[i - 1]);
                        typename gt_type::value_type verify_tmp = ci_v_nj_gt * v_vj_gt.inversed();
                        typename gt_type::value_type verify_msg =
                            algebra::pair_reduced<Curve>(acc.gg_keypair.second.gamma_ABC_g1.rest[i - 1],
                                                         acc.vk.rho_rhov_g2[i - 1])
                                .pow(acc.plain_text[i - 1].data);
                        bool ans_m = (verify_tmp == verify_msg);
                        ans &= ans_m;
                    }

                    return ans;
                }
            };

            template<typename Curve, std::size_t BlockBits>
            struct rerandomize_op<elgamal_verifiable<Curve, BlockBits>> {
                typedef elgamal_verifiable<Curve, BlockBits> scheme_type;
                typedef typename scheme_type::proof_system_type proof_system_type;
                typedef typename scheme_type::public_key_type public_key_type;

                typedef typename Curve::scalar_field_type scalar_field_type;
                typedef typename Curve::template g1_type<> g1_type;
                typedef typename Curve::template g2_type<> g2_type;
                typedef typename Curve::gt_type gt_type;

                struct init_params_type {
                    const public_key_type &pubkey;
                    const typename proof_system_type::keypair_type &gg_keypair;
                    const typename proof_system_type::proof_type &proof;
                };
                struct internal_accumulator_type {
                    const public_key_type &pubkey;
                    const typename proof_system_type::keypair_type &gg_keypair;
                    const typename proof_system_type::proof_type &proof;
                    std::vector<typename scalar_field_type::value_type> rnd;
                    std::vector<typename g1_type::value_type> cipher_text;
                };
                typedef typename scheme_type::cipher_type result_type;

                static inline internal_accumulator_type init_accumulator(const init_params_type &init_params) {
                    return internal_accumulator_type {init_params.pubkey, init_params.gg_keypair, init_params.proof,
                                                      std::vector<typename scalar_field_type::value_type> {},
                                                      std::vector<typename g1_type::value_type> {}};
                }

                // TODO: process input data in place
                // TODO: use marshalling module instead of custom marshalling to process input data
                template<typename InputIterator>
                static inline typename std::enable_if<
                    std::is_same<typename scalar_field_type::value_type,
                                 typename std::iterator_traits<InputIterator>::value_type>::value>::type
                    update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    std::copy(first, last, std::back_inserter(acc.rnd));
                }

                template<typename InputIterator>
                static inline typename std::enable_if<
                    std::is_same<typename g1_type::value_type,
                                 typename std::iterator_traits<InputIterator>::value_type>::value>::type
                    update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    std::copy(first, last, std::back_inserter(acc.cipher_text));
                }

                template<typename InputRange>
                static inline void update(internal_accumulator_type &acc, InputRange range) {
                    update(acc, std::cbegin(range), std::cend(range));
                }

                static inline result_type process(internal_accumulator_type &acc) {
                    BOOST_ASSERT_MSG(acc.rnd.size() >= 3, "Too few numbers in the source of randomness (at least 3).");
                    BOOST_ASSERT_MSG(acc.pubkey.delta_s_g1.size() == acc.cipher_text.size() - 2,
                                     "Cipher text size should be equal to the delta_s_g1 array size from pk (exclusive "
                                     "of 2 element in CT).");
                    BOOST_ASSERT_MSG(acc.pubkey.t_g1.size() == acc.cipher_text.size() - 2,
                                     "Cipher text size should be equal to the t_g1 array size from pk (exclusive of 2 "
                                     "element in CT).");
                    BOOST_ASSERT_MSG(acc.pubkey.t_g2.size() - 1 == acc.cipher_text.size() - 2,
                                     "Cipher text size should be equal to the t_g2 array size from pk (exclusive of 2 "
                                     "element in CT).");
                    std::vector<typename g1_type::value_type> ct_g1;
                    ct_g1.reserve(acc.cipher_text.size());

                    auto rnd_it = std::cbegin(acc.rnd);
                    typename scalar_field_type::value_type r = *rnd_it++;
                    typename scalar_field_type::value_type z1 = *rnd_it++;
                    typename scalar_field_type::value_type z2 = *rnd_it++;

                    typename scalar_field_type::value_type z1_inverse = z1.inversed();

                    ct_g1.emplace_back(acc.cipher_text.front() + r * acc.pubkey.delta_g1);
                    for (size_t i = 1; i < acc.cipher_text.size() - 1; ++i) {
                        ct_g1.emplace_back(acc.cipher_text[i] + r * acc.pubkey.delta_s_g1[i - 1]);
                    }
                    ct_g1.emplace_back(acc.cipher_text.back() + r * acc.pubkey.delta_sum_s_g1);

                    typename g1_type::value_type g1_A = z1 * acc.proof.g_A;
                    typename g2_type::value_type g2_B =
                        z1_inverse * acc.proof.g_B + z2 * acc.gg_keypair.second.delta_g2;
                    typename g1_type::value_type g1_C =
                        acc.proof.g_C + z2 * g1_A + r * acc.pubkey.gamma_inverse_sum_s_g1;

                    return std::make_pair(ct_g1, typename proof_system_type::proof_type {
                                                     std::move(g1_A), std::move(g2_B), std::move(g1_C)});
                }
            };
        }    // namespace pubkey
    }        // namespace crypto3
}    // namespace nil

#endif


#include "liver/macrocell_tree.hpp"
#include "utility/volume_image.hpp"
#include <boost/filesystem.hpp>
#define CATCH_CONFIG_MAIN
#include <catch.hpp>

using namespace jhmi;

TEST_CASE( "Storing and loading", "[macrocell_tree]" ) {
    GOOGLE_PROTOBUF_VERIFY_VERSION;
    std::random_device::result_type seed = 100;
    auto initial_vessels = "../data/vtree_cycle0.txt";
    auto liver = voxelized_shape{"../data/liver_extents.datz"};
    const int cycles = 15;
    auto final_radius = 7_mm;
    auto tree = macrocell_tree{build_tree, initial_vessels, liver, seed, cubic_meters_per_second{400. * mL / minutes}, 2.7, 25_mmHg, true /*use_lattice*/};
    tree.build(cycles, final_radius);
    REQUIRE(tree.validate());
    auto saved_file = boost::filesystem::current_path() / "vessel_tree.pbz";
    tree.write(saved_file);

    auto tree2 = macrocell_tree{load_tree, saved_file};
    REQUIRE(tree == tree2);
    google::protobuf::ShutdownProtobufLibrary();
}