/**
 This file is part of Poisson Image Editing.

 Copyright Christoph Heindl 2015

 Poisson Image Editing is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 Poisson Image Editing is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with Poisson Image Editing.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <blend/blend.h>
#include <blend/poisson_solver.h>
#include <opencv2/opencv.hpp>
#include <Eigen/Dense>
namespace blend {

    template<class T>
    Eigen::Map< Eigen::Matrix<T, Eigen::Dynamic, 1> > mapChannels(cv::Mat &m, int y, int x) {

        typedef Eigen::Matrix<T, Eigen::Dynamic, 1> V;
        return Eigen::Map<V>(m.ptr<T>(y, x), m.channels());
    }

    void seamlessBlend(cv::InputArray first_,
                       cv::InputArray second_,
                       cv::InputArray mask_,
                       cv::OutputArray destination_)
    {

        cv::Mat first = first_.getMat();
        cv::Mat second = second_.getMat();

        destination_.create(first.size(), first.type());
        cv::Mat dst = destination_.getMat();

        // Target Laplacians are zero
        cv::Mat f(first.size(), CV_MAKE_TYPE(CV_32F, first.channels()));
        f.setTo(0);

        cv::Mat bm(first.size(), CV_8UC1);
        bm.setTo(constants::UNKNOWN);
        cv::Mat bv(first.size(), CV_MAKE_TYPE(CV_32F, first.channels()));
        bv.setTo(0);

        cv::Rect boundsSecond(0, 0, second.cols, second.rows);

        for (int y = 0; y < first.rows; ++y) {
            const uchar *maskRow = mask_.getMat().ptr<uchar>(y);
            for (int x = 0; x < first.cols; ++x) {
                const bool isBorder = (y == 0) || (x == 0) || (y == (first.rows - 1)) || (x == (first.cols - 1));
                const bool isFirst = (maskRow[x] == 255);

                if (isFirst && isBorder) {
                    bm.at<uchar>(y, x) = constants::NEUMANN_BD;
                    bv.at<float>(y, x) = 0.f;
                } else if (!isFirst && boundsSecond.contains(cv::Point(x,y))) {
                    bm.at<uchar>(y, x) = constants::DIRICHLET_BD;
                    mapChannels<float>(bv, y, x) = mapChannels<uchar>(second, y, x).cast<float>() - mapChannels<uchar>(first, y, x).cast<float>();
                } else if (!isFirst) {
                    bm.at<uchar>(y, x) = constants::DIRICHLET_BD;
                    mapChannels<float>(bv, y, x).setZero();
                }
            }
        }

        // Solve Poisson equation
        cv::Mat result;
        solvePoissonEquations(f,
                              bm,
                              bv,
                              result);

        cv::Mat fixed;
        first.convertTo(fixed, result.depth());
        fixed += result;
        fixed.convertTo(dst, dst.depth());
        second.copyTo(dst, (255 - mask_.getMat()));

    }

}


/*
 * G_q.cpp
 *
 *  Created on: 30.09.2010
 *      Author: stephaniebayer
 *
 *
 */

#include "G_q.h"

#include <NTL/ZZ.h>
NTL_CLIENT

#include <time.h>
G_q::G_q() {
	// TODO Auto-generated constructor stub

}

//Constructor creates an instance of G_q subset Z_p with order o and generator g
G_q::G_q(Mod_p gen, long o, long p){

	generator = gen;
	order = to_ZZ(o);
	mod = to_ZZ(p);
	if (gen.get_mod() != p)
		cout  << "The modular value of the generator and p are not equal" << endl;
}

//Constructor creates an instance of G_q subset Z_p with order o and generator g
G_q::G_q(Mod_p gen, long o, ZZ p){

	generator = gen;
	order = to_ZZ(o);
	mod = p;

	if (gen.get_mod() != p)
		cout  << "The modular value of the generator and p are not equal" << endl;

}

//Constructor creates an instance of G_q subset Z_p with order o and generator g
G_q::G_q(Mod_p gen, ZZ o, ZZ p){

	generator = gen;
	order = o;
	mod = p;

	if (gen.get_mod() != p)
		cout  << "The modular value of the generator and p are not equal" << endl;

}

//Constructor creates an instance of G_q subset Z_p with order o and generator value val
G_q::G_q(ZZ val, long o, long p){

	generator = Mod_p(val, p);
	order = to_ZZ(o);
	mod = to_ZZ(p);

}

//Constructor creates an instance of G_q subset Z_p with order o and generator value val
G_q::G_q(ZZ val, long o, ZZ p){

	generator = Mod_p(val, p);
	order = to_ZZ(o);
	mod = p;

}

//Constructor creates an instance of G_q subset Z_p with order o and generator value val
G_q::G_q(ZZ val, ZZ o, ZZ p){

	generator = Mod_p(val, p);
	order = o;
	mod = p;

}

//Constructor creates an instance of G_q subset Z_p with order o and generator value val
G_q::G_q(long val, long o, long p){

	generator = Mod_p(val, p);
	order = to_ZZ(o);
	mod = to_ZZ(p);

}

//Constructor creates an instance of G_q subset Z_p with order o and generator value val
G_q::G_q(long val, long o, ZZ p){

	generator = Mod_p(val, p);
	order = to_ZZ(o),
	mod = p;

}

//Constructor creates an instance of G_q subset Z_p with order o and generator value val
G_q::G_q(long val,ZZ o, ZZ p){

	generator = Mod_p(val, p);
	order = o;
	mod = p;

}

//Constructor creates an instance of G_q with order o, generator gen and G_q is a subgroup if Z modulo gen.get_mod()
G_q::G_q(Mod_p gen, long o){

	generator = gen;
	order = to_ZZ(o);
	mod = gen.get_mod();
}

//Constructor creates an instance of G_q with order o, generator gen and G_q is a subgroup if Z modulo gen.get_mod()
G_q::G_q(Mod_p gen, ZZ o){

	generator = gen;
	order = o;
	mod = gen.get_mod();
}

//Constructor creates an instance of G_q  subset of Z_p with order o and searchs for the smallest generator
G_q::G_q(long o,ZZ p){

	ZZ i;
	order = to_ZZ(o);
	mod = p;
	for (i = to_ZZ(1); i < p; ++i)
	{
		if (is_generator(i))
		{
			generator = Mod_p(i,p);
			break;
		}
	}
}

//Constructor creates an instance of G_q  subset of Z_p with order o and searchs for the smallest generator
G_q::G_q(ZZ o,ZZ p){

	ZZ i,t;
	order = o;
	mod = p;
	for (i = to_ZZ(2); i < p; ++i)
	{
		t=i%100000;
		if(t==0){
			cout<<";";
		}
		if (is_generator(i))
		{
			generator = Mod_p(i,p);
			break;
		}
	}
}

//Constructor creates an instance of G_q  subset of Z_p with order o and searchs for the smallest generator
G_q::G_q(long o, long p){

	long i;
	order = to_ZZ(o);
	mod = to_ZZ(p);
	for (i = 1; i < p; ++i)
	{
		if (is_generator(i))
		{
			generator = Mod_p(i,p);
			break;
		}
	}
}

G_q::~G_q() {
	// TODO Auto-generated destructor stub
}

//return the generator
Mod_p G_q::get_gen()const{

	return generator;
}

//return the order o
ZZ G_q::get_ord()const{

	return order;
}

//return the modular value mod
ZZ G_q::get_mod()const{

	return mod;
}

//Checks if an element is a generator of the group G
bool G_q::is_generator(const Mod_p& el){
	ZZ pow;
	bool b;
	b=false;
	pow = PowerMod(el.get_val(),order,mod);
	if(pow == to_ZZ(1)& el.get_val()!=1)
	{
		b=true;
	}
	return b;
}

//Checks if an element is a generator of the group G
bool G_q::is_generator(const ZZ& x){
	ZZ pow;
	bool b;
	b=false;
	pow = PowerMod(x,order,mod);

	if(pow == to_ZZ(1) & x!=1)
	{
		b=true;

	}

	return b;
}

