// This file is part of the dune-gdt project:
//   http://users.dune-project.org/projects/dune-gdt
// Copyright holders: Felix Schindler
// License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)

#ifndef DUNE_GDT_PLAYGROUND_OPERATORS_RECONSTRUCTIONS_HH
#define DUNE_GDT_PLAYGROUND_OPERATORS_RECONSTRUCTIONS_HH

#include <boost/numeric/conversion/cast.hpp>

#include <dune/gdt/playground/localevaluation/swipdg.hh>
#include <dune/gdt/operators/fluxreconstruction.hh>

namespace Dune {
namespace GDT {
namespace Operators {

template< class GridViewType, class DiffusionFactorType, class DiffusionTensorType >
class DiffusiveFluxReconstruction
{
  static_assert(GridViewType::dimension == 2, "Only implemented for dimDomain 2 at the moment!");
  static_assert(std::is_base_of< Stuff::IsLocalizableFunction, DiffusionFactorType >::value,
                "DiffusionFactorType has to be tagged as Stuff::IsLocalizableFunction!");
  static_assert(std::is_base_of< Stuff::IsLocalizableFunction, DiffusionTensorType >::value,
                "DiffusionTensorType has to be tagged as Stuff::IsLocalizableFunction!");
public:
  typedef typename GridViewType::template Codim< 0 >::Entity EntityType;
  typedef typename GridViewType::ctype DomainFieldType;
  static const size_t dimDomain = GridViewType::dimension;
  typedef typename DiffusionFactorType::RangeFieldType FieldType;
  typedef typename DiffusionFactorType::DomainType DomainType;

private:
  static_assert(dimDomain == 2, "Not implemented!");

public:
  DiffusiveFluxReconstruction(const GridViewType& grid_view,
                              const DiffusionFactorType& diffusion_factor,
                              const DiffusionTensorType& diffusion_tensor,
                              const size_t over_integrate = 0)
    : grid_view_(grid_view)
    , diffusion_factor_(diffusion_factor)
    , diffusion_tensor_(diffusion_tensor)
    , over_integrate_(over_integrate)
  {}

  template< class GV, class V >
  void apply(const Stuff::LocalizableFunctionInterface< EntityType, DomainFieldType, dimDomain, FieldType, 1 >& source,
             DiscreteFunction< Spaces::RT::PdelabBased< GV, 0, FieldType, dimDomain >, V >& range) const
  {
    const auto& rtn0_space = range.space();
    auto& range_vector = range.vector();
    const FieldType infinity = std::numeric_limits< FieldType >::infinity();
    for (size_t ii = 0; ii < range_vector.size(); ++ii)
      range_vector[ii] = infinity;
    const LocalEvaluation::SWIPDG::Inner< DiffusionFactorType, DiffusionTensorType >
        inner_evaluation(diffusion_factor_, diffusion_tensor_);
    const LocalEvaluation::SWIPDG::BoundaryLHS< DiffusionFactorType, DiffusionTensorType >
        boundary_evaluation(diffusion_factor_, diffusion_tensor_);
    const Stuff::Functions::Constant< EntityType, DomainFieldType, dimDomain, FieldType, 1 > constant_one(1);
    DomainType normal(0);
    DomainType xx_entity(0);
    DynamicMatrix< FieldType > tmp_matrix(1, 1, 0);
    DynamicMatrix< FieldType > tmp_matrix_en_en(1, 1, 0);
    DynamicMatrix< FieldType > tmp_matrix_en_ne(1, 1, 0);
    std::vector< typename Spaces::RT::PdelabBased< GV, 0, FieldType, dimDomain >::BaseFunctionSetType::RangeType >
        basis_values(rtn0_space.mapper().maxNumDofs(),
                     typename Spaces::RT::PdelabBased< GV, 0, FieldType, dimDomain >::BaseFunctionSetType::RangeType(0));
    // walk the grid
    const auto entity_it_end = grid_view_.template end< 0 >();
    for (auto entity_it = grid_view_.template begin< 0 >(); entity_it != entity_it_end; ++entity_it) {
      const auto& entity = *entity_it;
      const auto local_DoF_indices = rtn0_space.local_DoF_indices(entity);
      const auto global_DoF_indices = rtn0_space.mapper().globalIndices(entity);
      assert(global_DoF_indices.size() == local_DoF_indices.size());
      const auto local_diffusion_factor = diffusion_factor_.local_function(entity);
      const auto local_diffusion_tensor = diffusion_tensor_.local_function(entity);
      const auto local_source = source.local_function(entity);
      const auto local_basis = rtn0_space.base_function_set(entity);
      const auto local_constant_one = constant_one.local_function(entity);
      // walk the intersections
      const auto intersection_it_end = grid_view_.iend(entity);
      for (auto intersection_it = grid_view_.ibegin(entity);
           intersection_it != intersection_it_end;
           ++intersection_it) {
        const auto& intersection = *intersection_it;
        if (intersection.neighbor() && !intersection.boundary()) {
          const auto neighbor_ptr = intersection.outside();
          const auto& neighbor = *neighbor_ptr;
          if (grid_view_.indexSet().index(entity) < grid_view_.indexSet().index(neighbor)) {
            const auto local_diffusion_factor_neighbor = diffusion_factor_.local_function(neighbor);
            const auto local_diffusion_tensor_neighbor = diffusion_tensor_.local_function(neighbor);
            const auto local_source_neighbor = source.local_function(neighbor);
            const auto local_constant_one_neighbor = constant_one.local_function(neighbor);
            const size_t local_intersection_index = intersection.indexInInside();
            const size_t local_DoF_index = local_DoF_indices[local_intersection_index];
            // do a face quadrature
            FieldType lhs = 0;
            FieldType rhs = 0;
            const size_t integrand_order = inner_evaluation.order(*local_diffusion_factor,
                                                                  *local_diffusion_tensor,
                                                                  *local_diffusion_factor_neighbor,
                                                                  *local_diffusion_tensor_neighbor,
                                                                  *local_constant_one,
                                                                  *local_source,
                                                                  *local_constant_one_neighbor,
                                                                  *local_source_neighbor);
            const auto& quadrature = QuadratureRules< DomainFieldType, dimDomain - 1 >::rule(
                  intersection.type(), boost::numeric_cast< int >(integrand_order + over_integrate_));
            const auto quadrature_it_end = quadrature.end();
            for (auto quadrature_it = quadrature.begin(); quadrature_it != quadrature_it_end; ++quadrature_it) {
              const auto& xx_intersection = quadrature_it->position();
              xx_entity = intersection.geometryInInside().global(xx_intersection);
              normal = intersection.unitOuterNormal(xx_intersection);
              const FieldType integration_factor = intersection.geometry().integrationElement(xx_intersection);
              const FieldType weigth = quadrature_it->weight();
              // evalaute
              local_basis.evaluate(xx_entity, basis_values);
              const auto& basis_value = basis_values[local_DoF_index];
              tmp_matrix *= 0.0;
              tmp_matrix_en_en *= 0.0;
              tmp_matrix_en_ne *= 0.0;
              inner_evaluation.evaluate(*local_diffusion_factor,
                                        *local_diffusion_tensor,
                                        *local_diffusion_factor_neighbor,
                                        *local_diffusion_tensor_neighbor,
                                        *local_constant_one,
                                        *local_source,
                                        *local_constant_one_neighbor,
                                        *local_source_neighbor,
                                        intersection,
                                        xx_intersection,
                                        tmp_matrix_en_en, // <- we are interested in this one
                                        tmp_matrix,
                                        tmp_matrix_en_ne, // <- and this one
                                        tmp_matrix);
              // compute integrals
              assert(tmp_matrix_en_en.rows() >= 1);
              assert(tmp_matrix_en_en.cols() >= 1);
              assert(tmp_matrix_en_ne.rows() >= 1);
              assert(tmp_matrix_en_ne.cols() >= 1);
              lhs += integration_factor * weigth * (basis_value * normal);
              rhs += integration_factor * weigth * (tmp_matrix_en_en[0][0] + tmp_matrix_en_ne[0][0]);
            } // do a face quadrature
            // set DoF
            const size_t global_DoF_index = global_DoF_indices[local_DoF_index];
            // and make sure we are the first to do so
            assert(!(range_vector[global_DoF_index] < infinity));
            range_vector[global_DoF_index] = rhs / lhs;
          }
        } else if (intersection.boundary() && !intersection.neighbor()) {
          const size_t local_intersection_index = intersection.indexInInside();
          const size_t local_DoF_index = local_DoF_indices[local_intersection_index];
          // do a face quadrature
          FieldType lhs = 0;
          FieldType rhs = 0;
          const size_t integrand_order = boundary_evaluation.order(*local_diffusion_factor,
                                                                   *local_diffusion_tensor,
                                                                   *local_source,
                                                                   *local_constant_one);
          const auto& quadrature = QuadratureRules< DomainFieldType, dimDomain - 1 >::rule(
                intersection.type(), boost::numeric_cast< int >(integrand_order + over_integrate_));
          const auto quadrature_it_end = quadrature.end();
          for (auto quadrature_it = quadrature.begin(); quadrature_it != quadrature_it_end; ++quadrature_it) {
            const auto xx_intersection = quadrature_it->position();
            normal = intersection.unitOuterNormal(xx_intersection);
            const FieldType integration_factor = intersection.geometry().integrationElement(xx_intersection);
            const FieldType weigth = quadrature_it->weight();
            xx_entity = intersection.geometryInInside().global(xx_intersection);
            // evalaute
            local_basis.evaluate(xx_entity, basis_values);
            const auto& basis_value = basis_values[local_DoF_index];
            tmp_matrix *= 0.0;
            boundary_evaluation.evaluate(*local_diffusion_factor,
                                         *local_diffusion_tensor,
                                         *local_constant_one,
                                         *local_source,
                                         intersection,
                                         xx_intersection,
                                         tmp_matrix);
            // compute integrals
            assert(tmp_matrix.rows() >= 1);
            assert(tmp_matrix.cols() >= 1);
            lhs += integration_factor * weigth * (basis_value * normal);
            rhs += integration_factor * weigth * tmp_matrix[0][0];
          } // do a face quadrature
          // set DoF
          const size_t global_DoF_index = global_DoF_indices[local_DoF_index];
          assert(!(range_vector[global_DoF_index] < infinity));
          // and make sure we are the first to do so
          range_vector[global_DoF_index] = rhs / lhs;
        } else
          DUNE_THROW(Stuff::Exceptions::internal_error, "Unknown intersection type!");
      } // walk the intersections
    } // walk the grid
  } // ... apply(...)

private:
  const GridViewType& grid_view_;
  const DiffusionFactorType& diffusion_factor_;
  const DiffusionTensorType& diffusion_tensor_;
  const size_t over_integrate_;
}; // class DiffusiveFluxReconstruction

template< class GV, class DF, class DT >
DiffusiveFluxReconstruction< GV, DF, DT > make_diffusive_flux_reconstruction(const GV& grid_view,
                                                                             const DF& diffusion_factor,
                                                                             const DT& diffusion_tensor,
                                                                             const size_t over_integrate = 0)
{
  return DiffusiveFluxReconstruction< GV, DF, DT >(grid_view, diffusion_factor, diffusion_tensor, over_integrate);
}

} // namespace Operators
} // namespace GDT
} // namespace Dune

#endif // DUNE_GDT_PLAYGROUND_OPERATORS_RECONSTRUCTIONS_HH


