#ifndef ZKDT_RELEASE_GROTH16_H
#define ZKDT_RELEASE_GROTH16_H

#include <iostream>
#include <cstdlib>
#include <chrono>

#include <libsnark/common/default_types/r1cs_gg_ppzksnark_pp.hpp>
#include <libsnark/relations/constraint_satisfaction_problems/r1cs/examples/r1cs_examples.hpp>
#include <libsnark/zk_proof_systems/ppzksnark/r1cs_gg_ppzksnark/examples/run_r1cs_gg_ppzksnark.hpp>

using namespace libsnark;

// mostly a wrapper of the Groth16 implementation from libsnark
template<typename ppT>
bool run_r1cs_gg_ppzksnark(const protoboard<libff::Fr<ppT>> &pb, const std::string& name="")
{
    // printf("========= Loading circuit constraints and variables...\n");
    printf("========= Generating Keys...\n");
    auto start_gen = std::chrono::high_resolution_clock::now();
    r1cs_gg_ppzksnark_keypair<ppT> keypair = r1cs_gg_ppzksnark_generator<ppT>(pb.get_constraint_system());
    //printf("========= Done.\n");
    //printf("========= PK Gen: %1.f seconds\n", elapsed_gen.count());

    //printf("========= Preprocess verification key\n");
    // auto start_vkey = std::chrono::high_resolution_clock::now();
    r1cs_gg_ppzksnark_processed_verification_key<ppT> pvk = r1cs_gg_ppzksnark_verifier_process_vk<ppT>(keypair.vk);
    // auto end_vkey = std::chrono::high_resolution_clock::now();
    // std::chrono::duration<double> elapsed_vkey = end_vkey - start_vkey;
    auto end_gen = std::chrono::high_resolution_clock::now();
    // printf("========= Done.\n");
    // printf("========= VK Gen: %1.f seconds\n", elapsed_vkey.count());
    std::chrono::duration<double> elapsed_gen = end_gen - start_gen;
    printf("========= Done.\n");
    printf("========= Gen: %1.f seconds\n", elapsed_gen.count());

    printf("========= R1CS GG-ppzkSNARK Prover...\n");
    auto start_prove = std::chrono::high_resolution_clock::now();
    r1cs_gg_ppzksnark_proof<ppT> proof = r1cs_gg_ppzksnark_prover<ppT>(keypair.pk, pb.primary_input(), pb.auxiliary_input());
    auto end_prove = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> elapsed_prove = end_prove - start_prove;
    printf("========= Proof generated.\n");
    printf("========= Prove: %1.f seconds\n", elapsed_prove.count());

    printf("========= R1CS GG-ppzkSNARK Verifier...\n");
    printf("* receiving proof \n"); 
    auto start_verify = std::chrono::high_resolution_clock::now();
    const bool ans = r1cs_gg_ppzksnark_verifier_strong_IC<ppT>(keypair.vk, pb.primary_input(), proof);
    auto end_verify = std::chrono::high_resolution_clock::now();
    // Print proof elements
    // proof = libff::reserialize<r1cs_gg_ppzksnark_proof<ppT> >(proof);
    // std::cout << "* Proof A (in G1): " << proof.g_A << std::endl << std::flush;
    // std::cout << "* Proof B (in G1): " << proof.g_B << std::endl << std::flush;
    // std::cout << "* Proof C (in G2): " << proof.g_C << std::endl << std::flush;
    std::chrono::duration<double> elapsed_verify = end_verify - start_verify;
    printf("* The verification result is: %s\n", (ans ? "PASS" : "FAIL"));
    printf("========= Verification ended.\n");
    printf("========= Verify: %3.f seconds\n", elapsed_verify.count());

    //printf("=============== PK Gen: %1.f seconds\n", elapsed_gen.count());
    //printf("=============== VK Gen: %1.f seconds\n", elapsed_vkey.count());
    printf("=============== Gen: %1.f seconds\n", elapsed_gen.count());
    printf("=============== Prove: %1.f seconds\n", elapsed_prove.count());
    printf("=============== Verify: %3.f seconds\n", elapsed_verify.count());

    if (name != "") {
        std::cout << "Saving proof" << std::endl;
        std::ofstream out(name);
        out << proof;
        out.close();
    }

    return ans;
}


#endif //ZKDT_RELEASE_GROTH16_H