//Checks if an element is a generator of the group G
bool G_q::is_generator(const long& x){
	ZZ pow;
	bool b;
	pow = PowerMod(to_ZZ(x),order, mod);
	if(pow == to_ZZ(1)& x!=1)
	{
		b=true;

	}

	return b;
}

//returns the identity of the group
Mod_p G_q::identity(){

	return Mod_p(1, mod);
}

//returns a random element of the group
Mod_p G_q::random_el(){
	ZZ ran,pow;
	Mod_p temp;
	SetSeed(to_ZZ(time(0)));
	ran = RandomBnd(mod);
	temp = generator.expo(ran);

	return temp;

}

//returns a random element of the group, without setting the seed
Mod_p G_q::random_el(int c){
	ZZ ran,pow;
	Mod_p temp;
	ran = RandomBnd(order);
	temp = generator.expo(ran);

	return temp;

}

//returns an element of the group with value v
Mod_p G_q::element(ZZ v){

	return Mod_p(v,mod);

}

//returns an element of the group with value v
Mod_p G_q::element(long v){

	return Mod_p(v,mod);

}

void G_q::operator =(const G_q& H){

	generator = H.get_gen();
	order = H.get_ord();
	mod = H.get_mod();
}

//Output operator, output format is (generator value, order, modular value)
ostream& operator<<(ostream& os, const G_q G){
	return os<< "("<< G.get_gen().get_val()<< ", " << G.get_ord() <<", " << G.get_mod()<<")";
}


//
//  main.cpp
//  lpm
//
//  Created by Seong-Hwan Jun on 2019-05-08.
//

#include <iostream>
#include <string>
#include <boost/algorithm/string.hpp>
#include <boost/program_options.hpp>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include "lpm.hpp"

using namespace std;

void draw_stratified_samples(gsl_rng *random, size_t n_mc_samples, double vec[], double max)
{
    double interval = max/n_mc_samples;
    double sum = 0.0;
    for (size_t i = 0; i < n_mc_samples; i++) {
        vec[i] = gsl_ran_flat(random, sum, sum + interval);
        sum += interval;
    }
}

int main(int argc, char *argv[])
{
    namespace po = boost::program_options;
    /**
     * arguments:
     * mode: model/pg
     * seed
     * model_len: range (i.e., 2-10)
     * n_mc_samples: 30
     * n_pg_iter: 30
     * n_particles: 400
     * n_smc_iter: 100
     * n_kernel_iter: 3
     * n_mh_w_gibbs_iter: 10
     * has_passenger: True
     * swap_prob: 0.1
     * fbp_max: 0
     * bgp_max: 0.2
     * mh_proposal_sd: 0.05
     * n_threads: 8
     * use_lik_tempering: True
     * data_path: data/Experiment2/With_passengers/5/error0.05/rep0/matrix.csv
     **/

    string data_path;
    string output_path;
//    string mode;
    unsigned long seed;
    unsigned int model_len;
    //unsigned int n_mc_samples;
    unsigned int n_mcmc_iter;
//    unsigned int n_particles;
//    unsigned int n_smc_iter;
//    unsigned int n_kernel_iter;
    unsigned int n_mh_w_gibbs_iter;
    unsigned int burn_in;
    unsigned int thinning_interval;
//    unsigned int n_mc_threads;
//    unsigned int n_smc_threads;
//    bool use_lik_tempering;
    double fbp_max, bgp_max;
    bool has_passenger;
    double swap_prob;
    double mh_proposal_sd;
    double prior_passenger_prob;

    po::options_description desc("Program options");
    desc.add_options()
    ("help", "Put a help message here.")
    ("data_path,d", po::value<string>(&data_path)->required(), "Specify path to the data.")
    ("output_path,o", po::value<string>(&output_path)->required(), "Specify output path.")
//    ("mode,m", po::value<string>(&mode)->required(), "Specify mode: model for model selection and pg for particle Gibbs.")
    ("seed,s", po::value<unsigned long>(&seed)->default_value(1), "Specify random seed.")
    ("model_len,l", po::value<unsigned int>(&model_len)->required(), "Specify model length.")
    //("n_mc_samples,M", po::value<unsigned int>(&n_mc_samples), "Specify number of MC samples to use for model selection.")
    ("n_mcmc_iter,p", po::value<unsigned int>(&n_mcmc_iter), "Specify number of iterations for PG/MCMC.")
    //("n_particles,P", po::value<unsigned int>(&n_particles)->required(), "Specify number of particles to use.")
    //("n_smc_iter,S", po::value<unsigned int>(&n_smc_iter)->required(), "Specify number of SMC iterations.")
    //("n_kernel_iter,k", po::value<unsigned int>(&n_kernel_iter)->default_value(5), "Specify number of iterations of MCMC kernels to apply within a SMC move. Specifying 0 reduces to random walk move.")
    ("n_mh_w_gibbs_iter,G", po::value<unsigned int>(&n_mh_w_gibbs_iter)->default_value(10), "Specify number of iterations of MH within Gibbs for inferring the parameters within PG.")
    //("n_mc_threads,t", po::value<unsigned int>(&n_mc_threads)->default_value(1), "Specify number of threads to use for parallel processing of Monte Carlo samples.")
    //("n_smc_threads", po::value<unsigned int>(&n_smc_threads)->default_value(1), "Specify number of threads to use for SMC algorithm.")
    //("use_lik_tempering,T", po::value<bool>(&use_lik_tempering)->default_value(true), "Specify whether to use likelihood tempering.")
    ("fbp_max,f", po::value<double>(&fbp_max)->default_value(0), "Specify max value for flip-back probability. Specify 0 here to use unified error probability.")
    ("bgp_max,b", po::value<double>(&bgp_max)->default_value(0.2), "Specify max value for background mutation probability. Specifying 0 for fbp_max to interpret this quantity as an error probability.")
    ("has_passenger", po::value<bool>(&has_passenger)->default_value(true), "Specify whether to detect passenger genes. 0: false, 1: true.")
    ("swap_prob", po::value<double>(&swap_prob)->default_value(0.2), "Specify pathway swap probability for SMC move.")
    ("mh_proposal_sd", po::value<double>(&mh_proposal_sd)->default_value(0.05), "Specify standard deviation of Gaussian random walk proposal within MHwGibbs for inferring the parameters (for mode = pg).")
    ("burn_in", po::value<unsigned int>(&burn_in)->default_value(0), "Specify burn in for MCMC.")
    ("thinning_interval", po::value<unsigned int>(&thinning_interval)->default_value(1), "Specify thinning interval for MCMC.")
    ("prior_passenger_prob", po::value<double>(&prior_passenger_prob)->default_value(0.95), "Specify prior probability of a gene being a passenger.")
    ;

    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);
    po::notify(vm);

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

    run_mcmc(seed, data_path.c_str(), output_path.c_str(), model_len, n_mcmc_iter, n_mh_w_gibbs_iter, thinning_interval, burn_in, has_passenger, swap_prob, fbp_max, bgp_max, mh_proposal_sd, prior_passenger_prob);

    //if (mode == "mcmc") {
//        run_mcmc(seed, data_path.c_str(), output_path.c_str(), model_len, n_mcmc_iter, n_mh_w_gibbs_iter, thinning_interval, burn_in, has_passenger, swap_prob, fbp_max, bgp_max, mh_proposal_sd, prior_passenger_prob);
//    } else {
//        cerr << "Unknown mode: " << mode << "." << endl;
//    }