/*
DD-based simulator by JKU Linz, Austria

Developer: Alwin Zulehner, Robert Wille

With code from the QMDD implementation provided by Michael Miller (University of Victoria, Canada)
and Philipp Niemann (University of Bremen, Germany).

For more information, please visit http://iic.jku.at/eda/research/quantum_simulation

If you have any questions feel free to contact us using
alwin.zulehner@jku.at or robert.wille@jku.at

If you use the quantum simulator for your research, we would be thankful if you referred to it
by citing the following publication:

@article{zulehner2018simulation,
    title={Advanced Simulation of Quantum Computations},
    author={Zulehner, Alwin and Wille, Robert},
    journal={IEEE Transactions on Computer Aided Design of Integrated Circuits and Systems (TCAD)},
    year={2018},
    eprint = {arXiv:1707.00865}
}

Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:

The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.

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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

*/

#include <iostream>
#include <stdlib.h>
#include <set>
#include <unordered_map>
#include <boost/program_options.hpp>
#include <vector>
#include <gmp.h>
#include <mpreal.h>
#include <QASMsimulator.h>
#include <queue>
#include <chrono>
#include <QMDDcore.h>
#include <QMDDpackage.h>
#include <QMDDcomplex.h>

#include <Simulator.h>
//#include <QASMscanner.hpp>

using mpfr::mpreal;

using namespace std;

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

	namespace po = boost::program_options;
	po::options_description description("Allowed options");
	description.add_options()
	    ("help", "produce help message")
		("seed", po::value<unsigned long>(), "seed for random number generator")
	    ("simulate_qasm", po::value<string>()->implicit_value(""), "simulate a quantum circuit given in QPENQASM 2.0 format (if no file is given, the circuit is read from stdin)")
		("shots", po::value<unsigned int>(), "number of shots")
		("ps", "print simulation stats (applied gates, sim. time, and maximal size of the DD)")
		("display_statevector", "adds the state-vector to snapshots")
		("display_probabilities", "adds the probabilities of the basis states to snapshots")
		("precision", po::value<double>(), "two numbers are treated to be equal if their difference is smaller than this value")
	;

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

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

	unsigned long seed = time(NULL);
	if (vm.count("seed")) {
		seed = vm["seed"].as<unsigned long>();
	}

	srand(seed);

	QMDDinit(0);

	if (vm.count("precision")) {
#if VERBOSE
		std::cout << "Set precision to " << vm["precision"].as<double>() << std::endl;
#endif
		Ctol = mpreal(vm["precision"].as<double>());
	}

	Simulator* simulator;

	if (vm.count("simulate_qasm")) {
		string fname = vm["simulate_qasm"].as<string>();
		if(fname == "") {
			simulator = new QASMsimulator(vm.count("display_statevector"), vm.count("display_probabilities"));
		} else {
			simulator = new QASMsimulator(fname, vm.count("display_statevector"), vm.count("display_probabilities"));
		}
	} else {
		cout << description << "\n";
	    return 1;
	}

    auto t1 = chrono::high_resolution_clock::now();

	if(vm.count("shots")) {
		simulator->Simulate(vm["shots"].as<unsigned int>());
	} else {
		simulator->Simulate(1);
	}

	auto t2 = chrono::high_resolution_clock::now();
	chrono::duration<float> diff = t2-t1;

	delete simulator;

	if (vm.count("ps")) {
		cout << endl << "SIMULATION STATS: " << endl;
		cout << "  Number of applied gates: " << simulator->GetGatecount() << endl;
		cout << "  Simulation time: " << diff.count() << " seconds" << endl;
		cout << "  Maximal size of DD (number of nodes) during simulation: " << simulator->GetMaxActive() << endl;
	}

	return 0;
}


#define BOOST_TEST_MODULE test_aig
#include <boost/test/unit_test.hpp>

//internal includes
#include <metaSMT/frontend/Logic.hpp>
#include <metaSMT/DirectSolver_Context.hpp>
#include <metaSMT/backend/SAT_Aiger.hpp>

//external includes
#include <boost/array.hpp>
#include <boost/proto/debug.hpp>
#include <boost/proto/fusion.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/foreach.hpp>

#include <iostream>

using namespace metaSMT;

struct clause_printer
{
  void clause ( std::vector < SAT::tag::lit_tag > const& clause )
  {
    BOOST_FOREACH ( SAT::tag::lit_tag const& lit, clause )
      std::cout << lit.id << " ";
    std::cout << std::endl;
  }

  void assertion ( SAT::tag::lit_tag const& lit )
  {
    std::cout << "Assertion: " << lit.id << std::endl;
  }
  void assumption ( SAT::tag::lit_tag const& lit )
  {
    std::cout << "Assumption: " << lit.id << std::endl;
  }

  bool solve ()
  {
    return false;
  }

};

class aig_Fixture {
  public:
  protected:
    DirectSolver_Context < SAT_Aiger < clause_printer > > ctx;
};

using boost::proto::display_expr;

BOOST_FIXTURE_TEST_SUITE(aig_t, aig_Fixture )

BOOST_AUTO_TEST_CASE( syntax )
{
  using namespace logic;
  BOOST_REQUIRE_EQUAL( evaluate (ctx, False ), aiger_false);
  BOOST_REQUIRE_EQUAL( evaluate (ctx, True ), aiger_true);

  BOOST_REQUIRE_EQUAL( evaluate (ctx, Not(True) ), aiger_false);
  BOOST_REQUIRE_EQUAL( evaluate (ctx, Not(False) ), aiger_true);

  BOOST_REQUIRE_EQUAL( evaluate (ctx, Not(Not(True) )), aiger_true);
  BOOST_REQUIRE_EQUAL( evaluate (ctx, Not(Not(False)) ), aiger_false);

  predicate x = new_variable ();
  predicate y = new_variable ();
  assertion ( ctx, And ( x, y) );

  bool r = solve ( ctx );

//    BOOST_REQUIRE_EQUAL(to_aiger( True, aig )()(),aiger_true);
//
//   display_expr( Not(False));
//   BOOST_REQUIRE_EQUAL(to_aiger ( Not(False), aig)( )(), aiger_true);
//   BOOST_REQUIRE_EQUAL(to_aiger ( Not(True ), aig)(), aiger_false);
//
//   BOOST_REQUIRE_EQUAL(to_aiger( Not(Not(False)), aig )(), aiger_false);
//   BOOST_REQUIRE_EQUAL(to_aiger( Not(Not(True)), aig )(), aiger_true);
}

BOOST_AUTO_TEST_SUITE_END() //QF_BV

//  vim: ft=cpp:ts=2:sw=2:expandtab


/**
 * \ file GainColoredExpanderFilter.cpp
 */

#include <ATK/Dynamic/GainColoredExpanderFilter.h>

#include <ATK/Core/InPointerFilter.h>
#include <ATK/Core/OutPointerFilter.h>

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>

#include <boost/math/constants/constants.hpp>
#include <boost/scoped_array.hpp>

#define PROCESSSIZE (64)

BOOST_AUTO_TEST_CASE( GainColoredExpanderFilter_const_1_test )
{
  boost::scoped_array<float> data(new float[PROCESSSIZE]);
  for(int64_t i = 0; i < PROCESSSIZE; ++i)
  {
    data[i] = 1;
  }

  ATK::InPointerFilter<float> generator(data.get(), 1, PROCESSSIZE, false);
  generator.set_output_sampling_rate(48000);

  boost::scoped_array<float> outdata(new float[PROCESSSIZE]);

  ATK::GainColoredExpanderFilter<float> filter(1);
  filter.set_input_sampling_rate(48000);
  filter.set_input_port(0, &generator, 0);
  filter.set_threshold(10);

  ATK::OutPointerFilter<float> output(outdata.get(), 1, PROCESSSIZE, false);
  output.set_input_sampling_rate(48000);
  output.set_input_port(0, &filter, 0);

  output.process(PROCESSSIZE);

  for(int64_t i = 0; i < PROCESSSIZE; ++i)
  {
    BOOST_REQUIRE_CLOSE(1, outdata[i], 0.1);
  }
}

BOOST_AUTO_TEST_CASE( GainColoredExpanderFilter_const_0_test )
{
  boost::scoped_array<float> data(new float[PROCESSSIZE]);
  for(int64_t i = 0; i < PROCESSSIZE; ++i)
  {
    data[i] = 0;
  }

  ATK::InPointerFilter<float> generator(data.get(), 1, PROCESSSIZE, false);
  generator.set_output_sampling_rate(48000);

  boost::scoped_array<float> outdata(new float[PROCESSSIZE]);

  ATK::GainColoredExpanderFilter<float> filter(1);
  filter.set_input_sampling_rate(48000);
  filter.set_input_port(0, &generator, 0);

  ATK::OutPointerFilter<float> output(outdata.get(), 1, PROCESSSIZE, false);
  output.set_input_sampling_rate(48000);
  output.set_input_port(0, &filter, 0);

  output.process(PROCESSSIZE);

  for(int64_t i = 0; i < PROCESSSIZE; ++i)
  {
    BOOST_REQUIRE_SMALL(outdata[i], 0.001f);
  }
}

BOOST_AUTO_TEST_CASE( GainColoredExpanderFilter_const_1_threshold_2_ratio_2_test )
{
  boost::scoped_array<float> data(new float[PROCESSSIZE]);
  for(int64_t i = 0; i < PROCESSSIZE; ++i)
  {
    data[i] = 1;
  }

  ATK::InPointerFilter<float> generator(data.get(), 1, PROCESSSIZE, false);
  generator.set_output_sampling_rate(48000);

  boost::scoped_array<float> outdata(new float[PROCESSSIZE]);

  ATK::GainColoredExpanderFilter<float> filter(1);
  filter.set_input_sampling_rate(48000);
  filter.set_input_port(0, &generator, 0);
  filter.set_threshold(2);
  filter.set_ratio(2);
  filter.set_softness(1);

  ATK::OutPointerFilter<float> output(outdata.get(), 1, PROCESSSIZE, false);
  output.set_input_sampling_rate(48000);
  output.set_input_port(0, &filter, 0);

  output.process(PROCESSSIZE);

  for(int64_t i = 0; i < PROCESSSIZE; ++i)
  {
    BOOST_REQUIRE_CLOSE(0.700553358, outdata[i], 0.1);
  }
}

BOOST_AUTO_TEST_CASE( GainColoredExpanderFilter_const_1_threshold_2_ratio_4_test )
{
  boost::scoped_array<float> data(new float[PROCESSSIZE]);
  for(int64_t i = 0; i < PROCESSSIZE; ++i)
  {
    data[i] = 1;
  }

  ATK::InPointerFilter<float> generator(data.get(), 1, PROCESSSIZE, false);
  generator.set_output_sampling_rate(48000);

  boost::scoped_array<float> outdata(new float[PROCESSSIZE]);

  ATK::GainColoredExpanderFilter<float> filter(1);
  filter.set_input_sampling_rate(48000);
  filter.set_input_port(0, &generator, 0);
  filter.set_threshold(2);
  filter.set_ratio(4);
  filter.set_softness(1);

  ATK::OutPointerFilter<float> output(outdata.get(), 1, PROCESSSIZE, false);
  output.set_input_sampling_rate(48000);
  output.set_input_port(0, &filter, 0);

  output.process(PROCESSSIZE);

  for(int64_t i = 0; i < PROCESSSIZE; ++i)
  {
    BOOST_REQUIRE_CLOSE(0.343814075, outdata[i], 0.1);
  }
}