//    if (mode == "pg") {
//        if (!vm.count("n_pg_iter")) {
//            cerr << "Please specify number of PG iterations." << endl;
//        }
//        run_pg(seed, data_path.c_str(), output_path.c_str(), model_len, n_mcmc_iter, n_particles, n_smc_iter, n_kernel_iter, n_mh_w_gibbs_iter, has_passenger, swap_prob, fbp_max, bgp_max, mh_proposal_sd, n_smc_threads);
//    } else if (mode == "model") {
//        if (!vm.count("n_mc_samples")) {
//            cerr << "Please specify number of Monte Carlo samples." << endl;
//        }
//        // sample fbps and bgps via stratified sampling
//        double *fbps = new double[n_mc_samples];
//        double *bgps = new double[n_mc_samples];
//        gsl_rng *random = generate_random_object(seed);
//        draw_stratified_samples(random, n_mc_samples, bgps, bgp_max);
//        if (fbp_max == 0.0) {
//            std::copy(bgps, bgps + n_mc_samples, fbps);
//        }
//        unsigned long new_seed = gsl_rng_get(random);
//        model_selection(new_seed, data_path.c_str(), output_path.c_str(), model_len, n_mc_samples, n_particles, n_smc_iter, n_kernel_iter, has_passenger, swap_prob, fbps, bgps, n_mc_threads, n_smc_threads, use_lik_tempering);
//        gsl_rng_free(random);
//    } else if (mode == "mcmc") {
//        run_mcmc(seed, data_path.c_str(), output_path.c_str(), model_len, n_mcmc_iter, n_mh_w_gibbs_iter, thinning_interval, has_passenger, swap_prob, fbp_max, bgp_max, mh_proposal_sd, prior_passenger_prob);
//    } else {
//        cerr << "Unknown mode: " << mode << "." << endl;
//    }
    return 0;
}


#ifndef CX_DQMC_DENSITY_HPP
#define CX_DQMC_DENSITY_HPP

#include "la.hpp"
#include "parameters.hpp"
#include "cx_workspace.hpp"

#include <Eigen/Eigenvalues>

#include <boost/foreach.hpp>

#include <cmath>
#include <exception>
#include <iomanip>

namespace cx_dqmc {
    namespace density {
	inline void density_matrix(dqmc::parameters& p,
				   cx_dqmc::workspace& ws) {
	    using namespace std;
	    srand(23);
	    double rand_hop;
	    double rand_eps = 0;

	    pvec_t eigval(p.N);
	    pmat_t lowest_eigenvectors(p.N, p.particles);
	    pmat_t density(p.N, p.N), eigvec(p.N, p.N);

	    density.setZero();

	    if (ws.density.cols() != p.N) {
		throw std::runtime_error("Dimensions of density are incorrect");
	    }

	    for (int attempt = 0; attempt < 4; ++attempt) {
		alps::graph_helper<>::bond_iterator itr1, itr1_end;
		for (boost::tie(itr1, itr1_end) = p.graph.bonds(); itr1 != itr1_end; ++itr1) {
		    rand_hop = (1 + rand_eps * ( -0.5 + (std::rand() % RAND_MAX)/double(RAND_MAX)));

		    density(p.graph.source(*itr1), p.graph.target(*itr1)) =
			-p.ts[p.graph.bond_type(*itr1)] * rand_hop;
		    density(p.graph.target(*itr1), p.graph.source(*itr1)) =
			-p.ts[p.graph.bond_type(*itr1)] * rand_hop;
		}
		eigvec = density;

		Eigen::SelfAdjointEigenSolver<mat> eig(density);
		eig.compute(density);
		eigval = eig.eigenvalues();
		eigvec = eig.eigenvectors();

		bool error = false;
		for (int i = 0; i < eigval.size() - 1; ++i) {
		    double diff;
		    diff = fabs(eigval(i + 1) - eigval(i));
		    if (diff < 1e-4) {
			if (attempt == 0) {
			    rand_eps = 1e-3;
			}
			else if (attempt == 1) {
			    rand_eps = 1e-2;
			}
			else if (attempt == 2) {
			    rand_eps = 1e-1;
			}
			else if (attempt == 3) {
			    rand_eps = 5e-1;
			}
			else if (attempt == 4) {
			    if (p.rank < 1) {
				cout << attempt << " @ rand_eps = " << rand_eps << " - " << diff << endl;
			    }
			    throw std::runtime_error("Degeneracy not lifted");
			}
			error = true;
			break;
		    }
		}
		if (!error) {
		    break;
		}
	    }

	    for (int i = 0; i < p.N; i++) {
	    	for (int j = 0; j < p.real_particles; j++) {
		    lowest_eigenvectors(i, j) = eigvec(i, j);
	    	}
	    }

	    for (int row = 0; row < p.N; row++) {
		for (int col = 0; col < p.N; col++) {
		    ws.density(row, col) = cx_double(0, 0);

		    for (int i = 0; i < p.real_particles; i++) {
			ws.density(row, col) +=
			    cx_double(lowest_eigenvectors(row, i) * lowest_eigenvectors(col, i), 0);
			ws.den_U(row, i) = cx_double(lowest_eigenvectors(row, i), 0);
		    }
		}
	    }

	    // cout << p.outp << "And den_u" << endl << ws.den_U << endl << endl;
 	}

	inline void cx_density_matrix(dqmc::parameters& p, cx_dqmc::workspace& ws) {
	    using namespace std;

	    srand(23);
	    double rand_hop;
	    double rand_eps = 0;

	    cx_vec eigval(p.N);
	    cx_mat_t lowest_eigenvectors(p.N, p.real_particles);
	    cx_mat_t density(p.N, p.N), eigvec(p.N, p.N);

	    if (ws.density.cols() != p.N) {
		throw std::runtime_error("Dimensions of density are incorrect");
	    }

	    ws.density.setZero();
	    for (int attempt = 0; attempt < 4; ++attempt) {
		alps::graph_helper<>::bond_iterator itr1, itr1_end;
		int s, t, b;
		double pref;

		for (boost::tie(itr1, itr1_end) = p.graph.bonds(); itr1 != itr1_end; ++itr1) {
		    rand_hop = (1 + rand_eps * ( -0.5 + (std::rand() % RAND_MAX)/double(RAND_MAX)));
		    s = p.graph.source(*itr1);
		    t = p.graph.target(*itr1);
		    b = p.graph.bond_type(*itr1);

		    ws.density(s, t) = -1. * cx_double(p.ts[b] * rand_hop, -p.im_ts[b]* rand_hop);
		    ws.density(t, s) = -1. * cx_double(p.ts[b] * rand_hop, p.im_ts[b] * rand_hop);
		}
		ws.eigvec = ws.density;

		Eigen::SelfAdjointEigenSolver<cx_mat_t> eig(ws.density);
		eig.compute(ws.density);
		vec revals = eig.eigenvalues();
		ws.eigvec = eig.eigenvectors();

		if (attempt == 0) {
		    ofstream mat_file("eigenvectors.mat", ios::out | ios::trunc);
		    mat_file << ws.eigvec;
		    mat_file.close();

		    ofstream vec_file("eigenvalues.vec", ios::out | ios::trunc);
		    vec_file << revals;
		    vec_file.close();
		}

		std::vector<max_pair > sorting;
		sorting.reserve(revals.size());
		for (int i = 0; i < revals.size(); i++) {
		    double val = revals(i);
		    sorting.push_back(max_pair(val,i));
		}
		std::sort(sorting.begin(),sorting.end());
		cx_mat_t sorted_eigvec;
		sorted_eigvec.resizeLike(ws.eigvec);
		for (int i = 0; i < revals.size(); i++) {
		    revals.coeffRef(i,0) = sorting[i].first;
		    sorted_eigvec.col(i) = ws.eigvec.col(sorting[i].second);
		}
		if (attempt == 0) {
		    ofstream sorted_mat_file("sorted_eigenvectors.mat", ios::out | ios::trunc);
		    sorted_mat_file << sorted_eigvec;
		    sorted_mat_file.close();

		    ofstream sorted_file("sorted_eigenvalues.vec", ios::out | ios::trunc);
		    sorted_file << revals;
		    sorted_file.close();
		}

		// eigval = revals;
		ws.eigvec = sorted_eigvec;

		bool error = false;
		for (int i = 0; i < revals.size() - 1; ++i) {
		    // break;
		    double diff;
		    diff = abs(revals(i + 1) - revals(i));
		    if (diff < 1e-5) {

			if (attempt == 0) {
			    rand_eps = 1e-3;
			}
			else if (attempt == 1) {
			    rand_eps = 1e-2;
			}
			else if (attempt == 2) {
			    rand_eps = 1e-1;
			}
			else if (attempt == 3) {
			    throw std::runtime_error("Degeneracy not lifted");
			}
			error = true;
			break;
		    }
		}
		if (!error) {
		    break;
		}
	    }
	    ws.den_U = ws.eigvec.block(0, 0, p.N, p.real_particles);
	    ws.density = ws.den_U * ws.den_U.adjoint();
 	}
    }
}
#endif


#include <boost/algorithm/hex.hpp>
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>

#include <nil/crypto3/zk/components/blueprint.hpp>
#include <nil/crypto3/zk/components/blueprint_variable.hpp>

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

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

#include "./types.h"
#include "./utils.cpp"
#include "./circuit.h"

using namespace std;

using namespace nil::crypto3::zk::components;
using namespace nil::crypto3::zk::snark;

ushort INVALID_PROOF_RETURN_CODE = 200;

int setup_keys(boost::filesystem::path pk_path, boost::filesystem::path vk_path) {
    blueprint<field_type> bp;
    LocationCircuit circuit(bp);
    circuit.generate_r1cs_constraints(bp);

    cout << "Blueprint size: " << bp.num_variables() << endl;
    cout << "Generating constraint system..." << endl;
    const r1cs_constraint_system<field_type> constraint_system = bp.get_constraint_system();
    cout << "Number of R1CS constraints: " << constraint_system.num_constraints() << endl;

    cout << "Generating keypair..." << endl;
    scheme_type::keypair_type keypair = generate<scheme_type>(constraint_system);

    cout << "Saving proving key to a file " << pk_path<< endl;
    save_proving_key(keypair.first, pk_path);

    cout << "Saving verification key to a file " << vk_path << endl;
    save_verification_key(keypair.second, vk_path);

    return 0;
}

int create_proof(boost::filesystem::path pk_path, boost::filesystem::path proof_path, boost::filesystem::path pi_path,float minLat, float maxLat, float minLng, float maxLng, float posLat, float posLng) {
    cout << "Loading proving key from a file " << pk_path << endl;
    typename scheme_type::proving_key_type pk = load_proving_key(pk_path);

    blueprint<field_type> bp;
    LocationCircuit circuit(bp);
    cout << "Generating constraint system..." << endl;
    circuit.generate_r1cs_constraints(bp);
    cout << "Generating witness..." << endl;
    circuit.generate_r1cs_witness(bp, minLat, maxLat, minLng, maxLng, posLat, posLng);

    cout << "Blueprint is satisfied: " << bp.is_satisfied() << endl;
    if (!bp.is_satisfied()) {
        return INVALID_PROOF_RETURN_CODE;
    }

    cout << "Generating proof..." << endl;
    const scheme_type::proof_type proof = prove<scheme_type>(pk, bp.primary_input(), bp.auxiliary_input());

    cout << "Saving proof to file " << proof_path << endl;
    save_proof(proof, proof_path);

    cout << "Saving primary input to file " << pi_path << endl;
    save_primary_input(bp.primary_input(), pi_path);
    return 0;
}

int verify_proof(boost::filesystem::path proof_path, boost::filesystem::path vk_path, boost::filesystem::path pi_path) {
    cout << "Loading proof from a file " << proof_path << endl;
    typename scheme_type::proof_type proof = load_proof(proof_path);

    cout << "Loading primary input from a file " << pi_path << endl;
    r1cs_primary_input<field_type> input = load_primary_input(pi_path);

    cout << "Loading verification key from a file " << vk_path << endl;
    typename scheme_type::verification_key_type vk = load_verification_key(vk_path);

    // verify
    using basic_proof_system = r1cs_gg_ppzksnark<curve_type>;
    const bool verified = verify<basic_proof_system>(vk, input, proof);
    cout << "Verification status: " << verified << endl;

    return verified ? 0 : INVALID_PROOF_RETURN_CODE;
}

int main(int argc, char *argv[]) {
    float minLat, maxLat, minLng, maxLng, posLat, posLng;
    boost::filesystem::path pk_path, vk_path, proof_path, pi_path;
    // bool hexFlag;

    boost::program_options::options_description options("CLI Proof Generator");
    options.add_options()
    // ("hex,h", boost::program_options::bool_switch(&hexFlag), "print only hex proof to output")
    ("minLat,minLat", boost::program_options::value<float>(&minLat)->default_value(0))
    ("maxLat,maxLat", boost::program_options::value<float>(&maxLat)->default_value(0))
    ("minLng,minLng", boost::program_options::value<float>(&minLng)->default_value(0))
    ("maxLng,maxLng", boost::program_options::value<float>(&maxLng)->default_value(0))
    ("posLat,posLat", boost::program_options::value<float>(&posLat)->default_value(0))
    ("posLng,posLng", boost::program_options::value<float>(&posLng)->default_value(0))
    ("proving-key-path,pk", boost::program_options::value<boost::filesystem::path>(&pk_path)->default_value("proving.key"))
    ("verification-key-path,vk", boost::program_options::value<boost::filesystem::path>(&vk_path)->default_value("verification.key"))
    ("proof-path,p", boost::program_options::value<boost::filesystem::path>(&proof_path)->default_value("proof"))
    ("primary-input-path,pi", boost::program_options::value<boost::filesystem::path>(&pi_path)->default_value("primary.input"));

    boost::program_options::variables_map vm;
    boost::program_options::store(boost::program_options::command_line_parser(argc, argv).options(options).run(), vm);
    boost::program_options::notify(vm);

    cout << setprecision(16);

    if (!argv[1]) {
        cout << "Please select a command: [setup/prove/verify]" << endl;
        return 0;
    }
    else if (string(argv[1]) == "setup") {
        // Generate proving.key & verification.key
        return setup_keys(pk_path, vk_path);
    } else if (string(argv[1]) == "prove") {
        // Generate and  save proof and primary_input to file
        return create_proof(pk_path, proof_path, pi_path, minLat, maxLat, minLng, maxLng, posLat, posLng);
    } else if (string(argv[1]) == "verify") {
        // Check the status of the proof
        return verify_proof(proof_path, pi_path, vk_path);
    }

    return 0;
}


// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2013 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 "massmatrix.h"
#include "normalize_row_sums.h"
#include "sparse.h"
#include "doublearea.h"
#include "repmat.h"
#include <Eigen/Geometry>
#include <iostream>