BOOST_AUTO_TEST_CASE( GainColoredExpanderFilter_always_more_1_test )
{
  boost::scoped_array<float> data(new float[PROCESSSIZE]);
  for(int64_t i = 0; i < PROCESSSIZE; ++i)
  {
    data[i] = i/1024.;
  }

  ATK::InPointerFilter<float> generator(data.get(), 1, PROCESSSIZE, false);
  generator.set_output_sampling_rate(48000);

  boost::scoped_array<float> outdata(new float[PROCESSSIZE]);

  ATK::GainColoredExpanderFilter<float> filter(1);
  filter.set_input_sampling_rate(48000);
  filter.set_input_port(0, &generator, 0);
  filter.set_threshold(1);
  filter.set_quality(.1);
  filter.set_color(.1);

  ATK::OutPointerFilter<float> output(outdata.get(), 1, PROCESSSIZE, false);
  output.set_input_sampling_rate(48000);
  output.set_input_port(0, &filter, 0);

  output.process(PROCESSSIZE);

  for(int64_t i = 1; i < PROCESSSIZE; ++i)
  {
    BOOST_REQUIRE_GE(outdata[i], 1);
  }
}

BOOST_AUTO_TEST_CASE( GainColoredExpanderFilter_always_less_1_test )
{
  boost::scoped_array<float> data(new float[PROCESSSIZE]);
  for(int64_t i = 0; i < PROCESSSIZE; ++i)
  {
    data[i] = i/1024.;
  }

  ATK::InPointerFilter<float> generator(data.get(), 1, PROCESSSIZE, false);
  generator.set_output_sampling_rate(48000);

  boost::scoped_array<float> outdata(new float[PROCESSSIZE]);

  ATK::GainColoredExpanderFilter<float> filter(1);
  filter.set_input_sampling_rate(48000);
  filter.set_input_port(0, &generator, 0);
  filter.set_threshold(1);
  filter.set_quality(.1);
  filter.set_color(-.1);

  ATK::OutPointerFilter<float> output(outdata.get(), 1, PROCESSSIZE, false);
  output.set_input_sampling_rate(48000);
  output.set_input_port(0, &filter, 0);

  output.process(PROCESSSIZE);

  for(int64_t i = 0; i < PROCESSSIZE; ++i)
  {
    BOOST_REQUIRE_LE(outdata[i], 1);
  }
}


/*
 * Copyright 2018 James Dyer
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 * This is being developed for the TANGO Project: http://tango-project.eu
 */

#include <jsoncpp/json/json.h>
#include <NTL/ZZ.h>
#include "GACDDecrypter.h"

NTL::ZZ GACDDecrypter::decrypt(NTL::ZZ& ciphertext){
	return ciphertext/k;
}

void GACDDecrypter::readSecretsFromJSON(std::string& secrets){
	Json::Value root;
	Json::Reader reader;
	bool parsingSuccessful = reader.parse(secrets,root);
	if (parsingSuccessful){
		k = NTL::conv<NTL::ZZ>(root["key"].asCString());
	}
}


#include <bsplines/python/DiffManifoldBSplineExporter.hpp>
#include <aslam/splines/OPTUnitQuaternionBSpline.hpp>
#include <aslam/splines/OPTEuclideanBSpline.hpp>
#include <aslam/splines/OPTEuclideanBSpline.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include <vector>

typedef std::vector<aslam::backend::DesignVariable *> DesignVariables;

using namespace bspline_exporter;
using namespace bsplines;

template <typename Spline>
void addOptSplineFunctions (BSplineExporter<Spline> & splineExporter){
  using namespace boost::python;
  splineExporter.getSplineClass()
      .def("getDesignVariables", static_cast<const DesignVariables &(Spline::*)() >(&Spline::getDesignVariables), "Get a list of all design variables", return_value_policy<copy_const_reference>())
      ;
}

void exportOptBSplines() {
  class_<DesignVariables>("DesignVariables")
      .def(boost::python::vector_indexing_suite<DesignVariables>() );

  auto euclidBSpl = BSplineExporter<aslam::splines::OPTBSpline<EuclideanBSpline<>::CONF>::BSpline>::exportEuclideanSpline("OptEuclideanBSpline");
  addOptSplineFunctions(euclidBSpl);
  auto unitQuatBSpl = BSplineExporter<aslam::splines::OPTBSpline<UnitQuaternionBSpline<>::CONF>::BSpline>::exportUnitQuaternionSpline("OptUnitQuaternionBSpline");
  addOptSplineFunctions(unitQuatBSpl);
}


#include "hashable_statespace.hh"

#include <boost/container_hash/hash.hpp>

namespace planner::util {
HashableStateSpace::StateType::StateType(const StateType& x) {
  space_ = x.space_;
  space_->allocStateComponents(this);
  space_->copyState(this, &x);
}

ob::State* HashableStateSpace::allocState() const {
  auto* state = new StateType();
  allocStateComponents(state);
  state->space_ = this;
  return static_cast<ob::State*>(state);
}

std::size_t hash_value(const HashableStateSpace::StateType& x) { return x.space_->computeHash(x); }

RobotBaseSpace::RobotBaseSpace() {
  setName("RobotBase" + getName());
  addSubspace(std::make_shared<ob::RealVectorStateSpace>(3), 1.0);
  addSubspace(std::make_shared<ob::SO2StateSpace>(), 1.0);
  lock();
}

std::size_t hash_value(const RobotBaseSpace::StateType& x) {
  size_t hash_val = 0;
  boost::hash_combine(hash_val, x.rotation());
  return hash_val;
}

void swap(HashableStateSpace::StateType& a, HashableStateSpace::StateType& b) {
  std::swap(a.components, b.components);
}

void HashableStateSpace::copyState(ob::State* destination, const ob::State* source) const {
  ob::CompoundStateSpace::copyState(destination, source);
  auto* cdest         = destination->as<StateType>();
  const auto* csource = source->as<StateType>();
  cdest->action       = csource->action;
  cdest->object_poses = csource->object_poses;
  cdest->space_       = csource->space_;
  cdest->sg           = csource->sg;
}
}  // namespace planner::util

namespace ompl::base {
std::size_t hash_value(const SO3StateSpace::StateType& x) {
  size_t hash_val = 0;
  boost::hash_combine(hash_val, x.x);
  boost::hash_combine(hash_val, x.y);
  boost::hash_combine(hash_val, x.z);
  boost::hash_combine(hash_val, x.w);
  return hash_val;
}

std::size_t hash_value(const SO2StateSpace::StateType& x) { return boost::hash_value(x.value); }

std::size_t hash_value(const DiscreteStateSpace::StateType& x) {
  return boost::hash_value(x.value);
}

std::size_t hash_value(const SE3StateSpace::StateType& x) {
  size_t hash_val = 0;
  boost::hash_combine(hash_val, x.getX());
  boost::hash_combine(hash_val, x.getY());
  boost::hash_combine(hash_val, x.getZ());
  boost::hash_combine(hash_val, x.rotation());
  return hash_val;
}
}  // namespace ompl::base


/*
@copyright Louis Dionne 2014
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#include <boost/hana/identity.hpp>

#include <boost/hana/detail/assert.hpp>
#include <boost/hana/functional.hpp>
#include <boost/hana/monad/laws.hpp>

#include "unique_comparable.hpp"
#include "unique_function.hpp"
using namespace boost::hana;

auto x = unique_comparable([]{});
auto y = unique_comparable([]{});
auto f = compose(identity, unique_function([]{}));
auto g = compose(identity, unique_function([]{}));

BOOST_HANA_CONSTANT_ASSERT(equal(flatten(identity(identity(x))), identity(x)));
BOOST_HANA_CONSTANT_ASSERT(Monad::laws::check(identity(x), y, f, g));

int main() { }


/*
    This file is part of Nori, a simple educational ray tracer

    Copyright (c) 2015 by Wenzel Jakob

    Nori is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License Version 3
    as published by the Free Software Foundation.

    Nori 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <nori/warp.h>
#include <nori/bsdf.h>
#include <nori/vector.h>
#include <nanogui/screen.h>
#include <nanogui/label.h>
#include <nanogui/window.h>
#include <nanogui/layout.h>
#include <nanogui/icons.h>
#include <nanogui/combobox.h>
#include <nanogui/slider.h>
#include <nanogui/textbox.h>
#include <nanogui/checkbox.h>
#include <nanogui/messagedialog.h>
#include <nanogui/renderpass.h>
#include <nanogui/shader.h>
#include <nanogui/texture.h>
#include <nanogui/screen.h>
#include <nanogui/opengl.h>
#include <nanogui/window.h>

#include <nanovg_gl.h>

#include <pcg32.h>
#include <hypothesis.h>
#include <tinyformat.h>

#include <Eigen/Geometry>

/* =======================================================================
 *   WARNING    WARNING    WARNING    WARNING    WARNING    WARNING
 * =======================================================================
 *   Remember to put on SAFETY GOGGLES before looking at this file. You
 *   are most certainly not expected to read or understand any of it.
 * ======================================================================= */

#if defined(_MSC_VER)
#  pragma warning (disable: 4305 4244)
#endif

using namespace nanogui;
using namespace std;

using nori::NoriException;
using nori::NoriObjectFactory;
using nori::Point2f;
using nori::Point2i;
using nori::Point3f;
using nori::Warp;
using nori::PropertyList;
using nori::BSDF;
using nori::BSDFQueryRecord;
using nori::Color3f;

enum PointType : int {
    Independent = 0,
    Grid,
    Stratified
};

enum WarpType : int {
    Square = 0,
    Tent,
    Disk,
    UniformSphere,
    UniformHemisphere,
    CosineHemisphere,
    Beckmann,
    MicrofacetBRDF,
    WarpTypeCount
};

static const std::string kWarpTypeNames[WarpTypeCount] = {
    "square", "tent", "disk", "uniform_sphere", "uniform_hemisphere",
    "cosine_hemisphere", "beckmann", "microfacet_brdf"
};

struct WarpTest {
    static const int kDefaultXres = 51;
    static const int kDefaultYres = 51;

    WarpType warpType;
    float parameterValue;
    BSDF *bsdf;
    BSDFQueryRecord bRec;
    int xres, yres, res;

    // Observed and expected frequencies, initialized after calling run().
    std::unique_ptr<double[]> obsFrequencies, expFrequencies;

    WarpTest(WarpType warpType_, float parameterValue_, BSDF *bsdf_ = nullptr,
             BSDFQueryRecord bRec_ = BSDFQueryRecord(nori::Vector3f()),
             int xres_ = kDefaultXres, int yres_ = kDefaultYres)
        : warpType(warpType_), parameterValue(parameterValue_), bsdf(bsdf_),
          bRec(bRec_), xres(xres_), yres(yres_) {

        if (warpType != Square && warpType != Disk && warpType != Tent)
            xres *= 2;
        res = xres * yres;
    }