template <typename DerivedV, typename DerivedF, typename Scalar>
IGL_INLINE void igl::massmatrix(
  const Eigen::MatrixBase<DerivedV> & V,
  const Eigen::MatrixBase<DerivedF> & F,
  const MassMatrixType type,
  Eigen::SparseMatrix<Scalar>& M)
{
  using namespace Eigen;
  using namespace std;

  const int n = V.rows();
  const int m = F.rows();
  const int simplex_size = F.cols();

  MassMatrixType eff_type = type;
  // Use voronoi of for triangles by default, otherwise barycentric
  if(type == MASSMATRIX_TYPE_DEFAULT)
  {
    eff_type = (simplex_size == 3?MASSMATRIX_TYPE_VORONOI:MASSMATRIX_TYPE_BARYCENTRIC);
  }

  // Not yet supported
  assert(type!=MASSMATRIX_TYPE_FULL);

  Matrix<int,Dynamic,1> MI;
  Matrix<int,Dynamic,1> MJ;
  Matrix<Scalar,Dynamic,1> MV;
  if(simplex_size == 3)
  {
    // Triangles
    // edge lengths numbered same as opposite vertices
    Matrix<Scalar,Dynamic,3> l(m,3);
    // loop over faces
    for(int i = 0;i<m;i++)
    {
      l(i,0) = (V.row(F(i,1))-V.row(F(i,2))).norm();
      l(i,1) = (V.row(F(i,2))-V.row(F(i,0))).norm();
      l(i,2) = (V.row(F(i,0))-V.row(F(i,1))).norm();
    }
    Matrix<Scalar,Dynamic,1> dblA;
    doublearea(l,dblA);

    switch(eff_type)
    {
      case MASSMATRIX_TYPE_BARYCENTRIC:
        // diagonal entries for each face corner
        MI.resize(m*3,1); MJ.resize(m*3,1); MV.resize(m*3,1);
        MI.block(0*m,0,m,1) = F.col(0);
        MI.block(1*m,0,m,1) = F.col(1);
        MI.block(2*m,0,m,1) = F.col(2);
        MJ = MI;
        repmat(dblA,3,1,MV);
        MV.array() /= 6.0;
        break;
      case MASSMATRIX_TYPE_VORONOI:
        {
          // diagonal entries for each face corner
          // http://www.alecjacobson.com/weblog/?p=874
          MI.resize(m*3,1); MJ.resize(m*3,1); MV.resize(m*3,1);
          MI.block(0*m,0,m,1) = F.col(0);
          MI.block(1*m,0,m,1) = F.col(1);
          MI.block(2*m,0,m,1) = F.col(2);
          MJ = MI;

          // Holy shit this needs to be cleaned up and optimized
          Matrix<Scalar,Dynamic,3> cosines(m,3);
          cosines.col(0) =
            (l.col(2).array().pow(2)+l.col(1).array().pow(2)-l.col(0).array().pow(2))/(l.col(1).array()*l.col(2).array()*2.0);
          cosines.col(1) =
            (l.col(0).array().pow(2)+l.col(2).array().pow(2)-l.col(1).array().pow(2))/(l.col(2).array()*l.col(0).array()*2.0);
          cosines.col(2) =
            (l.col(1).array().pow(2)+l.col(0).array().pow(2)-l.col(2).array().pow(2))/(l.col(0).array()*l.col(1).array()*2.0);
          Matrix<Scalar,Dynamic,3> barycentric = cosines.array() * l.array();
          normalize_row_sums(barycentric,barycentric);
          Matrix<Scalar,Dynamic,3> partial = barycentric;
          partial.col(0).array() *= dblA.array() * 0.5;
          partial.col(1).array() *= dblA.array() * 0.5;
          partial.col(2).array() *= dblA.array() * 0.5;
          Matrix<Scalar,Dynamic,3> quads(partial.rows(),partial.cols());
          quads.col(0) = (partial.col(1)+partial.col(2))*0.5;
          quads.col(1) = (partial.col(2)+partial.col(0))*0.5;
          quads.col(2) = (partial.col(0)+partial.col(1))*0.5;

          quads.col(0) = (cosines.col(0).array()<0).select( 0.25*dblA,quads.col(0));
          quads.col(1) = (cosines.col(0).array()<0).select(0.125*dblA,quads.col(1));
          quads.col(2) = (cosines.col(0).array()<0).select(0.125*dblA,quads.col(2));

          quads.col(0) = (cosines.col(1).array()<0).select(0.125*dblA,quads.col(0));
          quads.col(1) = (cosines.col(1).array()<0).select(0.25*dblA,quads.col(1));
          quads.col(2) = (cosines.col(1).array()<0).select(0.125*dblA,quads.col(2));

          quads.col(0) = (cosines.col(2).array()<0).select(0.125*dblA,quads.col(0));
          quads.col(1) = (cosines.col(2).array()<0).select(0.125*dblA,quads.col(1));
          quads.col(2) = (cosines.col(2).array()<0).select( 0.25*dblA,quads.col(2));

          MV.block(0*m,0,m,1) = quads.col(0);
          MV.block(1*m,0,m,1) = quads.col(1);
          MV.block(2*m,0,m,1) = quads.col(2);

          break;
        }
      case MASSMATRIX_TYPE_FULL:
        assert(false && "Implementation incomplete");
        break;
      default:
        assert(false && "Unknown Mass matrix eff_type");
    }

  }else if(simplex_size == 4)
  {
    assert(V.cols() == 3);
    assert(eff_type == MASSMATRIX_TYPE_BARYCENTRIC);
    MI.resize(m*4,1); MJ.resize(m*4,1); MV.resize(m*4,1);
    MI.block(0*m,0,m,1) = F.col(0);
    MI.block(1*m,0,m,1) = F.col(1);
    MI.block(2*m,0,m,1) = F.col(2);
    MI.block(3*m,0,m,1) = F.col(3);
    MJ = MI;
    // loop over tets
    for(int i = 0;i<m;i++)
    {
      // http://en.wikipedia.org/wiki/Tetrahedron#Volume
      Matrix<Scalar,3,1> v0m3 = V.row(F(i,0)) - V.row(F(i,3));
      Matrix<Scalar,3,1> v1m3 = V.row(F(i,1)) - V.row(F(i,3));
      Matrix<Scalar,3,1> v2m3 = V.row(F(i,2)) - V.row(F(i,3));
      Scalar v = fabs(v0m3.dot(v1m3.cross(v2m3)))/6.0;
      MV(i+0*m) = v/4.0;
      MV(i+1*m) = v/4.0;
      MV(i+2*m) = v/4.0;
      MV(i+3*m) = v/4.0;
    }
  }else
  {
    // Unsupported simplex size
    assert(false && "Unsupported simplex size");
  }
  sparse(MI,MJ,MV,n,n,M);
}

#ifdef IGL_STATIC_LIBRARY
// Explicit template specialization
// generated by autoexplicit.sh
template void igl::massmatrix<Eigen::Matrix<double, -1, 3, 0, -1, 3>, Eigen::Matrix<int, -1, 4, 0, -1, 4>, double>(Eigen::MatrixBase<Eigen::Matrix<double, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase<Eigen::Matrix<int, -1, 4, 0, -1, 4> > const&, igl::MassMatrixType, Eigen::SparseMatrix<double, 0, int>&);
// generated by autoexplicit.sh
template void igl::massmatrix<Eigen::Matrix<double, -1, 3, 0, -1, 3>, Eigen::Matrix<int, -1, 3, 0, -1, 3>, double>(Eigen::MatrixBase<Eigen::Matrix<double, -1, 3, 0, -1, 3> > const&, Eigen::MatrixBase<Eigen::Matrix<int, -1, 3, 0, -1, 3> > const&, igl::MassMatrixType, Eigen::SparseMatrix<double, 0, int>&);
template void igl::massmatrix<Eigen::Matrix<double, -1, 3, 1, -1, 3>, Eigen::Matrix<int, -1, 3, 1, -1, 3>, double>(Eigen::MatrixBase<Eigen::Matrix<double, -1, 3, 1, -1, 3> > const&, Eigen::MatrixBase<Eigen::Matrix<int, -1, 3, 1, -1, 3> > const&, igl::MassMatrixType, Eigen::SparseMatrix<double, 0, int>&);
template void igl::massmatrix<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, double>(Eigen::MatrixBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::MatrixBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, igl::MassMatrixType, Eigen::SparseMatrix<double, 0, int>&);
#endif


/*    This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT.
 *    See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details.
 *    Author(s):       Vincent Rouvreau
 *
 *    Copyright (C) 2014 Inria
 *
 *    Modification(s):
 *      - YYYY/MM Author: Description of the modification
 */

#include <boost/program_options.hpp>
#include <boost/variant.hpp>

#include <gudhi/Alpha_complex_3d.h>
#include <gudhi/Simplex_tree.h>
#include <gudhi/Persistent_cohomology.h>
#include <gudhi/Points_3D_off_io.h>

#include <fstream>
#include <string>
#include <vector>
#include <limits>  // for numeric_limits<>

// gudhi type definition
using Simplex_tree = Gudhi::Simplex_tree<Gudhi::Simplex_tree_options_fast_persistence>;
using Filtration_value = Simplex_tree::Filtration_value;
using Persistent_cohomology =
    Gudhi::persistent_cohomology::Persistent_cohomology<Simplex_tree, Gudhi::persistent_cohomology::Field_Zp>;

void program_options(int argc, char *argv[], std::string &off_file_points, bool &exact, bool &safe,
                     std::string &weight_file, std::string &cuboid_file, std::string &output_file_diag,
                     Filtration_value &alpha_square_max_value, int &coeff_field_characteristic,
                     Filtration_value &min_persistence);

bool read_weight_file(const std::string &weight_file, std::vector<double> &weights) {
  // Read weights information from file
  std::ifstream weights_ifstr(weight_file);
  if (weights_ifstr.good()) {
    double weight = 0.0;
    // Attempt read the weight in a double format, return false if it fails
    while (weights_ifstr >> weight) {
      weights.push_back(weight);
    }
  } else {
    return false;
  }
  return true;
}

bool read_cuboid_file(const std::string &cuboid_file, double &x_min, double &y_min, double &z_min, double &x_max,
                      double &y_max, double &z_max) {
  // Read weights information from file
  std::ifstream iso_cuboid_str(cuboid_file);
  if (iso_cuboid_str.is_open()) {
    if (!(iso_cuboid_str >> x_min >> y_min >> z_min >> x_max >> y_max >> z_max)) {
      return false;
    }
  } else {
    return false;
  }
  return true;
}

template <typename AlphaComplex3d>
std::vector<typename AlphaComplex3d::Bare_point_3> read_off(const std::string &off_file_points) {
  // Read the OFF file (input file name given as parameter) and triangulate points
  Gudhi::Points_3D_off_reader<typename AlphaComplex3d::Bare_point_3> off_reader(off_file_points);
  // Check the read operation was correct
  if (!off_reader.is_valid()) {
    std::cerr << "Unable to read OFF file " << off_file_points << std::endl;
    exit(-1);
  }
  return off_reader.get_point_cloud();
}

int main(int argc, char **argv) {
  std::string off_file_points;
  std::string weight_file;
  std::string cuboid_file;
  std::string output_file_diag;
  Filtration_value alpha_square_max_value = 0.;
  int coeff_field_characteristic = 0;
  Filtration_value min_persistence = 0.;
  bool exact_version = false;
  bool fast_version = false;
  bool weighted_version = false;
  bool periodic_version = false;

  program_options(argc, argv, off_file_points, exact_version, fast_version, weight_file, cuboid_file, output_file_diag,
                  alpha_square_max_value, coeff_field_characteristic, min_persistence);

  std::vector<double> weights;
  if (weight_file != std::string()) {
    if (!read_weight_file(weight_file, weights)) {
      std::cerr << "Unable to read weights file " << weight_file << std::endl;
      exit(-1);
    }
    weighted_version = true;
  }

  double x_min = 0., y_min = 0., z_min = 0., x_max = 0., y_max = 0., z_max = 0.;
  std::ifstream iso_cuboid_str(argv[3]);
  if (cuboid_file != std::string()) {
    if (!read_cuboid_file(cuboid_file, x_min, y_min, z_min, x_max, y_max, z_max)) {
      std::cerr << "Unable to read cuboid file " << cuboid_file << std::endl;
      exit(-1);
    }
    periodic_version = true;
  }

  Gudhi::alpha_complex::complexity complexity = Gudhi::alpha_complex::complexity::SAFE;
  if (exact_version) {
    if (fast_version) {
      std::cerr << "You cannot set the exact and the fast version." << std::endl;
      exit(-1);
    }
    complexity = Gudhi::alpha_complex::complexity::EXACT;
  }
  if (fast_version) {
    complexity = Gudhi::alpha_complex::complexity::FAST;
  }

  Simplex_tree simplex_tree;

  switch (complexity) {
    case Gudhi::alpha_complex::complexity::FAST:
      if (weighted_version) {
        if (periodic_version) {
          using Alpha_complex_3d =
              Gudhi::alpha_complex::Alpha_complex_3d<Gudhi::alpha_complex::complexity::FAST, true, true>;
          auto points = read_off<Alpha_complex_3d>(off_file_points);
          Alpha_complex_3d alpha_complex(points, weights, x_min, y_min, z_min, x_max, y_max, z_max);
          alpha_complex.create_complex(simplex_tree, alpha_square_max_value);
        } else {
          using Alpha_complex_3d =
              Gudhi::alpha_complex::Alpha_complex_3d<Gudhi::alpha_complex::complexity::FAST, true, false>;
          auto points = read_off<Alpha_complex_3d>(off_file_points);
          Alpha_complex_3d alpha_complex(points, weights);
          alpha_complex.create_complex(simplex_tree, alpha_square_max_value);
        }
      } else {
        if (periodic_version) {
          using Alpha_complex_3d =
              Gudhi::alpha_complex::Alpha_complex_3d<Gudhi::alpha_complex::complexity::FAST, false, true>;
          auto points = read_off<Alpha_complex_3d>(off_file_points);
          Alpha_complex_3d alpha_complex(points, x_min, y_min, z_min, x_max, y_max, z_max);
          alpha_complex.create_complex(simplex_tree, alpha_square_max_value);
        } else {
          using Alpha_complex_3d =
              Gudhi::alpha_complex::Alpha_complex_3d<Gudhi::alpha_complex::complexity::FAST, false, false>;
          auto points = read_off<Alpha_complex_3d>(off_file_points);
          Alpha_complex_3d alpha_complex(points);
          alpha_complex.create_complex(simplex_tree, alpha_square_max_value);
        }
      }
      break;
    case Gudhi::alpha_complex::complexity::EXACT:
      if (weighted_version) {
        if (periodic_version) {
          using Alpha_complex_3d =
              Gudhi::alpha_complex::Alpha_complex_3d<Gudhi::alpha_complex::complexity::EXACT, true, true>;
          auto points = read_off<Alpha_complex_3d>(off_file_points);
          Alpha_complex_3d alpha_complex(points, weights, x_min, y_min, z_min, x_max, y_max, z_max);
          alpha_complex.create_complex(simplex_tree, alpha_square_max_value);
        } else {
          using Alpha_complex_3d =
              Gudhi::alpha_complex::Alpha_complex_3d<Gudhi::alpha_complex::complexity::EXACT, true, false>;
          auto points = read_off<Alpha_complex_3d>(off_file_points);
          Alpha_complex_3d alpha_complex(points, weights);
          alpha_complex.create_complex(simplex_tree, alpha_square_max_value);
        }
      } else {
        if (periodic_version) {
          using Alpha_complex_3d =
              Gudhi::alpha_complex::Alpha_complex_3d<Gudhi::alpha_complex::complexity::EXACT, false, true>;
          auto points = read_off<Alpha_complex_3d>(off_file_points);
          Alpha_complex_3d alpha_complex(points, x_min, y_min, z_min, x_max, y_max, z_max);
          alpha_complex.create_complex(simplex_tree, alpha_square_max_value);
        } else {
          using Alpha_complex_3d =
              Gudhi::alpha_complex::Alpha_complex_3d<Gudhi::alpha_complex::complexity::EXACT, false, false>;
          auto points = read_off<Alpha_complex_3d>(off_file_points);
          Alpha_complex_3d alpha_complex(points);
          alpha_complex.create_complex(simplex_tree, alpha_square_max_value);
        }
      }
      break;
    case Gudhi::alpha_complex::complexity::SAFE:
      if (weighted_version) {
        if (periodic_version) {
          using Alpha_complex_3d =
              Gudhi::alpha_complex::Alpha_complex_3d<Gudhi::alpha_complex::complexity::SAFE, true, true>;
          auto points = read_off<Alpha_complex_3d>(off_file_points);
          Alpha_complex_3d alpha_complex(points, weights, x_min, y_min, z_min, x_max, y_max, z_max);
          alpha_complex.create_complex(simplex_tree, alpha_square_max_value);
        } else {
          using Alpha_complex_3d =
              Gudhi::alpha_complex::Alpha_complex_3d<Gudhi::alpha_complex::complexity::SAFE, true, false>;
          auto points = read_off<Alpha_complex_3d>(off_file_points);
          Alpha_complex_3d alpha_complex(points, weights);
          alpha_complex.create_complex(simplex_tree, alpha_square_max_value);
        }
      } else {
        if (periodic_version) {
          using Alpha_complex_3d =
              Gudhi::alpha_complex::Alpha_complex_3d<Gudhi::alpha_complex::complexity::SAFE, false, true>;
          auto points = read_off<Alpha_complex_3d>(off_file_points);
          Alpha_complex_3d alpha_complex(points, x_min, y_min, z_min, x_max, y_max, z_max);
          alpha_complex.create_complex(simplex_tree, alpha_square_max_value);
        } else {
          using Alpha_complex_3d =
              Gudhi::alpha_complex::Alpha_complex_3d<Gudhi::alpha_complex::complexity::SAFE, false, false>;
          auto points = read_off<Alpha_complex_3d>(off_file_points);
          Alpha_complex_3d alpha_complex(points);
          alpha_complex.create_complex(simplex_tree, alpha_square_max_value);
        }
      }
      break;
    default:
      std::cerr << "Unknown complexity value " << std::endl;
      exit(-1);
      break;
  }

  std::clog << "Simplex_tree dim: " << simplex_tree.dimension() << std::endl;
  // Compute the persistence diagram of the complex
  Persistent_cohomology pcoh(simplex_tree, true);
  // initializes the coefficient field for homology
  pcoh.init_coefficients(coeff_field_characteristic);

  pcoh.compute_persistent_cohomology(min_persistence);

  // Output the diagram in filediag
  if (output_file_diag.empty()) {
    pcoh.output_diagram();
  } else {
    std::clog << "Result in file: " << output_file_diag << std::endl;
    std::ofstream out(output_file_diag);
    pcoh.output_diagram(out);
    out.close();
  }

  return 0;
}