    std::pair<bool, std::string> run() {
        int sampleCount = 1000 * res;
        obsFrequencies.reset(new double[res]);
        expFrequencies.reset(new double[res]);
        memset(obsFrequencies.get(), 0, res*sizeof(double));
        memset(expFrequencies.get(), 0, res*sizeof(double));

        nori::MatrixXf points, values;
        generatePoints(sampleCount, Independent, points, values);

        for (int i=0; i<sampleCount; ++i) {
            if (values(0, i) == 0)
                continue;
            nori::Vector3f sample = points.col(i);
            float x, y;

            if (warpType == Square) {
                x = sample.x();
                y = sample.y();
            } else if (warpType == Disk || warpType == Tent) {
                x = sample.x() * 0.5f + 0.5f;
                y = sample.y() * 0.5f + 0.5f;
            } else {
                x = std::atan2(sample.y(), sample.x()) * INV_TWOPI;
                if (x < 0)
                    x += 1;
                y = sample.z() * 0.5f + 0.5f;
            }

            int xbin = std::min(xres-1, std::max(0, (int) std::floor(x * xres)));
            int ybin = std::min(yres-1, std::max(0, (int) std::floor(y * yres)));
            obsFrequencies[ybin * xres + xbin] += 1;
        }

        auto integrand = [&](double y, double x) -> double {
            if (warpType == Square) {
                return Warp::squareToUniformSquarePdf(Point2f(x, y));
            } else if (warpType == Disk) {
                x = x * 2 - 1; y = y * 2 - 1;
                return Warp::squareToUniformDiskPdf(Point2f(x, y));
            } else if (warpType == Tent) {
                x = x * 2 - 1; y = y * 2 - 1;
                return Warp::squareToTentPdf(Point2f(x, y));
            } else {
                x *= 2 * M_PI;
                y = y * 2 - 1;

                double sinTheta = std::sqrt(1 - y * y);
                double sinPhi = std::sin(x),
                       cosPhi = std::cos(x);

                nori::Vector3f v((float) (sinTheta * cosPhi),
                           (float) (sinTheta * sinPhi),
                           (float) y);

                if (warpType == UniformSphere)
                    return Warp::squareToUniformSpherePdf(v);
                else if (warpType == UniformHemisphere)
                    return Warp::squareToUniformHemispherePdf(v);
                else if (warpType == CosineHemisphere)
                    return Warp::squareToCosineHemispherePdf(v);
                else if (warpType == Beckmann)
                    return Warp::squareToBeckmannPdf(v, parameterValue);
                else if (warpType == MicrofacetBRDF) {
                    BSDFQueryRecord br(bRec);
                    br.wo = v;
                    br.measure = nori::ESolidAngle;
                    return bsdf->pdf(br);
                } else {
                    throw NoriException("Invalid warp type");
                }
            }
        };

        double scale = sampleCount;
        if (warpType == Square)
            scale *= 1;
        else if (warpType == Disk || warpType == Tent)
            scale *= 4;
        else
            scale *= 4*M_PI;

        double *ptr = expFrequencies.get();
        for (int y=0; y<yres; ++y) {
            double yStart =  y    / (double) yres;
            double yEnd   = (y+1) / (double) yres;
            for (int x=0; x<xres; ++x) {
                double xStart =  x    / (double) xres;
                double xEnd   = (x+1) / (double) xres;
                ptr[y * xres + x] = hypothesis::adaptiveSimpson2D(
                    integrand, yStart, xStart, yEnd, xEnd) * scale;
                if (ptr[y * xres + x] < 0)
                    throw NoriException("The Pdf() function returned negative values!");
            }
        }

        /* Write the test input data to disk for debugging */
        hypothesis::chi2_dump(yres, xres, obsFrequencies.get(), expFrequencies.get(), "chitest.m");

        /* Perform the Chi^2 test */
        const int minExpFrequency = 5;
        const float significanceLevel = 0.01f;

        return hypothesis::chi2_test(yres*xres, obsFrequencies.get(),
                                     expFrequencies.get(), sampleCount,
                                     minExpFrequency, significanceLevel, 1);
    }

    std::pair<Point3f, float> warpPoint(const Point2f &sample) {
        Point3f result;

        switch (warpType) {
            case Square:
                result << Warp::squareToUniformSquare(sample), 0; break;
            case Tent:
                result << Warp::squareToTent(sample), 0; break;
            case Disk:
                result << Warp::squareToUniformDisk(sample), 0; break;
            case UniformSphere:
                result << Warp::squareToUniformSphere(sample); break;
            case UniformHemisphere:
                result << Warp::squareToUniformHemisphere(sample); break;
            case CosineHemisphere:
                result << Warp::squareToCosineHemisphere(sample); break;
            case Beckmann:
                result << Warp::squareToBeckmann(sample, parameterValue); break;
            case MicrofacetBRDF: {
                BSDFQueryRecord br(bRec);
                float value = bsdf->sample(br, sample).getLuminance();
                return std::make_pair(
                    br.wo,
                    value == 0 ? 0.f : bsdf->eval(br)[0]
                );
             }
             default:
                throw std::runtime_error("Unsupported warp type.");
        }

        return std::make_pair(result, 1.f);
    }

    void generatePoints(int &pointCount, PointType pointType,
                        nori::MatrixXf &positions, nori::MatrixXf &weights) {
        /* Determine the number of points that should be sampled */
        int sqrtVal = (int) (std::sqrt((float) pointCount) + 0.5f);
        float invSqrtVal = 1.f / sqrtVal;
        if (pointType == Grid || pointType == Stratified)
            pointCount = sqrtVal*sqrtVal;

        pcg32 rng;
        positions.resize(3, pointCount);
        weights.resize(1, pointCount);

        for (int i=0; i<pointCount; ++i) {
            int y = i / sqrtVal, x = i % sqrtVal;
            Point2f sample;

            switch (pointType) {
                case Independent:
                    sample = Point2f(rng.nextFloat(), rng.nextFloat());
                    break;

                case Grid:
                    sample = Point2f((x + 0.5f) * invSqrtVal, (y + 0.5f) * invSqrtVal);
                    break;

                case Stratified:
                    sample = Point2f((x + rng.nextFloat()) * invSqrtVal,
                                     (y + rng.nextFloat()) * invSqrtVal);
                    break;
            }

            auto result = warpPoint(sample);
            positions.col(i) = result.first;
            weights(0, i) = result.second;
        }
    }

    static std::pair<BSDF *, BSDFQueryRecord>
    create_microfacet_bsdf(float alpha, float kd, float bsdfAngle) {
        PropertyList list;
        list.setFloat("alpha", alpha);
        list.setColor("kd", Color3f(kd));
        auto * brdf = (BSDF *) NoriObjectFactory::createInstance("microfacet", list);

        nori::Vector3f wi(std::sin(bsdfAngle), 0.f,
                    std::max(std::cos(bsdfAngle), 1e-4f));
        wi = wi.normalized();
        BSDFQueryRecord bRec(wi);
        return { brdf, bRec };
    }
};

struct Arcball {
    using Quaternionf = Eigen::Quaternion<float, Eigen::DontAlign>;

    Arcball(float speedFactor = 2.0f)
        : m_active(false), m_lastPos(nori::Vector2i::Zero()), m_size(nori::Vector2i::Zero()),
          m_quat(Quaternionf::Identity()),
          m_incr(Quaternionf::Identity()),
          m_speedFactor(speedFactor) { }

    void setSize(nori::Vector2i size) { m_size = size; }

    const nori::Vector2i &size() const { return m_size; }

    void button(nori::Vector2i pos, bool pressed) {
        m_active = pressed;
        m_lastPos = pos;
        if (!m_active)
            m_quat = (m_incr * m_quat).normalized();
        m_incr = Quaternionf::Identity();
    }

    bool motion(nori::Vector2i pos) {
        if (!m_active)
            return false;

        /* Based on the rotation controller from AntTweakBar */
        float invMinDim = 1.0f / m_size.minCoeff();
        float w = (float) m_size.x(), h = (float) m_size.y();

        float ox = (m_speedFactor * (2*m_lastPos.x() - w) + w) - w - 1.0f;
        float tx = (m_speedFactor * (2*pos.x()      - w) + w) - w - 1.0f;
        float oy = (m_speedFactor * (h - 2*m_lastPos.y()) + h) - h - 1.0f;
        float ty = (m_speedFactor * (h - 2*pos.y())      + h) - h - 1.0f;

        ox *= invMinDim; oy *= invMinDim;
        tx *= invMinDim; ty *= invMinDim;

        nori::Vector3f v0(ox, oy, 1.0f), v1(tx, ty, 1.0f);
        if (v0.squaredNorm() > 1e-4f && v1.squaredNorm() > 1e-4f) {
            v0.normalize(); v1.normalize();
            nori::Vector3f axis = v0.cross(v1);
            float sa = std::sqrt(axis.dot(axis)),
                  ca = v0.dot(v1),
                  angle = std::atan2(sa, ca);
            if (tx*tx + ty*ty > 1.0f)
                angle *= 1.0f + 0.2f * (std::sqrt(tx*tx + ty*ty) - 1.0f);
            m_incr = Eigen::AngleAxisf(angle, axis.normalized());
            if (!std::isfinite(m_incr.norm()))
                m_incr = Quaternionf::Identity();
        }
        return true;
    }

    Eigen::Matrix4f matrix() const {
        Eigen::Matrix4f result2 = Eigen::Matrix4f::Identity();
        result2.block<3,3>(0, 0) = (m_incr * m_quat).toRotationMatrix();
        return result2;
    }

private:
    /// Whether or not this Arcball is currently active.
    bool m_active;

    /// The last click position (which triggered the Arcball to be active / non-active).
    nori::Vector2i m_lastPos;

    /// The size of this Arcball.
    nori::Vector2i m_size;

    /**
     * The current stable state.  When this Arcball is active, represents the
     * state of this Arcball when \ref Arcball::button was called with
     * ``down = true``.
     */
    Quaternionf m_quat;

    /// When active, tracks the overall update to the state.  Identity when non-active.
    Quaternionf m_incr;

    /**
     * The speed at which this Arcball rotates.  Smaller values mean it rotates
     * more slowly, higher values mean it rotates more quickly.
     */
    float m_speedFactor;

public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};

class WarpTestScreen : public Screen {
public:

    WarpTestScreen(): Screen(Vector2i(800, 600), "warptest: Sampling and Warping"), m_bRec(nori::Vector3f()) {
        inc_ref();
        initializeGUI();
        m_drawHistogram = false;
    }

    static float mapParameter(WarpType warpType, float parameterValue) {
        if (warpType == Beckmann || warpType == MicrofacetBRDF)
            parameterValue = std::exp(std::log(0.01f) * (1 - parameterValue) +
                                      std::log(1.f)   *  parameterValue);
        return parameterValue;
    }

    void refresh() {
        PointType pointType = (PointType) m_pointTypeBox->selected_index();
        WarpType warpType = (WarpType) m_warpTypeBox->selected_index();
        float parameterValue = mapParameter(warpType, m_parameterSlider->value());
        float parameter2Value = mapParameter(warpType, m_parameter2Slider->value());
        m_pointCount = (int) std::pow(2.f, 15 * m_pointCountSlider->value() + 5);

        if (warpType == MicrofacetBRDF) {
            BSDF *ptr;
            float bsdfAngle = M_PI * (m_angleSlider->value() - 0.5f);
            std::tie(ptr, m_bRec) = WarpTest::create_microfacet_bsdf(
                parameterValue, parameter2Value, bsdfAngle);
            m_brdf.reset(ptr);
        }

        /* Generate the point positions */
        nori::MatrixXf positions, values;
        try {
            WarpTest tester(warpType, parameterValue, m_brdf.get(), m_bRec);
            tester.generatePoints(m_pointCount, pointType, positions, values);
        } catch (const NoriException &e) {
            m_warpTypeBox->set_selected_index(0);
            refresh();
            new MessageDialog(this, MessageDialog::Type::Warning, "Error", "An error occurred: " + std::string(e.what()));
            return;
        }

        float value_scale = 0.f;
        for (int i=0; i<m_pointCount; ++i)
            value_scale = std::max(value_scale, values(0, i));
        value_scale = 1.f / value_scale;

        if (!m_brdfValueCheckBox->checked() || warpType != MicrofacetBRDF)
            value_scale = 0.f;

        if (warpType != Square) {
            for (int i=0; i < m_pointCount; ++i) {
                if (values(0, i) == 0.0f) {
                    positions.col(i) = nori::Vector3f::Constant(std::numeric_limits<float>::quiet_NaN());
                    continue;
                }
                positions.col(i) =
                    ((value_scale == 0 ? 1.0f : (value_scale * values(0, i))) *
                     positions.col(i)) * 0.5f + nori::Vector3f(0.5f, 0.5f, 0.0f);
            }
        }

        /* Generate a color gradient */
        nori::MatrixXf colors(3, m_pointCount);
        float colScale = 1.f / m_pointCount;
        for (int i=0; i<m_pointCount; ++i)
            colors.col(i) << i*colScale, 1-i*colScale, 0;

        /* Upload points to GPU */
        m_pointShader->set_buffer("position", VariableType::Float32, {(size_t) m_pointCount, 3}, positions.data());
        m_pointShader->set_buffer("color", VariableType::Float32, {(size_t) m_pointCount, 3}, colors.data());

        /* Upload lines to GPU */
        if (m_gridCheckBox->checked()) {
            int gridRes = (int) (std::sqrt((float) m_pointCount) + 0.5f);
            int fineGridRes = 16*gridRes, idx = 0;
            m_lineCount = 4 * (gridRes+1) * (fineGridRes+1);
            positions.resize(3, m_lineCount);
            float coarseScale = 1.f / gridRes, fineScale = 1.f / fineGridRes;

            WarpTest tester(warpType, parameterValue, m_brdf.get(), m_bRec);
            for (int i=0; i<=gridRes; ++i) {
                for (int j=0; j<=fineGridRes; ++j) {
                    auto pt = tester.warpPoint(Point2f(j * fineScale, i * coarseScale));
                    positions.col(idx++) = value_scale == 0.f ? pt.first : (pt.first * pt.second * value_scale);
                    pt = tester.warpPoint(Point2f((j+1) * fineScale, i * coarseScale));
                    positions.col(idx++) = value_scale == 0.f ? pt.first : (pt.first * pt.second * value_scale);
                    pt = tester.warpPoint(Point2f(i*coarseScale, j     * fineScale));
                    positions.col(idx++) = value_scale == 0.f ? pt.first : (pt.first * pt.second * value_scale);
                    pt = tester.warpPoint(Point2f(i*coarseScale, (j+1) * fineScale));
                    positions.col(idx++) = value_scale == 0.f ? pt.first : (pt.first * pt.second * value_scale);
                }
            }
            if (warpType != Square) {
                for (int i=0; i<m_lineCount; ++i)
                    positions.col(i) = positions.col(i) * 0.5f + nori::Vector3f(0.5f, 0.5f, 0.0f);
            }
            m_gridShader->set_buffer("position", VariableType::Float32, {(size_t) m_lineCount, 3}, positions.data());
        }

        int ctr = 0;
        positions.resize(3, 106);
        for (int i=0; i<=50; ++i) {
            float angle1 = i * 2 * M_PI / 50;
            float angle2 = (i+1) * 2 * M_PI / 50;
            positions.col(ctr++) << std::cos(angle1)*.5f + 0.5f, std::sin(angle1)*.5f + 0.5f, 0.f;
            positions.col(ctr++) << std::cos(angle2)*.5f + 0.5f, std::sin(angle2)*.5f + 0.5f, 0.f;
        }
        positions.col(ctr++) << 0.5f, 0.5f, 0.f;
        positions.col(ctr++) << -m_bRec.wi.x() * 0.5f + 0.5f, -m_bRec.wi.y() * 0.5f + 0.5f, m_bRec.wi.z() * 0.5f;
        positions.col(ctr++) << 0.5f, 0.5f, 0.f;
        positions.col(ctr++) << m_bRec.wi.x() * 0.5f + 0.5f, m_bRec.wi.y() * 0.5f + 0.5f, m_bRec.wi.z() * 0.5f;
        m_arrowShader->set_buffer("position", VariableType::Float32, {106, 3}, positions.data());

        /* Update user interface */
        std::string str;
        if (m_pointCount > 1000000) {
            m_pointCountBox->set_units("M");
            str = tfm::format("%.2f", m_pointCount * 1e-6f);
        } else if (m_pointCount > 1000) {
            m_pointCountBox->set_units("K");
            str = tfm::format("%.2f", m_pointCount * 1e-3f);
        } else {
            m_pointCountBox->set_units(" ");
            str = tfm::format("%i", m_pointCount);
        }
        m_pointCountBox->set_value(str);
        m_parameterBox->set_value(tfm::format("%.1g", parameterValue));
        m_parameter2Box->set_value(tfm::format("%.1g", parameter2Value));
        m_angleBox->set_value(tfm::format("%.1f", m_angleSlider->value() * 180-90));
        m_parameterSlider->set_enabled(warpType == Beckmann || warpType == MicrofacetBRDF);
        m_parameterBox->set_enabled(warpType == Beckmann || warpType == MicrofacetBRDF);
        m_parameter2Slider->set_enabled(warpType == MicrofacetBRDF);
        m_parameter2Box->set_enabled(warpType == MicrofacetBRDF);
        m_angleBox->set_enabled(warpType == MicrofacetBRDF);
        m_angleSlider->set_enabled(warpType == MicrofacetBRDF);
        m_brdfValueCheckBox->set_enabled(warpType == MicrofacetBRDF);
        m_pointCountSlider->set_value((std::log((float) m_pointCount) / std::log(2.f) - 5) / 15);
    }

    bool mouse_motion_event(const Vector2i &p, const Vector2i &rel,
                                  int button, int modifiers) {
        if (!Screen::mouse_motion_event(p, rel, button, modifiers))
            m_arcball.motion(nori::Vector2i(p.x(), p.y()));
        return true;
    }

    bool mouse_button_event(const Vector2i &p, int button, bool down, int modifiers) {
        if (down && !m_window->visible()) {
            m_drawHistogram = false;
            m_window->set_visible(true);
            return true;
        }
        if (!Screen::mouse_button_event(p, button, down, modifiers)) {
            if (button == GLFW_MOUSE_BUTTON_1)
                m_arcball.button(nori::Vector2i(p.x(), p.y()), down);
        }
        return true;
    }

    void draw_contents() {
        /* Set up a perspective camera matrix */
        Matrix4f view, proj, model(1.f);
        view = Matrix4f::look_at(Vector3f(0, 0, 4), Vector3f(0, 0, 0), Vector3f(0, 1, 0));
        const float viewAngle = 30, near_clip = 0.01, far_clip = 100;
        proj = Matrix4f::perspective(viewAngle / 360.0f * M_PI, near_clip, far_clip,
                                    (float) m_size.x() / (float) m_size.y());
        model = Matrix4f::translate(Vector3f(-0.5f, -0.5f, 0.0f)) * model;

        Matrix4f arcball_ng(1.f);
        memcpy(arcball_ng.m, m_arcball.matrix().data(), sizeof(float) * 16);
        model = arcball_ng * model;
        m_renderPass->resize(framebuffer_size());
        m_renderPass->begin();

        if (m_drawHistogram) {
            /* Render the histograms */
            WarpType warpType = (WarpType) m_warpTypeBox->selected_index();
            const int spacer = 20;
            const int histWidth = (width() - 3*spacer) / 2;
            const int histHeight = (warpType == Square || warpType == Disk || warpType == Tent) ? histWidth : histWidth / 2;
            const int verticalOffset = (height() - histHeight) / 2;

            drawHistogram(Vector2i(spacer, verticalOffset), Vector2i(histWidth, histHeight), m_textures[0].get());
            drawHistogram(Vector2i(2*spacer + histWidth, verticalOffset), Vector2i(histWidth, histHeight), m_textures[1].get());

            auto ctx = m_nvg_context;
            nvgBeginFrame(ctx, m_size[0], m_size[1], m_pixel_ratio);
            nvgBeginPath(ctx);
            nvgRect(ctx, spacer, verticalOffset + histHeight + spacer, width()-2*spacer, 70);
            nvgFillColor(ctx, m_testResult.first ? Color(100, 255, 100, 100) : Color(255, 100, 100, 100));
            nvgFill(ctx);
            nvgFontSize(ctx, 24.0f);
            nvgFontFace(ctx, "sans-bold");
            nvgTextAlign(ctx, NVG_ALIGN_CENTER | NVG_ALIGN_TOP);
            nvgFillColor(ctx, Color(255, 255));
            nvgText(ctx, spacer + histWidth / 2, verticalOffset - 3 * spacer,
                    "Sample histogram", nullptr);
            nvgText(ctx, 2 * spacer + (histWidth * 3) / 2, verticalOffset - 3 * spacer,
                    "Integrated density", nullptr);
            nvgStrokeColor(ctx, Color(255, 255));
            nvgStrokeWidth(ctx, 2);
            nvgBeginPath(ctx);
            nvgRect(ctx, spacer, verticalOffset, histWidth, histHeight);
            nvgRect(ctx, 2 * spacer + histWidth, verticalOffset, histWidth,
                    histHeight);
            nvgStroke(ctx);
            nvgFontSize(ctx, 20.0f);
            nvgTextAlign(ctx, NVG_ALIGN_CENTER | NVG_ALIGN_TOP);

            float bounds[4];
            nvgTextBoxBounds(ctx, 0, 0, width() - 2 * spacer,
                             m_testResult.second.c_str(), nullptr, bounds);
            nvgTextBox(
                ctx, spacer, verticalOffset + histHeight + spacer + (70 - bounds[3])/2,
                width() - 2 * spacer, m_testResult.second.c_str(), nullptr);
            nvgEndFrame(ctx);
        } else {
            /* Render the point set */
            Matrix4f mvp = proj * view * model;
            m_pointShader->set_uniform("mvp", mvp);
            glPointSize(2);
            m_renderPass->set_depth_test(RenderPass::DepthTest::Less, true);
            m_pointShader->begin();
            m_pointShader->draw_array(nanogui::Shader::PrimitiveType::Point, 0, m_pointCount);
            m_pointShader->end();

            bool drawGrid = m_gridCheckBox->checked();
            if (drawGrid) {
                m_gridShader->set_uniform("mvp", mvp);
                m_gridShader->begin();
                m_gridShader->draw_array(nanogui::Shader::PrimitiveType::Line, 0, m_lineCount);
                m_gridShader->end();
            }
            if (m_warpTypeBox->selected_index() == MicrofacetBRDF) {
                m_arrowShader->set_uniform("mvp", mvp);
                m_arrowShader->begin();
                m_arrowShader->draw_array(nanogui::Shader::PrimitiveType::Line, 0, 106);
                m_arrowShader->end();
            }
        }
        m_renderPass->end();
    }

    void drawHistogram(const nanogui::Vector2i &pos_, const nanogui::Vector2i &size_, Texture *texture) {
        Vector2f s = -(Vector2f(pos_) + Vector2f(0.25f, 0.25f)) / Vector2f(size_);
        Vector2f e = Vector2f(size()) / Vector2f(size_) + s;
        Matrix4f mvp = Matrix4f::ortho(s.x(), e.x(), s.y(), e.y(), -1, 1);
        m_renderPass->set_depth_test(RenderPass::DepthTest::Always, true);
        m_histogramShader->set_uniform("mvp", mvp);
        m_histogramShader->set_texture("tex", texture);
        m_histogramShader->begin();
        m_histogramShader->draw_array(nanogui::Shader::PrimitiveType::Triangle, 0, 6, true);
        m_histogramShader->end();
    }