void program_options(int argc, char *argv[], std::string &off_file_points, bool &exact, bool &fast,
                     std::string &weight_file, std::string &cuboid_file, std::string &output_file_diag,
                     Filtration_value &alpha_square_max_value, int &coeff_field_characteristic,
                     Filtration_value &min_persistence) {
  namespace po = boost::program_options;
  po::options_description hidden("Hidden options");
  hidden.add_options()("input-file", po::value<std::string>(&off_file_points),
                       "Name of file containing a point set. Format is one point per line:   X1 ... Xd ");

  po::options_description visible("Allowed options", 100);
  visible.add_options()("help,h", "produce help message")(
      "exact,e", po::bool_switch(&exact),
      "To activate exact version of Alpha complex 3d (default is false, not available if fast is set)")(
      "fast,f", po::bool_switch(&fast),
      "To activate fast version of Alpha complex 3d (default is false, not available if exact is set)")(
      "weight-file,w", po::value<std::string>(&weight_file)->default_value(std::string()),
      "Name of file containing a point weights. Format is one weight per line:\n  W1\n  ...\n  Wn ")(
      "cuboid-file,c", po::value<std::string>(&cuboid_file),
      "Name of file describing the periodic domain. Format is:\n  min_hx min_hy min_hz\n  max_hx max_hy max_hz")(
      "output-file,o", po::value<std::string>(&output_file_diag)->default_value(std::string()),
      "Name of file in which the persistence diagram is written. Default print in std::clog")(
      "max-alpha-square-value,r",
      po::value<Filtration_value>(&alpha_square_max_value)
          ->default_value(std::numeric_limits<Filtration_value>::infinity()),
      "Maximal alpha square value for the Alpha complex construction.")(
      "field-charac,p", po::value<int>(&coeff_field_characteristic)->default_value(11),
      "Characteristic p of the coefficient field Z/pZ for computing homology.")(
      "min-persistence,m", po::value<Filtration_value>(&min_persistence),
      "Minimal lifetime of homology feature to be recorded. Default is 0. Enter a negative value to see zero length "
      "intervals");

  po::positional_options_description pos;
  pos.add("input-file", 1);

  po::options_description all;
  all.add(visible).add(hidden);

  po::variables_map vm;
  po::store(po::command_line_parser(argc, argv).options(all).positional(pos).run(), vm);
  po::notify(vm);

  if (vm.count("help") || !vm.count("input-file") || !vm.count("weight-file")) {
    std::clog << std::endl;
    std::clog << "Compute the persistent homology with coefficient field Z/pZ \n";
    std::clog << "of a 3D Alpha complex defined on a set of input points.\n";
    std::clog << "3D Alpha complex can be safe (by default) exact or fast, weighted and/or periodic\n\n";
    std::clog << "The output diagram contains one bar per line, written with the convention: \n";
    std::clog << "   p   dim b d \n";
    std::clog << "where dim is the dimension of the homological feature,\n";
    std::clog << "b and d are respectively the birth and death of the feature and \n";
    std::clog << "p is the characteristic of the field Z/pZ used for homology coefficients.\n\n";

    std::clog << "Usage: " << argv[0] << " [options] input-file weight-file\n\n";
    std::clog << visible << std::endl;
    exit(-1);
  }
}


//=======================================================================
// Copyright 2007 Aaron Windsor
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================
#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/properties.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/ref.hpp>
#include <vector>

#include <boost/graph/boyer_myrvold_planar_test.hpp>
#include <boost/graph/is_kuratowski_subgraph.hpp>

using namespace boost;

int main(int argc, char** argv)
{

  typedef adjacency_list
    < vecS,
      vecS,
      undirectedS,
      property<vertex_index_t, int>,
      property<edge_index_t, int>
    >
    graph;

  // Create a K_6 (complete graph on 6 vertices), which
  // contains both Kuratowski subgraphs as minors.
  graph g(6);
  add_edge(0,1,g);
  add_edge(0,2,g);
  add_edge(0,3,g);
  add_edge(0,4,g);
  add_edge(0,5,g);
  add_edge(1,2,g);
  add_edge(1,3,g);
  add_edge(1,4,g);
  add_edge(1,5,g);
  add_edge(2,3,g);
  add_edge(2,4,g);
  add_edge(2,5,g);
  add_edge(3,4,g);
  add_edge(3,5,g);
  add_edge(4,5,g);

  // Initialize the interior edge index
  property_map<graph, edge_index_t>::type e_index = get(edge_index, g);
  graph_traits<graph>::edges_size_type edge_count = 0;
  graph_traits<graph>::edge_iterator ei, ei_end;
  for(tie(ei, ei_end) = edges(g); ei != ei_end; ++ei)
    put(e_index, *ei, edge_count++);

  // Test for planarity - we know it is not planar, we just want to
  // compute the kuratowski subgraph as a side-effect
  typedef std::vector< graph_traits<graph>::edge_descriptor >
    kuratowski_edges_t;
  kuratowski_edges_t kuratowski_edges;
  if (boyer_myrvold_planarity_test(boyer_myrvold_params::graph = g,
                                   boyer_myrvold_params::kuratowski_subgraph =
                                       std::back_inserter(kuratowski_edges)
                                   )
      )
    std::cout << "Input graph is planar" << std::endl;
  else
    {
      std::cout << "Input graph is not planar" << std::endl;

      std::cout << "Edges in the Kuratowski subgraph: ";
      kuratowski_edges_t::iterator ki, ki_end;
      ki_end = kuratowski_edges.end();
      for(ki = kuratowski_edges.begin(); ki != ki_end; ++ki)
        {
          std::cout << *ki << " ";
        }
      std::cout << std::endl;

      std::cout << "Is a kuratowski subgraph? ";
      if (is_kuratowski_subgraph
          (g, kuratowski_edges.begin(), kuratowski_edges.end())
          )
        std::cout << "Yes." << std::endl;
      else
        std::cout << "No." << std::endl;
    }

  return 0;
}


/******************************************************************************
 *
 * AMDiS - Adaptive multidimensional simulations
 *
 * Copyright (C) 2013 Dresden University of Technology. All Rights Reserved.
 * Web: https://fusionforge.zih.tu-dresden.de/projects/amdis
 *
 * Authors:
 * Simon Vey, Thomas Witkowski, Andreas Naumann, Simon Praetorius, et al.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 *
 * This file is part of AMDiS
 *
 * See also license.opensource.txt in the distribution.
 *
 ******************************************************************************/

// Written by Simon Praetorius

#ifndef ITL_DETAIL_INCLUDE
#define ITL_DETAIL_INCLUDE

#include <boost/math/special_functions/sign.hpp>

namespace itl
{

  namespace details
  {