    void runTest() {
        // Prepare and run test, passing parameters from UI.
        WarpType warpType = (WarpType) m_warpTypeBox->selected_index();
        float parameterValue = mapParameter(warpType, m_parameterSlider->value());

        WarpTest tester(warpType, parameterValue, m_brdf.get(), m_bRec);
        m_testResult = tester.run();

        float maxValue = 0, minValue = std::numeric_limits<float>::infinity();
        for (int i=0; i<tester.res; ++i) {
            maxValue = std::max(maxValue,
                                (float) std::max(tester.obsFrequencies[i], tester.expFrequencies[i]));
            minValue = std::min(minValue,
                                (float) std::min(tester.obsFrequencies[i], tester.expFrequencies[i]));
        }
        minValue /= 2;
        float texScale = 1/(maxValue - minValue);

        /* Upload histograms to GPU */
        std::unique_ptr<float[]> buffer(new float[tester.res]);
        for (int k=0; k<2; ++k) {
            for (int i=0; i<tester.res; ++i)
                buffer[i] = ((k == 0 ? tester.obsFrequencies[i]
                                     : tester.expFrequencies[i]) - minValue) * texScale;
            m_textures[k] = new Texture(Texture::PixelFormat::R,
                                        Texture::ComponentFormat::Float32,
                                        nanogui::Vector2i(tester.xres, tester.yres),
                                        Texture::InterpolationMode::Nearest,
                                        Texture::InterpolationMode::Nearest);
            m_textures[k]->upload((uint8_t *) buffer.get());
        }
        m_drawHistogram = true;
        m_window->set_visible(false);
    }

    void initializeGUI() {
        m_window = new Window(this, "Warp tester");
        m_window->set_position(Vector2i(15, 15));
        m_window->set_layout(new GroupLayout());

        set_resize_callback([&](Vector2i size) {
            m_arcball.setSize(nori::Vector2i(size.x(), size.y()));
        });

        m_arcball.setSize(nori::Vector2i(m_size.x(), m_size.y()));

        new Label(m_window, "Input point set", "sans-bold");

        /* Create an empty panel with a horizontal layout */
        Widget *panel = new Widget(m_window);
        panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));

        /* Add a slider and set defaults */
        m_pointCountSlider = new Slider(panel);
        m_pointCountSlider->set_fixed_width(55);
        m_pointCountSlider->set_callback([&](float) { refresh(); });

        /* Add a textbox and set defaults */
        m_pointCountBox = new TextBox(panel);
        m_pointCountBox->set_fixed_size(Vector2i(80, 25));

        m_pointTypeBox = new ComboBox(m_window, { "Independent", "Grid", "Stratified" });
        m_pointTypeBox->set_callback([&](int) { refresh(); });

        new Label(m_window, "Warping method", "sans-bold");
        m_warpTypeBox = new ComboBox(m_window, { "Square", "Tent", "Disk", "Sphere", "Hemisphere (unif.)",
                "Hemisphere (cos)", "Beckmann distr.", "Microfacet BRDF" });
        m_warpTypeBox->set_callback([&](int) { refresh(); });

        panel = new Widget(m_window);
        panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));
        m_parameterSlider = new Slider(panel);
        m_parameterSlider->set_fixed_width(55);
        m_parameterSlider->set_callback([&](float) { refresh(); });
        m_parameterBox = new TextBox(panel);
        m_parameterBox->set_fixed_size(Vector2i(80, 25));
        panel = new Widget(m_window);
        panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));
        m_parameter2Slider = new Slider(panel);
        m_parameter2Slider->set_fixed_width(55);
        m_parameter2Slider->set_callback([&](float) { refresh(); });
        m_parameter2Box = new TextBox(panel);
        m_parameter2Box->set_fixed_size(Vector2i(80, 25));
        m_gridCheckBox = new CheckBox(m_window, "Visualize warped grid");
        m_gridCheckBox->set_callback([&](bool) { refresh(); });

        new Label(m_window, "BSDF parameters", "sans-bold");

        panel = new Widget(m_window);
        panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 20));

        m_angleSlider = new Slider(panel);
        m_angleSlider->set_fixed_width(55);
        m_angleSlider->set_callback([&](float) { refresh(); });
        m_angleBox = new TextBox(panel);
        m_angleBox->set_fixed_size(Vector2i(80, 25));
        m_angleBox->set_units(utf8(0x00B0).data());

        m_brdfValueCheckBox = new CheckBox(m_window, "Visualize BRDF values");
        m_brdfValueCheckBox->set_callback([&](bool) { refresh(); });

        new Label(m_window,
            std::string(utf8(0x03C7).data()) +
            std::string(utf8(0x00B2).data()) + " hypothesis test",
            "sans-bold");

        Button *testBtn = new Button(m_window, "Run", FA_CHECK);
        testBtn->set_background_color(Color(0, 255, 0, 25));
        testBtn->set_callback([&]{
            try {
                runTest();
            } catch (const NoriException &e) {
                new MessageDialog(this, MessageDialog::Type::Warning, "Error", "An error occurred: " + std::string(e.what()));
            }
        });

        m_renderPass = new RenderPass({ this });
        m_renderPass->set_clear_color(0, Color(0.f, 0.f, 0.f, 1.f));

        perform_layout();

        m_pointShader = new Shader(
            m_renderPass,
            "Point shader",

            /* Vertex shader */
            R"(#version 330
            uniform mat4 mvp;
            in vec3 position;
            in vec3 color;
            out vec3 frag_color;
            void main() {
                gl_Position = mvp * vec4(position, 1.0);
                if (isnan(position.r)) /* nan (missing value) */
                    frag_color = vec3(0.0);
                else
                    frag_color = color;
            })",

            /* Fragment shader */
            R"(#version 330
            in vec3 frag_color;
            out vec4 out_color;
            void main() {
                if (frag_color == vec3(0.0))
                    discard;
                out_color = vec4(frag_color, 1.0);
            })"
        );

        m_gridShader = new Shader(
            m_renderPass,
            "Grid shader",

            /* Vertex shader */
            R"(#version 330
            uniform mat4 mvp;
            in vec3 position;
            void main() {
                gl_Position = mvp * vec4(position, 1.0);
            })",

            /* Fragment shader */
            R"(#version 330
            out vec4 out_color;
            void main() {
                out_color = vec4(vec3(1.0), 0.4);
            })", Shader::BlendMode::AlphaBlend
        );

        m_arrowShader = new Shader(
            m_renderPass,
            "Arrow shader",

            /* Vertex shader */
            R"(#version 330
            uniform mat4 mvp;
            in vec3 position;
            void main() {
                gl_Position = mvp * vec4(position, 1.0);
            })",

            /* Fragment shader */
            R"(#version 330
            out vec4 out_color;
            void main() {
                out_color = vec4(vec3(1.0), 0.4);
            })"
        );

        m_histogramShader = new Shader(
            m_renderPass,
            "Histogram shader",

            /* Vertex shader */
            R"(#version 330
            uniform mat4 mvp;
            in vec2 position;
            out vec2 uv;
            void main() {
                gl_Position = mvp * vec4(position, 0.0, 1.0);
                uv = position;
            })",

            /* Fragment shader */
            R"(#version 330
            out vec4 out_color;
            uniform sampler2D tex;
            in vec2 uv;
            /* http://paulbourke.net/texture_colour/colourspace/ */
            vec3 colormap(float v, float vmin, float vmax) {
                vec3 c = vec3(1.0);
                if (v < vmin)
                    v = vmin;
                if (v > vmax)
                    v = vmax;
                float dv = vmax - vmin;

                if (v < (vmin + 0.25 * dv)) {
                    c.r = 0.0;
                    c.g = 4.0 * (v - vmin) / dv;
                } else if (v < (vmin + 0.5 * dv)) {
                    c.r = 0.0;
                    c.b = 1.0 + 4.0 * (vmin + 0.25 * dv - v) / dv;
                } else if (v < (vmin + 0.75 * dv)) {
                    c.r = 4.0 * (v - vmin - 0.5 * dv) / dv;
                    c.b = 0.0;
                } else {
                    c.g = 1.0 + 4.0 * (vmin + 0.75 * dv - v) / dv;
                    c.b = 0.0;
                }
                return c;
            }
            void main() {
                float value = texture(tex, uv).r;
                out_color = vec4(colormap(value, 0.0, 1.0), 1.0);
            })"
        );

        /* Upload a single quad */
        uint32_t indices[3 * 2] = {
            0, 1, 2,
            2, 3, 0
        };
        float positions[2 * 4] = {
            0.f, 0.f,
            1.f, 0.f,
            1.f, 1.f,
            0.f, 1.f
        };
        m_histogramShader->set_buffer("indices", VariableType::UInt32, {3 * 2}, indices);
        m_histogramShader->set_buffer("position", VariableType::Float32, {4, 2}, positions);

        /* Set default and register slider callback */
        m_pointCountSlider->set_value(7.f / 15.f);
        m_parameterSlider->set_value(.5f);
        m_parameter2Slider->set_value(0.f);
        m_angleSlider->set_value(.5f);

        refresh();
        set_visible(true);
        draw_all();
    }

private:
    nanogui::ref<Shader> m_pointShader, m_gridShader, m_histogramShader, m_arrowShader;
    Window *m_window;
    Slider *m_pointCountSlider, *m_parameterSlider, *m_parameter2Slider, *m_angleSlider;
    TextBox *m_pointCountBox, *m_parameterBox, *m_parameter2Box, *m_angleBox;
    nanogui::ref<nanogui::Texture> m_textures[2];
    ComboBox *m_pointTypeBox;
    ComboBox *m_warpTypeBox;
    CheckBox *m_gridCheckBox;
    CheckBox *m_brdfValueCheckBox;
    Arcball m_arcball;
    int m_pointCount, m_lineCount;
    bool m_drawHistogram;
    std::unique_ptr<BSDF> m_brdf;
    BSDFQueryRecord m_bRec;
    std::pair<bool, std::string> m_testResult;
    nanogui::ref<RenderPass> m_renderPass;
};

std::tuple<WarpType, float, float> parse_arguments(int argc, char **argv) {
    WarpType tp = WarpTypeCount;
    for (int i = 0; i < WarpTypeCount; ++i) {
        if (strcmp(kWarpTypeNames[i].c_str(), argv[1]) == 0)
            tp = WarpType(i);
    }
    if (tp >= WarpTypeCount)
        throw std::runtime_error("Invalid warp type!");

    float value = 0.f, value2 = 0.f;
    if (argc > 2)
        value = std::stof(argv[2]);
    if (argc > 3)
        value2 = std::stof(argv[3]);

    return { tp, value, value2 };
}

int main(int argc, char **argv) {
    if (argc <= 1) {
        // GUI mode
        nanogui::init();
        WarpTestScreen *screen = new WarpTestScreen();
        nanogui::mainloop();
        delete screen;
        nanogui::shutdown();
        return 0;
    }

    // CLI mode
    WarpType warpType;
    float paramValue, param2Value;
    std::unique_ptr<BSDF> bsdf;
    auto bRec = BSDFQueryRecord(nori::Vector3f());
    std::tie(warpType, paramValue, param2Value) = parse_arguments(argc, argv);
    if (warpType == MicrofacetBRDF) {
        float bsdfAngle = M_PI * 0.f;
        BSDF *ptr;
        std::tie(ptr, bRec) = WarpTest::create_microfacet_bsdf(
            paramValue, param2Value, bsdfAngle);
        bsdf.reset(ptr);
    }

    std::string extra = "";
    if (param2Value > 0)
        extra = tfm::format(", second parameter value = %f", param2Value);
    std::cout << tfm::format(
        "Testing warp %s, parameter value = %f%s",
         kWarpTypeNames[int(warpType)], paramValue, extra
    ) << std::endl;
    WarpTest tester(warpType, paramValue, bsdf.get(), bRec);
    auto res = tester.run();
    if (res.first)
        return 0;

    std::cout << tfm::format("warptest failed: %s", res.second) << std::endl;
    return 1;
}


#ifndef eye_HPP
#define eye_HPP

#include <vector>
#include <opencv2/core/mat.hpp>
#include <opencv2/core/types.hpp>
#include <opencv2/imgproc.hpp>
#include <dlib/image_processing/full_object_detection.h>

#include "calibration.hpp"

class Eye
{
public:
    int xmin, xmax, ymin, ymax;
    int xcenter, ycenter;

    Eye() {}

    cv::Mat analyze(cv::Mat &frame, std::vector<dlib::full_object_detection> &shapes, int side)
    {
        std::vector<cv::Point> eyeRegion;
        std::vector<std::vector<cv::Point>> temp;

        if(side == 0) // left eye
        {
            for(const int &i : leftEyePoints)
            {
                eyeRegion.push_back(cv::Point(shapes[0].part(i).x(), shapes[0].part(i).y()));
            }
        }
        else if(side == 1) // right eye
        {
            for(const int &i : rightEyePoints)
            {
                eyeRegion.push_back(cv::Point(shapes[0].part(i).x(), shapes[0].part(i).y()));
            }
        }
        temp.push_back(eyeRegion);

        cv::Mat blackFrame = cv::Mat(frame.size(), CV_8UC1, cv::Scalar(0));
        cv::Mat mask = cv::Mat(frame.size(), CV_8UC1, cv::Scalar(255));
        cv::fillPoly(mask, temp, cv::Scalar(0, 0, 0));
        cv::Mat eyeFrame = frame.clone();
        cv::bitwise_not(blackFrame, eyeFrame, mask=mask);

        // Crop the eye region
        xmin = (*min_element(eyeRegion.begin(), eyeRegion.end(), [](cv::Point i, cv::Point j) { return i.x < j.x; })).x - margin;
        xmax = (*max_element(eyeRegion.begin(), eyeRegion.end(), [](cv::Point i, cv::Point j) { return i.x < j.x; })).x + margin;
        ymin = (*min_element(eyeRegion.begin(), eyeRegion.end(), [](cv::Point i, cv::Point j) { return i.y < j.y; })).y - margin;
        ymax = (*max_element(eyeRegion.begin(), eyeRegion.end(), [](cv::Point i, cv::Point j) { return i.y < j.y; })).y + margin;

        xcenter = xmin + (xmax - xmin) / 2;
        ycenter = ymin + (ymax - ymin) / 2;

        return eyeFrame;
    }

private:
    const int leftEyePoints[6] = {36, 37, 38, 39, 40, 41};
    const int rightEyePoints[6] = {42, 43, 44, 45, 46, 47};
    const int margin = 5;
};

#endif


#include "SPlisHSPlasH/Common.h"
#include "GL/glew.h"
#include "Visualization/MiniGL.h"
#include "GL/glut.h"
#include "SPlisHSPlasH/TimeManager.h"
#include <Eigen/Dense>
#include <iostream>
#include "Utilities/Timing.h"
#include "Utilities/PartioReaderWriter.h"
#include "PositionBasedDynamicsWrapper/PBDRigidBody.h"
#include "Utilities/OBJLoader.h"
#include "SPlisHSPlasH/Utilities/PoissonDiskSampling.h"
#include "PositionBasedDynamicsWrapper/PBDWrapper.h"
#include "Demos/Common/DemoBase.h"
#include "Utilities/FileSystem.h"
#include "Utilities/Version.h"
#include "Utilities/SystemInfo.h"
#include "Utilities/Counting.h"
#include "SPlisHSPlasH/Simulation.h"
#include "Demos/Common/TweakBarParameters.h"

// Enable memory leak detection
#ifdef _DEBUG
#ifndef EIGEN_ALIGN
	#define new DEBUG_NEW
#endif
#endif

using namespace SPH;
using namespace Eigen;
using namespace std;
using namespace Utilities;
using namespace GenParam;

void timeStep ();
void initBoundaryData();
void render ();
void renderBoundary();
void reset();
void initParameters();
void updateBoundaryParticles(const bool forceUpdate);
void updateBoundaryForces();
void TW_CALL setCurrentFluidModel(const void *value, void *clientData);
void TW_CALL getCurrentFluidModel(void *value, void *clientData);

DemoBase *base;
PBDWrapper pbdWrapper;
unsigned int currentFluidModel = 0;

// main
int main( int argc, char **argv )
{
	REPORT_MEMORY_LEAKS;

	base = new DemoBase();
	base->init(argc, argv, "DynamicBoundaryDemo");

	Simulation *sim = Simulation::getCurrent();
	sim->init(base->getScene().particleRadius);

	//////////////////////////////////////////////////////////////////////////
	// PBD
	//////////////////////////////////////////////////////////////////////////
	pbdWrapper.initShader();
	pbdWrapper.readScene(base->getSceneFile());

	base->buildModel();

	initBoundaryData();
	unsigned int nBoundaryParticles = 0;
	for (unsigned int i = 0; i < sim->numberOfBoundaryModels(); i++)
		nBoundaryParticles += sim->getBoundaryModel(i)->numberOfParticles();

	LOG_INFO << "Number of boundary particles: " << nBoundaryParticles;

	for (unsigned int i = 0; i < sim->numberOfFluidModels(); i++)
	{
		FluidModel *model = sim->getFluidModel(i);
		const std::string &key = model->getId();
		model->setDragMethodChangedCallback([&]() { initParameters(); base->getSceneLoader()->readParameterObject(key, (ParameterObject*)model->getDragBase()); });
		model->setSurfaceMethodChangedCallback([&]() { initParameters(); base->getSceneLoader()->readParameterObject(key, (ParameterObject*)model->getSurfaceTensionBase()); });
		model->setViscosityMethodChangedCallback([&]() { initParameters(); base->getSceneLoader()->readParameterObject(key, (ParameterObject*)model->getViscosityBase()); });
		model->setVorticityMethodChangedCallback([&]() { initParameters(); base->getSceneLoader()->readParameterObject(key, (ParameterObject*)model->getVorticityBase()); });
	}

	initParameters();
	base->readParameters();

	Simulation::getCurrent()->setSimulationMethodChangedCallback([&]() { reset(); initParameters(); base->getSceneLoader()->readParameterObject("Configuration", Simulation::getCurrent()->getTimeStep()); });

	pbdWrapper.initModel(TimeManager::getCurrent()->getTimeStepSize());

	MiniGL::setClientIdleFunc(50, timeStep);
	MiniGL::setKeyFunc(0, 'r', reset);
	MiniGL::setClientSceneFunc(render);

	glutMainLoop ();

	base->cleanup();

	Utilities::Timing::printAverageTimes();
	Utilities::Timing::printTimeSums();

	Utilities::Counting::printAverageCounts();
	Utilities::Counting::printCounterSums();

	delete Simulation::getCurrent();
	delete base;

	return 0;
}

void initParameters()
{
	TwRemoveAllVars(MiniGL::getTweakBar());
	TweakBarParameters::cleanup();

	MiniGL::initTweakBarParameters();

	Simulation *sim = Simulation::getCurrent();
	TweakBarParameters::createParameterGUI();
	TweakBarParameters::createParameterObjectGUI(base);
	TweakBarParameters::createParameterObjectGUI(sim);
	TweakBarParameters::createParameterObjectGUI(sim->getTimeStep());

	// Enum for all fluid models
	if (sim->numberOfFluidModels() > 0)
	{
		TwType enumType = TwDefineEnum("CurrentFluidModelEnum", NULL, 0);
		std::ostringstream oss;
		oss << 0 << " {" << sim->getFluidModel(0)->getId().c_str() << "}";
		for (unsigned int j = 1; j < sim->numberOfFluidModels(); j++)
		{
			oss << ", " << j << " {" << sim->getFluidModel(j)->getId().c_str() << "}";
		}
		std::string enumStr = " label='Current fluid model' enum='" + oss.str() + "' group='Fluid model'";
		TwAddVarCB(MiniGL::getTweakBar(), "CurrentFluidModel", enumType, setCurrentFluidModel, getCurrentFluidModel, &currentFluidModel, enumStr.c_str());
	}

	// show GUI only for currently selected fluid model
	unsigned int i = currentFluidModel;
	FluidModel *model = sim->getFluidModel(currentFluidModel);
	TweakBarParameters::createParameterObjectGUI(model);
	TweakBarParameters::createParameterObjectGUI((GenParam::ParameterObject*) model->getDragBase());
	TweakBarParameters::createParameterObjectGUI((GenParam::ParameterObject*) model->getSurfaceTensionBase());
	TweakBarParameters::createParameterObjectGUI((GenParam::ParameterObject*) model->getViscosityBase());
	TweakBarParameters::createParameterObjectGUI((GenParam::ParameterObject*) model->getVorticityBase());
	TwDefine((std::string("TweakBar/FluidModel group='") + model->getId() + "'").c_str());
	TwDefine((std::string("TweakBar/'Drag force' group='") + model->getId() + "'").c_str());
	TwDefine((std::string("TweakBar/'Surface tension' group='") + model->getId() + "'").c_str());
	TwDefine((std::string("TweakBar/Viscosity group='") + model->getId() + "'").c_str());
	TwDefine((std::string("TweakBar/Vorticity group='") + model->getId() + "'").c_str());

	pbdWrapper.initGUI();
}

void reset()
{
	Utilities::Timing::printAverageTimes();
	Utilities::Timing::reset();

	Simulation::getCurrent()->reset();

	//////////////////////////////////////////////////////////////////////////
	// PBD
	//////////////////////////////////////////////////////////////////////////
	pbdWrapper.reset();

	updateBoundaryParticles(true);
	base->getSelectedParticles().clear();
}

void timeStep ()
{
	const Real pauseAt = base->getValue<Real>(DemoBase::PAUSE_AT);
	if ((pauseAt > 0.0) && (pauseAt < TimeManager::getCurrent()->getTime()))
		base->setValue(DemoBase::PAUSE, true);

	if (base->getValue<bool>(DemoBase::PAUSE))
		return;

	// Simulation code
	const unsigned int numSteps = base->getValue<unsigned int>(DemoBase::NUM_STEPS_PER_RENDER);
	for (unsigned int i = 0; i < numSteps; i++)
	{
		START_TIMING("SimStep");
		Simulation::getCurrent()->getTimeStep()->step();
		STOP_TIMING_AVG;

		updateBoundaryForces();

		//////////////////////////////////////////////////////////////////////////
		// PBD
		//////////////////////////////////////////////////////////////////////////
		START_TIMING("SimStep - PBD");
		pbdWrapper.timeStep();
		STOP_TIMING_AVG;

		updateBoundaryParticles(false);

		base->step();
	}
}