    /// Compute the Givens rotation matrix parameters for a and b.
    //     template<typename T>
    //     void rotmat(const T& a, const T& b , T& c, T& s)
    //     {
    //       using std::abs; using std::sqrt; using mtl::conj;
    //
    //       const T zero = math::zero(T());
    //       if (a == zero) {
    // 	c = 0.0;
    // 	s = 1.0;
    //       } else {
    // 	double temp = abs(a) / sqrt( conj(a)*a + conj(b)*b );
    // 	c = temp;
    // 	s = temp * (b / a);
    //       }
    //     }

    inline void rotmat(const double& a, const double& b , double& c, double& s)
    {
      using std::abs;
      using std::sqrt;
      if ( b == 0.0 )
      {
        c = 1.0;
        s = 0.0;
      }
      else if ( abs(b) > abs(a) )
      {
        double temp = a / b;
        s = 1.0 / sqrt( 1.0 + temp*temp );
        c = temp * s;
      }
      else
      {
        double temp = b / a;
        c = 1.0 / sqrt( 1.0 + temp*temp );
        s = temp * c;
      }
    }
  }
}

#endif // ITL_DETAIL_INCLUDE


//
// Copyright (c) 2015 Singular Inversions Inc. (facegen.com)
// Use, modification and distribution is subject to the MIT License,
// see accompanying file LICENSE.txt or facegen.com/base_library_license.txt
//
// Authors:     Andrew Beatty
// Created:     Jan 27, 2009
//
// Not currently threadsafe.

#include "stdafx.h"

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>
#include "FgRandom.hpp"
#include "FgDiagnostics.hpp"
#include "FgImage.hpp"
#include "FgImgDisplay.hpp"
#include "FgMath.hpp"
#include "FgAffine1.hpp"
#include "FgMain.hpp"

using namespace std;

struct      RNG
{
    boost::random::mt19937      gen;
    RNG() : gen(42u) {}
};

static RNG rng;

uint32
fgRandUint32()
{return rng.gen(); }

uint
fgRandUint(uint size)
{
    uint    lim = numeric_limits<uint>::max(),
            max = lim - (lim%size),
            ret;
    while ((ret = rng.gen()) > max) {}
    return ret % size;
}

uint64
fgRandUint64()
{
    uint64  hi = rng.gen(),
            lo = rng.gen();
    return (lo + (hi << 32));
}

double
fgRand()
{return double(fgRandUint64()) / double(numeric_limits<uint64>::max()); }

void
fgRandSeedRepeatable(uint seed)
{rng.gen = boost::random::mt19937(seed); }

double
fgRandNormal()
{
    // Polar (Box-Muller) method; See Knuth v2, 3rd ed, p122.
    double  x, y, r2;
    do {
        x = -1 + 2 * fgRand();
        y = -1 + 2 * fgRand();
        // see if it is in the unit circle:
        r2 = x * x + y * y;
    }
    while (r2 > 1.0 || r2 == 0);
    // Box-Muller transform:
    return y * sqrt (-2.0 * log (r2) / r2);
}

FgDbls
fgRandNormals(size_t num,double mean,double stdev)
{
    FgDbls      ret(num);
    for (size_t ii=0; ii<num; ++ii)
        ret[ii] = mean + stdev * fgRandNormal();
    return ret;
}

static
char
randChar()
{
    uint    val = fgRandUint(26+26+10);
    if (val < 10)
        return char(48+val);
    val -= 10;
    if (val < 26)
        return char(65+val);
    val -= 26;
    FGASSERT(val < 26);
    return char(97+val);
}

string
fgRandString(uint numChars)
{
    string  ret;
    for (uint ii=0; ii<numChars; ++ii)
        ret = ret + randChar();
    return ret;
}

void
fgRandomTest(const FgArgs &)
{
    fgout << fgnl << "sizeof(RNG) = " << sizeof(RNG);
    // Create a histogram of normal samples:
    fgRandSeedRepeatable();
    size_t          numStdevs = 6,
                    binsPerStdev = 50,
                    numSamples = 1000000,
                    sz = numStdevs * 2 * binsPerStdev;
    vector<size_t>  histogram(sz,0);
    FgAffine1D      randToHist(FgVectD2(-double(numStdevs),numStdevs),FgVectD2(0,sz));
    for (size_t ii=0; ii<numSamples; ++ii) {
        int         rnd = fgRound(randToHist * fgRandNormal());
        if ((rnd >= 0) && (rnd < int(sz)))
            ++histogram[rnd]; }

    // Make a bar graph of it:
    // numSamples = binScale * stdNormIntegral * binsPerStdev
    double          binScale = double(numSamples) / (fgSqrt_2pi() * binsPerStdev),
                    hgtRatio = 0.9;
    FgImgRgbaUb     img(sz,sz,FgRgbaUB(0));
    for (size_t xx=0; xx<sz; ++xx) {
        size_t      hgt = fgRound(histogram[xx] * sz * hgtRatio / binScale);
        for (size_t yy=0; yy<hgt; ++yy)
            img.xy(xx,yy) = FgRgbaUB(255); }

    // Superimpose a similarly scaled Gaussian:
    FgAffine1D      histToRand = randToHist.inverse();
    for (size_t xx=0; xx<sz; ++xx) {
        double      val = std::exp(-0.5 * fgSqr(histToRand * (xx + 0.5)));
        size_t      hgt = fgRound(val * sz * hgtRatio);
        img.xy(xx,hgt) = FgRgbaUB(255,0,0,255); }

    // Display:
    fgImgFlipVertical(img);
    fgImgDisplay(img);
}

// */


/* boost random/cauchy_distribution.hpp header file
 *
 * Copyright Jens Maurer 2000-2001
 * Permission to use, copy, modify, sell, and distribute this software
 * is hereby granted without fee provided that the above copyright notice
 * appears in all copies and that both that copyright notice and this
 * permission notice appear in supporting documentation,
 *
 * Jens Maurer makes no representations about the suitability of this
 * software for any purpose. It is provided "as is" without express or
 * implied warranty.
 *
 * See http://www.boost.org for most recent version including documentation.
 *
 * $Id: cauchy_distribution.hpp 11696 2001-11-14 21:53:38Z jmaurer $
 *
 * Revision history
 *  2001-02-18  moved to individual header files
 */

#ifndef BOOST_RANDOM_CAUCHY_DISTRIBUTION_HPP
#define BOOST_RANDOM_CAUCHY_DISTRIBUTION_HPP

#include <cmath>
#include <boost/random/uniform_01.hpp>

namespace boost {

#if defined(__GNUC__) && (__GNUC__ < 3)
// Special gcc workaround: gcc 2.95.x ignores using-declarations
// in template classes (confirmed by gcc author Martin v. Loewis)
  using std::tan;
#endif

// Cauchy distribution: p(x) = sigma/(pi*(sigma**2 + (x-median)**2))
template<class UniformRandomNumberGenerator, class RealType = double>
class cauchy_distribution
{
public:
  typedef UniformRandomNumberGenerator base_type;
  typedef RealType result_type;

  cauchy_distribution(base_type & rng, result_type median = 0,
                      result_type sigma = 1)
    : _rng(rng), _median(median), _sigma(sigma) { }
  // compiler-generated copy constructor is fine
  // uniform_01 cannot be assigned, neither can this class
  result_type operator()()
  {
    const double pi = 3.14159265358979323846;
#ifndef BOOST_NO_STDC_NAMESPACE
    using std::tan;
#endif
    return _median + _sigma * tan(pi*(_rng()-0.5));
  }
#ifndef BOOST_NO_OPERATORS_IN_NAMESPACE
  friend bool operator==(const cauchy_distribution& x,
                         const cauchy_distribution& y)
  {
    return x._median == y._median && x._sigma == y._sigma && x._rng == y._rng;
  }
#else
  // Use a member function
  bool operator==(const cauchy_distribution& rhs) const
  {
    return _median == rhs._median && _sigma == rhs._sigma && _rng == rhs._rng;
  }
#endif
private:
  uniform_01<base_type, result_type> _rng;
  result_type _median, _sigma;
};

} // namespace boost

#endif // BOOST_RANDOM_CAUCHY_DISTRIBUTION_HPP