void renderBoundary()
{
	Simulation *sim = Simulation::getCurrent();
	Shader &shader = base->getShader();
	Shader &meshShader = base->getMeshShader();
	SceneLoader::Scene &scene = base->getScene();
	const int renderWalls = base->getValue<int>(DemoBase::RENDER_WALLS);
	GLint context_major_version = base->getContextMajorVersion();

	float wallColor[4] = { 0.1f, 0.6f, 0.6f, 1.0f };
	if ((renderWalls == 1) || (renderWalls == 2))
	{
		if (context_major_version > 3)
		{
			shader.begin();
			glUniform3fv(shader.getUniform("color"), 1, &wallColor[0]);
			glEnableVertexAttribArray(0);
			for (int body = sim->numberOfBoundaryModels() - 1; body >= 0; body--)
			{
				if ((renderWalls == 1) || (!scene.boundaryModels[body]->isWall))
				{
					BoundaryModel *bm = sim->getBoundaryModel(body);
					glVertexAttribPointer(0, 3, GL_DOUBLE, GL_FALSE, 0, &bm->getPosition(0));
					glDrawArrays(GL_POINTS, 0, bm->numberOfParticles());
				}
			}
			glDisableVertexAttribArray(0);
			shader.end();
		}
		else
		{
			glDisable(GL_LIGHTING);
			glPointSize(4.0f);

			glBegin(GL_POINTS);
			for (int body = sim->numberOfBoundaryModels() - 1; body >= 0; body--)
			{
				if ((renderWalls == 1) || (!scene.boundaryModels[body]->isWall))
				{
					BoundaryModel *bm = sim->getBoundaryModel(body);
					for (unsigned int i = 0; i < bm->numberOfParticles(); i++)
					{
						glColor3fv(wallColor);
						glVertex3v(&bm->getPosition(i)[0]);
					}
				}
			}
			glEnd();
			glEnable(GL_LIGHTING);
		}
	}
}

void render()
{
	float gridColor[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
	MiniGL::drawGrid(gridColor);

	MiniGL::coordinateSystem();
	MiniGL::drawTime(TimeManager::getCurrent()->getTime());

	Simulation *sim = Simulation::getCurrent();
	for (unsigned int i = 0; i < sim->numberOfFluidModels(); i++)
	{
		FluidModel *model = sim->getFluidModel(i);

		float fluidColor[4] = { 0.3f, 0.5f, 0.9f, 1.0f };
		MiniGL::hsvToRgb(0.61f - 0.1f*i, 0.66f, 0.9f, fluidColor);
		base->renderFluid(model, fluidColor);
	}
	renderBoundary();

	//////////////////////////////////////////////////////////////////////////
	// PBD
	//////////////////////////////////////////////////////////////////////////

	PBD::SimulationModel &model = pbdWrapper.getSimulationModel();
	PBD::SimulationModel::RigidBodyVector &rb = model.getRigidBodies();

	const int renderWalls = base->getValue<int>(DemoBase::RENDER_WALLS);
	SceneLoader::Scene &scene = base->getScene();
	if ((renderWalls == 3) || (renderWalls == 4))
	{
		for (size_t i = 0; i < rb.size(); i++)
		{
			const PBD::VertexData &vd = rb[i]->getGeometry().getVertexData();
			const Utilities::IndexedFaceMesh &mesh = rb[i]->getGeometry().getMesh();
			if ((renderWalls == 3) || (!scene.boundaryModels[i]->isWall))
			{
				float *col = &scene.boundaryModels[i]->color[0];
				if (!scene.boundaryModels[i]->isWall)
				{
					base->meshShaderBegin(col);
					pbdWrapper.drawMesh(vd, mesh, 0, col);
					base->meshShaderEnd();
				}
				else
				{
					base->meshShaderBegin(col);
					pbdWrapper.drawMesh(vd, mesh, 0, col);
					base->meshShaderEnd();
				}
			}
		}
	}

	pbdWrapper.renderTriangleModels();
	pbdWrapper.renderTetModels();
	pbdWrapper.renderConstraints();
	pbdWrapper.renderBVH();
}

void initBoundaryData()
{
	std::string scene_path = FileSystem::getFilePath(base->getSceneFile());
	std::string scene_file_name = FileSystem::getFileName(base->getSceneFile());
	SceneLoader::Scene &scene = base->getScene();
	const bool useCache = base->getUseParticleCaching();

	string cachePath = scene_path + "/Cache";

	for (unsigned int i = 0; i < scene.boundaryModels.size(); i++)
	{
		string meshFileName = FileSystem::normalizePath(scene_path + "/" + scene.boundaryModels[i]->meshFile);

		std::string md5FileName = FileSystem::normalizePath(cachePath + "/" + FileSystem::getFileNameWithExt(meshFileName) + ".md5");
		bool md5 = false;
		if (useCache)
		{
			string md5Str = FileSystem::getFileMD5(meshFileName);
			if (FileSystem::fileExists(md5FileName))
				md5 = FileSystem::checkMD5(md5Str, md5FileName);
		}

		std::vector<Vector3r> boundaryParticles;
		if (scene.boundaryModels[i]->samplesFile != "")
		{
			string particleFileName = scene_path + "/" + scene.boundaryModels[i]->samplesFile;
			PartioReaderWriter::readParticles(particleFileName, Vector3r::Zero(), Matrix3r::Identity(), scene.boundaryModels[i]->scale[0], boundaryParticles);
		}

		PBD::SimulationModel &model = pbdWrapper.getSimulationModel();
		PBD::SimulationModel::RigidBodyVector &rigidBodies = model.getRigidBodies();
		PBDRigidBody *rb = new PBDRigidBody(rigidBodies[i]);
		PBD::RigidBodyGeometry &geo = rigidBodies[i]->getGeometry();
		Utilities::IndexedFaceMesh &mesh = geo.getMesh();
		PBD::VertexData &vd = geo.getVertexData();

		if (scene.boundaryModels[i]->samplesFile == "")
		{
			// Cache sampling
			std::string mesh_base_path = FileSystem::getFilePath(scene.boundaryModels[i]->meshFile);
			std::string mesh_file_name = FileSystem::getFileName(scene.boundaryModels[i]->meshFile);

			const string resStr = to_string(scene.boundaryModels[i]->scale[0]) + "_" + to_string(scene.boundaryModels[i]->scale[1]) + "_" + to_string(scene.boundaryModels[i]->scale[2]);
			const string particleFileName = FileSystem::normalizePath(cachePath + "/" + mesh_file_name + "_dbd_" + std::to_string(scene.particleRadius) + "_" + resStr + ".bgeo");

			// check MD5 if cache file is available
			bool foundCacheFile = false;

			if (useCache)
				foundCacheFile = FileSystem::fileExists(particleFileName);

			if (useCache && foundCacheFile && md5)
			{
				PartioReaderWriter::readParticles(particleFileName, Vector3r::Zero(), Matrix3r::Identity(), 1.0, boundaryParticles);
				LOG_INFO << "Loaded cached boundary sampling: " << particleFileName;
			}

			if (!useCache || !foundCacheFile || !md5)
			{
				LOG_INFO << "Surface sampling of " << scene.boundaryModels[i]->meshFile;
				START_TIMING("Poisson disk sampling");
				PoissonDiskSampling sampling;
				sampling.sampleMesh(mesh.numVertices(), &vd.getPosition(0), mesh.numFaces(), mesh.getFaces().data(), scene.particleRadius, 10, 1, boundaryParticles);
				STOP_TIMING_AVG;

				// transform back to local coordinates
				for (unsigned int j = 0; j < boundaryParticles.size(); j++)
					boundaryParticles[j] = rb->getRotation().transpose() * (boundaryParticles[j] - rb->getPosition());

				// Cache sampling
				if (useCache && (FileSystem::makeDir(cachePath) == 0))
				{
					LOG_INFO << "Save particle sampling: " << particleFileName;
					PartioReaderWriter::writeParticles(particleFileName, (unsigned int)boundaryParticles.size(), boundaryParticles.data(), nullptr, scene.particleRadius);
					FileSystem::writeMD5File(meshFileName, md5FileName);
				}
			}
		}
		Simulation::getCurrent()->addBoundaryModel(rb, static_cast<unsigned int>(boundaryParticles.size()), &boundaryParticles[0]);
	}
	Simulation::getCurrent()->updateBoundaryPsi();
	updateBoundaryParticles(true);
}

void updateBoundaryParticles(const bool forceUpdate = false)
{
	Simulation *sim = Simulation::getCurrent();
	SceneLoader::Scene &scene = base->getScene();
	const unsigned int nObjects = sim->numberOfBoundaryModels();
	for (unsigned int i = 0; i < nObjects; i++)
	{
		BoundaryModel *bm = sim->getBoundaryModel(i);
		RigidBodyObject *rbo = bm->getRigidBodyObject();
		if (rbo->isDynamic() || forceUpdate)
		{
			#pragma omp parallel default(shared)
			{
				#pragma omp for schedule(static)
				for (int j = 0; j < (int)bm->numberOfParticles(); j++)
				{
					bm->getPosition(j) = rbo->getRotation() * bm->getPosition0(j) + rbo->getPosition();
					bm->getVelocity(j) = rbo->getAngularVelocity().cross(bm->getPosition(j) - rbo->getPosition()) + rbo->getVelocity();
				}
			}
		}
	}
}

void updateBoundaryForces()
{
	Real h = TimeManager::getCurrent()->getTimeStepSize();
	SceneLoader::Scene &scene = base->getScene();
	Simulation *sim = Simulation::getCurrent();
	const unsigned int nObjects = sim->numberOfBoundaryModels();
	for (unsigned int i = 0; i < nObjects; i++)
	{
		BoundaryModel *bm = sim->getBoundaryModel(i);
		RigidBodyObject *rbo = bm->getRigidBodyObject();
		if (rbo->isDynamic())
		{
			((PBDRigidBody*)rbo)->updateTimeStepSize();
			Vector3r force, torque;
			force.setZero();
			torque.setZero();

			for (int j = 0; j < (int)bm->numberOfParticles(); j++)
			{
				force += bm->getForce(j);
				torque += (bm->getPosition(j) - rbo->getPosition()).cross(bm->getForce(j));
				bm->getForce(j).setZero();
			}
			rbo->addForce(force);
			rbo->addTorque(torque);
		}
	}
}

void TW_CALL setCurrentFluidModel(const void *value, void *clientData)
{
	const unsigned int val = *(const unsigned int *)(value);
	*((unsigned int*)clientData) = val;

	initParameters();
}

void TW_CALL getCurrentFluidModel(void *value, void *clientData)
{
	*(unsigned int *)(value) = *((unsigned int*)clientData);
}


#include "password.hpp"

#include <boost/version.hpp>
#if BOOST_VERSION > 106800
	#include <boost/uuid/detail/sha1.hpp>
#else
	#include <boost/uuid/sha1.hpp>
#endif
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/algorithm/string.hpp>

std::string encode64(const std::string& val) {
    using namespace boost::archive::iterators;
    using It = base64_from_binary<transform_width<std::string::const_iterator, 6, 8>>;
    auto tmp = std::string(It(std::begin(val)), It(std::end(val)));
    return tmp.append((3 - val.size() % 3) % 3, '=');
}

std::string calculatePassword(const std::string& secret, const std::string& alias) {
	std::string secretAndAlias = secret + alias;
	boost::uuids::detail::sha1 sha1;
	sha1.process_bytes(secretAndAlias.c_str(), secretAndAlias.size());
	unsigned int digest[5];
	sha1.get_digest(digest);
	std::string hash;
	hash.resize(20);
	for (size_t i = 0; i < 5; ++i) {
		// Swap byte order because of Little Endian
		const char* tmp = reinterpret_cast<char*>(digest);
		hash[i * 4    ] = tmp[i * 4 + 3];
		hash[i * 4 + 1] = tmp[i * 4 + 2];
		hash[i * 4 + 2] = tmp[i * 4 + 1];
		hash[i * 4 + 3] = tmp[i * 4    ];
	}
	return encode64(hash).substr(0, 16);
}