#include <cctbx/boost_python/flex_fwd.h>

#include <cctbx/maptbx/fft.h>
#include <cctbx/maptbx/average_densities.h>
#include <cctbx/maptbx/standard_deviations_around_sites.hpp>
#include <scitbx/boost_python/utils.h>
#include <boost/python/module.hpp>
#include <boost/python/class.hpp>
#include <boost/python/def.hpp>
#include <boost/python/args.hpp>
#include <cctbx/maptbx/histogram.h>
#include <cctbx/maptbx/resolution.h>
#include <cctbx/maptbx/fsc.h>
#include <cctbx/maptbx/sphericity.h>
#include <cctbx/maptbx/mask.h>
#include <cctbx/maptbx/utils.h>
#include <cctbx/maptbx/connectivity.h>
#include <cctbx/maptbx/marked_grid_points.h>
#include <cctbx/maptbx/mask_utils.h>
#include <cctbx/maptbx/map_accumulator.h>
#include <cctbx/maptbx/ft_analytical_1d_point_scatterer_at_origin.h>
#include <cctbx/maptbx/target_and_gradients.h>

namespace cctbx { namespace maptbx { namespace boost_python {

  void wrap_grid_indices_around_sites();
  void wrap_grid_tags();
  void wrap_gridding();
  void wrap_misc();
  void wrap_peak_list();
  void wrap_pymol_interface();
  void wrap_statistics();
  void wrap_structure_factors();
  void wrap_coordinate_transformers();

  template <typename FloatType, typename GridType>
  struct map_accumulator_wrapper
  {
    //typedef map_accumulator<FloatType, GridType> w_t; // works too
    typedef cctbx::maptbx::map_accumulator<FloatType, GridType> w_t;
    static void wrap() {
      using namespace boost::python;
      class_<w_t>("map_accumulator", no_init)
        .def(init<af::int3 const&,
             double const&,
             double const&,
             int const&,
             bool,
             bool >((arg("n_real"),
                     arg("smearing_b"),
                     arg("max_peak_scale"),
                     arg("smearing_span"),
                     arg("use_exp_table"),
                     arg("use_max_map"))))
        .def("as_median_map", &w_t::as_median_map)
        .def("add", &w_t::add, (arg("map_data")))
        .def("at_index", &w_t::at_index, (arg("n")))
        .def("int_to_float_at_index", &w_t::int_to_float_at_index, (arg("n")))
      ;
    }
  };

  template <typename FloatType>
  struct ft_analytical_1d_point_scatterer_at_origin_wrapper
  {
    typedef ft_analytical_1d_point_scatterer_at_origin<FloatType> w_t;
    static void wrap() {
      using namespace boost::python;
      class_<w_t>("ft_analytical_1d_point_scatterer_at_origin", no_init)
        .def(init<int const& >(
                    (arg("N"))))
        .def("distances", &w_t::distances)
        .def("rho", &w_t::rho)
        .def("compute", &w_t::compute,
          (arg("miller_indices"),arg("step"),arg("left"),
            arg("right"),arg("u_frac")))
      ;
    }
  };

namespace {

  void init_module()
  {
    using namespace boost::python;

    map_accumulator_wrapper<double, af::c_grid<3> >::wrap();
    ft_analytical_1d_point_scatterer_at_origin_wrapper<double>::wrap();
    wrap_grid_indices_around_sites();
    wrap_grid_tags();
    wrap_gridding();
    wrap_misc();
    wrap_peak_list();
    wrap_pymol_interface();
    wrap_statistics();
    wrap_structure_factors();
    wrap_coordinate_transformers();

// Real-space target and gradients ---------------------------------------------
    {
      typedef cctbx::maptbx::target_and_gradients::diffmap::compute w_t;
      class_<w_t>("target_and_gradients_diffmap", no_init)
        .def(init<uctbx::unit_cell const&,
             af::const_ref<double, af::c_grid_padded<3> > const&,
             af::const_ref<double, af::c_grid_padded<3> > const&,
             double const&,
             af::const_ref<scitbx::vec3<double> > const& >((
                                    arg("unit_cell"),
                                    arg("map_target"),
                                    arg("map_current"),
                                    arg("step"),
                                    arg("sites_frac"))))
        .def("target", &w_t::target)
        .def("gradients", &w_t::gradients)
      ;
    }

    //- Magnification begin ----------------------------------------------------
    {
      typedef cctbx::maptbx::target_and_gradients::simple::magnification<double> w_t;
      class_<w_t>("target_and_gradients_simple_magnification", no_init)
        .def(init<uctbx::unit_cell const&,
             af::const_ref<double, af::c_grid_padded<3> > const&,
             af::const_ref<scitbx::vec3<double> > const&,
             scitbx::mat3<double> const& >((
                                    arg("unit_cell"),
                                    arg("map_target"),
                                    arg("sites_cart"),
                                    arg("K"))))
        .def(init<uctbx::unit_cell const&,
             af::const_ref<double, af::c_grid_padded<3> > const&,
             af::const_ref<scitbx::vec3<double> > const&,
             scitbx::vec3<double> const& >((
                                    arg("unit_cell"),
                                    arg("map_target"),
                                    arg("sites_cart"),
                                    arg("K"))))
        .def("target", &w_t::target)
        .def("gradients", &w_t::gradients)
      ;
    }

    def("magnification_isotropic",
      (double(*)
        (uctbx::unit_cell const&,
         af::const_ref<double, af::c_grid_padded<3> > const&,
         af::const_ref<scitbx::vec3<double> > const&))
           cctbx::maptbx::target_and_gradients::simple::magnification_isotropic, (
             arg("unit_cell"),
             arg("density_map"),
             arg("sites_cart")));

    def("magnification_anisotropic",
      (scitbx::vec3<double>(*)
        (uctbx::unit_cell const&,
         af::const_ref<double, af::c_grid_padded<3> > const&,
         af::const_ref<scitbx::vec3<double> > const&))
           cctbx::maptbx::target_and_gradients::simple::magnification_anisotropic, (
             arg("unit_cell"),
             arg("density_map"),
             arg("sites_cart")));
    //- Magnification end ------------------------------------------------------

    {
      typedef cctbx::maptbx::target_and_gradients::simple::compute<double> w_t;
      class_<w_t>("target_and_gradients_simple", no_init)
        .def(init<uctbx::unit_cell const&,
             af::const_ref<double, af::c_grid_padded<3> > const&,
             af::const_ref<scitbx::vec3<double> > const&,
             double const&,
             af::const_ref<bool> const& >((
                                    arg("unit_cell"),
                                    arg("map_target"),
                                    arg("sites_cart"),
                                    arg("delta"),
                                    arg("selection"))))
        .def(init<uctbx::unit_cell const&,
             af::const_ref<double, af::c_grid_padded<3> > const&,
             af::const_ref<scitbx::vec3<double> > const&,
             af::const_ref<bool> const&,
             std::string const& >((
                                    arg("unit_cell"),
                                    arg("map_target"),
                                    arg("sites_cart"),
                                    arg("selection"),
                                    arg("interpolation"))))
        .def("target", &w_t::target)
        .def("gradients", &w_t::gradients)
      ;
    }

    def("real_space_target_simple",
      (double(*)
        (uctbx::unit_cell const&,
         af::const_ref<double, af::c_grid_padded<3> > const&,
         af::const_ref<scitbx::vec3<double> > const&,
         af::const_ref<bool> const&))
           cctbx::maptbx::target_and_gradients::simple::target, (
             arg("unit_cell"),
             arg("density_map"),
             arg("sites_cart"),
             arg("selection")));

    def("real_space_target_simple_with_adjacent_similarity",
      (double(*)
        (uctbx::unit_cell const&,
         af::const_ref<double, af::c_grid_padded<3> > const&,
         af::const_ref<scitbx::vec3<double> > const&,
         af::const_ref<std::size_t > const&,
         af::const_ref<double> const&))
           cctbx::maptbx::target_and_gradients::simple::target_with_adjacent_similarity, (
             arg("unit_cell"),
             arg("density_map"),
             arg("sites_cart"),
             arg("selection"),
             arg("weights")));

    def("real_space_target_simple",
      (double(*)
        (uctbx::unit_cell const&,
         af::const_ref<double, af::c_grid_padded<3> > const&,
         af::const_ref<scitbx::vec3<double> > const&))
           cctbx::maptbx::target_and_gradients::simple::target, (
             arg("unit_cell"),
             arg("density_map"),
             arg("sites_cart")));

    def("real_space_target_simple",
      (double(*)
        (uctbx::unit_cell const&,
         af::const_ref<double, af::c_grid_padded<3> > const&,
         af::const_ref<scitbx::vec3<double> > const&,
         af::const_ref<std::size_t> const&))
           cctbx::maptbx::target_and_gradients::simple::target, (
             arg("unit_cell"),
             arg("density_map"),
             arg("sites_cart"),
             arg("selection")));

    def("real_space_target_simple",
      (double(*)
        (af::const_ref<double, af::c_grid<3> > const&,
         af::const_ref<scitbx::vec3<double> > const&))
           cctbx::maptbx::target_and_gradients::simple::target, (
             arg("density_map"),
             arg("sites_frac")));

    def("real_space_target_simple_per_site",
      (af::shared<double>(*)
        (uctbx::unit_cell const&,
         af::const_ref<double, af::c_grid_padded<3> > const&,
         af::const_ref<scitbx::vec3<double> > const&))
           cctbx::maptbx::target_and_gradients::simple::target_per_site, (
             arg("unit_cell"),
             arg("density_map"),
             arg("sites_cart")));

    def("real_space_gradients_simple",
      (af::shared<scitbx::vec3<double> >(*)
        (uctbx::unit_cell const&,
         af::const_ref<double, af::c_grid_padded<3> > const&,
         af::const_ref<scitbx::vec3<double> > const&,
         double,
         af::const_ref<bool> const&))
           cctbx::maptbx::target_and_gradients::simple::gradients, (
             arg("unit_cell"),
             arg("density_map"),
             arg("sites_cart"),
             arg("delta"),
             arg("selection")));

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

    {
      typedef grid_points_in_sphere_around_atom_and_distances w_t;

      class_<w_t>("grid_points_in_sphere_around_atom_and_distances", no_init)
        .def(init<cctbx::uctbx::unit_cell const&,
                  af::const_ref<double, af::c_grid<3> > const&,
                  double const&,
                  double const&,
                  scitbx::vec3<double> const& >(
                    (arg("unit_cell"),
                     arg("data"),
                     arg("radius"),
                     arg("shell"),
                     arg("site_frac"))))
        .def("data_at_grid_points", &w_t::data_at_grid_points)
        .def("data_at_grid_points_averaged", &w_t::data_at_grid_points_averaged)
        .def("distances", &w_t::distances)
      ;
    }

    {
      typedef non_linear_map_modification_to_match_average_cumulative_histogram w_t;

      class_<w_t>("non_linear_map_modification_to_match_average_cumulative_histogram", no_init)
        .def(init<af::const_ref<double, af::c_grid<3> > const&,
                  af::const_ref<double, af::c_grid<3> > const& >(
                    (arg("map_1"),
                     arg("map_2"))))
        .def("map_1", &w_t::map_1)
        .def("map_2", &w_t::map_2)
        .def("histogram_1", &w_t::histogram_1)
        .def("histogram_2", &w_t::histogram_2)
        .def("histogram_12", &w_t::histogram_12)
        .def("histogram_values", &w_t::histogram_values)
      ;
    }

    {
      typedef d99 w_t;

      class_<w_t>("d99", no_init)
        .def(init<
          af::const_ref<std::complex<double> > const&,
          af::const_ref<double> const&,
          af::const_ref<miller::index<> > const&,
          double const&,
          double const& >(
                    (arg("f"),
                     arg("d_spacings"),
                     arg("hkl"),
                     arg("d_min"),
                     arg("d_max"))))
        .def("d_min_cc9",      &w_t::d_min_cc9)
        .def("d_min_cc99",     &w_t::d_min_cc99)
        .def("d_min_cc999",    &w_t::d_min_cc999)
        .def("d_min_cc9999",   &w_t::d_min_cc9999)
        .def("d_min_cc99999",  &w_t::d_min_cc99999)
        .def("d_min_cc999999", &w_t::d_min_cc999999)
      ;
    }

    {
      typedef fsc w_t;

      class_<w_t>("fsc", no_init)
        .def(init<
          af::const_ref<std::complex<double> > const&,
          af::const_ref<std::complex<double> > const&,
          af::const_ref<double> const&,
          int const& >(
                    (arg("f1"),
                     arg("f2"),
                     arg("d_spacings"),
                     arg("step"))))
        .def("fsc",   &w_t::cc)
        .def("d",     &w_t::d)
        .def("d_inv", &w_t::d_inv)
      ;
    }

    {
      typedef histogram w_t;

      class_<w_t>("histogram", no_init)
        .def(init<af::const_ref<double, af::c_grid<3> > const&,
                  int const& >(
                    (arg("map"),
                     arg("n_bins"))))
        .def(init<af::const_ref<double> const&,
                  int const& >(
                    (arg("map"),
                     arg("n_bins"))))
        .def("values",    &w_t::values)
        .def("c_values",  &w_t::c_values)
        .def("v_values",  &w_t::v_values)
        .def("bin_width", &w_t::bin_width)
        .def("arguments", &w_t::arguments)
      ;
    }

    {
      typedef volume_scale w_t;

      class_<w_t>("volume_scale", no_init)
        .def(init<af::const_ref<double, af::c_grid<3> > const&,
                  int const& >(
                    (arg("map"),
                     arg("n_bins"))))
        .def("map_data", &w_t::map_data)
        .def("v_values", &w_t::v_values)
      ;
    }

    {
      typedef volume_scale_1d w_t;

      class_<w_t>("volume_scale_1d", no_init)
        .def(init<af::const_ref<double> const&,
                  int const& >(
                    (arg("map"),
                     arg("n_bins"))))
        .def("map_data", &w_t::map_data)
        .def("v_values", &w_t::v_values)
      ;
    }

    {
      typedef one_gaussian_peak_approximation w_t;

      class_<w_t>("one_gaussian_peak_approximation", no_init)
        .def(init<af::const_ref<double> const&,
                  af::const_ref<double> const&,
                  bool const&,
                  bool const& >(
                    (arg("data_at_grid_points"),
                     arg("distances"),
                     arg("use_weights"),
                     arg("optimize_cutoff_radius"))))
        .def("a_real_space", &w_t::a_real_space)
        .def("b_real_space", &w_t::b_real_space)
        .def("a_reciprocal_space", &w_t::a_reciprocal_space)
        .def("b_reciprocal_space", &w_t::b_reciprocal_space)
        .def("gof", &w_t::gof)
        .def("cutoff_radius", &w_t::cutoff_radius)
        .def("weight_power", &w_t::weight_power)
        .def("first_zero_radius", &w_t::first_zero_radius)
      ;
    }

    {
      typedef connectivity w_t;

      class_<w_t>("connectivity", no_init)
        .def(init<af::const_ref<float, af::flex_grid<> > const&,
                  float const&,
                  bool >(
                    (arg("map_data"),
                     arg("threshold"),
                     arg("wrapping")=true)))
        .def(init<af::const_ref<double, af::flex_grid<> > const&,
                  double const&,
                  bool >(
                    (arg("map_data"),
                     arg("threshold"),
                     arg("wrapping")=true)))
        .def(init<af::const_ref<int, af::flex_grid<> > const&,
                  int const&,
                  bool >(
                    (arg("map_data"),
                     arg("threshold"),
                     arg("wrapping")=true)))
        .def("result",    &w_t::result)
        .def("regions",   &w_t::regions)
        .def("volume_cutoff_mask", &w_t::volume_cutoff_mask,
                    (arg("volume_cutoff")))
        .def("get_blobs_boundaries", &w_t::get_blobs_boundaries)
        .def("expand_mask", &w_t::expand_mask,
                  (arg("id_to_expand"),
                   arg("expand_size")))
        .def("noise_elimination_two_cutoffs",
             &w_t::noise_elimination_two_cutoffs,
                    (arg("connectivity_object_at_t1"),
                     arg("elimination_volume_threshold_at_t1"),
                     arg("zero_all_interblob_region")=true))
        .def("maximum_coors", &w_t::maximum_coors)
        .def("maximum_values", &w_t::maximum_values)
      ;
    }
    {
      typedef marked_grid_points w_t;

      class_<w_t>("marked_grid_points", no_init)
        .def(init<af::const_ref<bool, af::flex_grid<> > const&,
                  int const&
                  >(
                    (arg("map_data"),
                     arg("every_nth_point")
                     )))
        .def("result",    &w_t::result)
      ;
    }

    {
      typedef sample_all_mask_regions w_t;
      class_<w_t>("sample_all_mask_regions", no_init)
        .def(init<af::const_ref<int, af::flex_grid<> > const&,
            af::shared<int> const&,
            af::shared<int> const&,
            cctbx::uctbx::unit_cell const& > ((
              arg("mask"),
              arg("volumes"),
              arg("sampling_rates"),
              arg("unit_cell"))))
        .def("get_array", &w_t::get_array,
          (arg("n")))
      ;
    }

    {
      typedef zero_boundary_box_map w_t;
      class_<w_t>("zero_boundary_box_map", no_init)
        .def(init<af::const_ref<double, af::flex_grid<> > const&,
            int const& > ((
              arg("mask"),
              arg("boundary"))))
        .def("result",    &w_t::result)
      ;
    }

    def("copy",
      (af::versa<float, af::flex_grid<> >(*)
        (af::const_ref<float, af::flex_grid<> > const&,
         af::flex_grid<> const&)) maptbx::copy, (
      arg("map"),
      arg("result_grid")));
    def("copy",
      (af::versa<double, af::flex_grid<> >(*)
        (af::const_ref<double, af::flex_grid<> > const&,
         af::flex_grid<> const&)) maptbx::copy, (
      arg("map"),
      arg("result_grid")));
    def("copy",
      (af::versa<float, af::flex_grid<> >(*)
        (af::const_ref<float, c_grid_padded_p1<3> > const&,
         af::int3 const&,
         af::int3 const&)) maptbx::copy, (
      arg("map_unit_cell"),
      arg("first"),
      arg("last")));
    def("copy",
      (af::versa<double, af::flex_grid<> >(*)
        (af::const_ref<double, c_grid_padded_p1<3> > const&,
         af::int3 const&,
         af::int3 const&)) maptbx::copy, (
      arg("map_unit_cell"),
      arg("first"),
      arg("last")));
    def("copy_box",
      (af::versa<float, af::flex_grid<> >(*)
        (af::const_ref<float, af::flex_grid<> > const&,
         af::int3 const&,
         af::int3 const&)) maptbx::copy_box, (
      arg("map"),
      arg("first"),
      arg("last")));
    def("copy_box",
      (af::versa<double, af::flex_grid<> >(*)
        (af::const_ref<double, af::flex_grid<> > const&,
         af::int3 const&,
         af::int3 const&)) maptbx::copy_box, (
      arg("map"),
      arg("first"),
      arg("last")));
    def("unpad_in_place",
      (void(*)(af::versa<float, af::flex_grid<> >&))
        maptbx::unpad_in_place, (arg("map")));
    def("unpad_in_place",
      (void(*)(af::versa<double, af::flex_grid<> >&))
        maptbx::unpad_in_place, (arg("map")));

    def("fft_to_real_map_unpadded",
      (af::versa<double, af::c_grid<3> >(*)(
        sgtbx::space_group const&,
        af::tiny<int, 3> const&,
        af::const_ref<miller::index<> > const&,
        af::const_ref<std::complex<double> > const&))
          maptbx::fft_to_real_map_unpadded, (
            arg("space_group"),
            arg("n_real"),
            arg("miller_indices"),
            arg("data")));

    def("direct_summation_at_point",
      (std::complex<double>(*)(
        af::const_ref<miller::index<> > const&,
        af::const_ref<std::complex<double> > const&,
        scitbx::vec3<double>))
          maptbx::direct_summation_at_point, (
            arg("miller_indices"),
            arg("data"),
            arg("site_frac")));

    def("cc_weighted_maps",cc_weighted_maps);

    def("kuwahara_filter",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         int const&)) kuwahara_filter, (
      arg("map_data"),
      arg("index_span")));

    def("median_filter",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         int const&)) median_filter, (
      arg("map_data"),
      arg("index_span")));

    def("remove_single_node_peaks",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         af::ref<double, af::c_grid_padded<3> >,
         double const&,
         int const&)) remove_single_node_peaks, (
      arg("map_data"),
      arg("mask_data"),
      arg("cutoff"),
      arg("index_span")));

    def("map_box_average",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         double const&,
         int const&)) map_box_average, (
      arg("map_data"),
      arg("cutoff"),
      arg("index_span")));

    def("map_box_average",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         int const&)) map_box_average, (
      arg("map_data"),
      arg("index_span")));

    def("center_of_mass",
      (cctbx::cartesian<>(*)
        (af::const_ref<double, af::c_grid<3> > const&,
         uctbx::unit_cell const&,
         double const&)) center_of_mass, (
      arg("map_data"),
      arg("unit_cell"),
      arg("cutoff")));

    def("fit_point_3d_grid_search",
      (cctbx::cartesian<>(*)
        (cctbx::cartesian<> const&,
         af::const_ref<double, af::c_grid<3> > const&,
         uctbx::unit_cell const&,
         double const&,
         double const&)) fit_point_3d_grid_search, (
      arg("site_cart"),
      arg("map_data"),
      arg("unit_cell"),
      arg("amplitude"),
      arg("increment")));

    def("sharpen",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         int const&,
         int const&,
         bool)) sharpen, (
      arg("map_data"),
      arg("index_span"),
      arg("n_averages"),
      arg("allow_negatives")));

    def("gamma_compression",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         double const&)) gamma_compression, (
      arg("map_data"),
      arg("gamma")));

    def("map_box_average",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         cctbx::uctbx::unit_cell const&,
         double const&)) map_box_average, (
      arg("map_data"),
      arg("unit_cell"),
      arg("radius")));

    def("hoppe_gassman_modification",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         double,
         int)) hoppe_gassman_modification, (
      arg("data"),
      arg("mean_scale"),
      arg("n_iterations")));

    def("hoppe_gassman_modification2",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         int)) hoppe_gassman_modification2, (
      arg("data"),
      arg("n_iterations")));

    def("sphericity_tensor",
      (scitbx::sym_mat3<double>(*)
        (af::const_ref<double, af::c_grid<3> > const&,
          uctbx::unit_cell const&,
         double const&,
         cctbx::fractional<> const&)) sphericity_tensor, (
      arg("map_data"),
      arg("unit_cell"),
      arg("radius"),
      arg("site_frac")));

    def("sphericity",
      (af::shared<double>(*)
        (af::const_ref<double, af::c_grid<3> > const&,
          uctbx::unit_cell const&,
         double const&,
         af::const_ref<scitbx::vec3<double> > const&)) sphericity, (
      arg("map_data"),
      arg("unit_cell"),
      arg("radius"),
      arg("sites_frac")));

    def("average_densities",
      (af::shared<double>(*)
        (uctbx::unit_cell const&,
         af::const_ref<double, af::c_grid<3> > const&,
         af::const_ref<scitbx::vec3<double> > const&,
         float)) average_densities, (
      arg("unit_cell"),
      arg("data"),
      arg("sites_frac"),
      arg("radius")));

    def("mask",
      (af::versa<double, af::c_grid<3> >(*)
        (af::const_ref<scitbx::vec3<double> > const&,
          uctbx::unit_cell const&,
          af::tiny<int, 3> const&,
          double const&,
          double const&,
          af::const_ref<double> const&,
          bool const&)) mask, (
      arg("sites_frac"),
      arg("unit_cell"),
      arg("n_real"),
      arg("mask_value_inside_molecule"),
      arg("mask_value_outside_molecule"),
      arg("radii"),
      arg("wrapping")=true
       ));

    def("convert_to_non_negative",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         double)) convert_to_non_negative, (
      arg("data"),
      arg("substitute_value")));

    def("flexible_boundary_mask",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         af::ref<double, af::c_grid<3> >)) flexible_boundary_mask, (
      arg("data"),
      arg("mask")));

    def("negate_selected_in_place",
      (af::versa<double, af::c_grid_padded<3> >(*)
        (af::const_ref<double, af::c_grid_padded<3> > const&,
         std::vector<unsigned> const&)) negate_selected_in_place, (
      arg("map_data"),
      arg("selection")));

    def("reset",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         double,
         double,
         double,
         bool)) reset, (
      arg("data"),
      arg("substitute_value"),
      arg("less_than_threshold"),
      arg("greater_than_threshold"),
      arg("use_and")));

    def("intersection",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         af::ref<double, af::c_grid<3> >,
         af::ref<double> const&,
         bool)) intersection, (
      arg("map_data_1"),
      arg("map_data_2"),
      arg("thresholds"),
      arg("average")));

    def("rotate_translate_map",
      (af::versa<double, af::c_grid<3> >(*)
        (uctbx::unit_cell const&,
         af::const_ref<double, af::c_grid<3> > const&,
         scitbx::mat3<double> const&,
         scitbx::vec3<double> const&,
         af::tiny<int, 3> const&,
         af::tiny<int, 3> const&)) rotate_translate_map, (
      arg("unit_cell"),
      arg("map_data"),
      arg("rotation_matrix"),
      arg("translation_vector"),
      arg("start"),
      arg("end")));

    def("rotate_translate_map",
      (af::versa<double, af::c_grid<3> >(*)
        (uctbx::unit_cell const&,
         af::const_ref<double, af::c_grid<3> > const&,
         scitbx::mat3<double> const&,
         scitbx::vec3<double> const& )) rotate_translate_map, (
      arg("unit_cell"),
      arg("map_data"),
      arg("rotation_matrix"),
      arg("translation_vector")));

    def("superpose_maps",
      (af::versa<double, af::c_grid<3> >(*)
        (uctbx::unit_cell const&,
         uctbx::unit_cell const&,
         af::const_ref<double, af::c_grid<3> > const&,
         af::tiny<int, 3> const&,
         scitbx::mat3<double> const&,
         scitbx::vec3<double> const&,
         bool wrap )) superpose_maps, (
      arg("unit_cell_1"),
      arg("unit_cell_2"),
      arg("map_data_1"),
      arg("n_real_2"),
      arg("rotation_matrix"),
      arg("translation_vector"),
      arg("wrapping")=true));

    def("combine_and_maximize_maps",
      (af::versa<double, af::c_grid<3> >(*)
        (af::const_ref<double, af::c_grid<3> > const&,
         af::const_ref<double, af::c_grid<3> > const&,
         af::tiny<int, 3> const& )) combine_and_maximize_maps, (
      arg("map_data_1"),
      arg("map_data_2"),
      arg("n_real")));

    def("denmod_simple",
      (af::versa<double, af::c_grid<3> >(*)
        (af::const_ref<double, af::c_grid<3> > const&,
         af::tiny<int, 3> const&,
         double,double)) denmod_simple, (
      arg("map_data"),
      arg("n_real"),
      arg("cutoffp"),
      arg("cutoffm")));

    def("binarize",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         double const&,
         double const&,
         double const&)) binarize, (
      arg("map_data"),
      arg("threshold"),
      arg("substitute_value_below"),
      arg("substitute_value_above")));

    def("truncate_special",
      (void(*)
        (af::ref<int, af::c_grid<3> >,
         af::ref<double, af::c_grid<3> >)) truncate_special, (
      arg("mask"),
      arg("map_data")));

    def("combine_1",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         af::ref<double, af::c_grid<3> >)) combine_1, (
      arg("map_data"),
      arg("diff_map")));

    def("truncate_between_min_max",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         double const&,
         double const&)) truncate_between_min_max, (
      arg("map_data"),
      arg("min"),
      arg("max")));

    def("truncate",
      (void(*)
        (af::ref<double, af::c_grid<3> >,
         double const&,
         double const&,
         double const&,
         double const&)) truncate, (
      arg("map_data"),
      arg("standard_deviation"),
      arg("by_sigma_less_than"),
      arg("scale_by"),
      arg("set_value")));

    def("fem_averaging_loop",
      (af::shared<std::complex<double> >(*)
        (af::const_ref<std::complex<double> > const&,
         af::const_ref<double> const&,
         af::const_ref<double> const&,
         double const&,
         int const&,
         int const&)) fem_averaging_loop, (
      arg("map_coefficients"),
      arg("r_factors"),
      arg("sigma_over_f_obs"),
      arg("random_scale"),
      arg("random_seed"),
      arg("n_cycles")));

    def("conditional_solvent_region_filter",
      (af::versa<double, af::c_grid<3> >(*)
        (af::const_ref<double, af::c_grid_padded<3> > const&,
         af::const_ref<double, af::c_grid<3> > const&,
         double const&)) conditional_solvent_region_filter, (
      arg("bulk_solvent_mask"),
      arg("map_data"),
      arg("threshold")));

    def("update_f_part1_helper",
      (af::versa<double, af::c_grid<3> >(*)
        (af::const_ref<int, af::c_grid_padded<3> > const&,
         af::const_ref<double, af::c_grid<3> > const&,
         int const&)) update_f_part1_helper, (
      arg("connectivity_map"),
      arg("map_data"),
      arg("region_id")));

    def("map_sum_at_sites_frac",
      (double(*)
        (af::const_ref<double, af::c_grid<3> > const&,
         af::const_ref<scitbx::vec3<double> > const&)) map_sum_at_sites_frac, (
      arg("map_data"),
      arg("sites_frac")));

    def("discrepancy_function",
      (af::shared<double>(*)
        (af::const_ref<double> const&,
         af::const_ref<double> const&,
         af::const_ref<double> const&)) discrepancy_function, (
      arg("map_1"),
      arg("map_2"),
      arg("cutoffs")));

    def("discrepancy_function",
      (af::shared<double>(*)
        (af::const_ref<double, af::c_grid<3> > const&,
         af::const_ref<double, af::c_grid<3> > const&,
         af::const_ref<double> const&)) discrepancy_function, (
      arg("map_1"),
      arg("map_2"),
      arg("cutoffs")));

    def("cc_complex_complex",
      (af::shared<double>(*)
        (af::const_ref<std::complex<double> > const&,
         af::const_ref<std::complex<double> > const&,
         af::const_ref<double> const&,
         af::const_ref<double> const&,
         af::const_ref<double> const&,
         double const&)) cc_complex_complex, (
      arg("f_1"),
      arg("f_2"),
      arg("d_spacings"),
      arg("ss"),
      arg("d_mins"),
      arg("b_iso")));

    def("cc_complex_complex",
      (double(*)
        (af::const_ref<std::complex<double> > const&,
         af::const_ref<std::complex<double> > const&)) cc_complex_complex, (
      arg("f_1"),
      arg("f_2")));

    def("cc_peak",
      (double(*)
        (af::const_ref<double, af::c_grid<3> > const&,
         af::const_ref<double, af::c_grid<3> > const&,
         double const&)) cc_peak, (
      arg("map_1"),
      arg("map_2"),
      arg("cutoff")));

    def("set_box_copy",
      (af::versa<double, af::c_grid<3> >(*)
        (double const&,
         af::ref<double, af::c_grid<3> >,
         af::tiny<int, 3> const&,
         af::tiny<int, 3> const&)) set_box_copy, (
      arg("value"),
      arg("map_data_to"),
      arg("start"),
      arg("end")));

    def("set_box",
      (void(*)
        (double const&,
         af::ref<double, af::c_grid<3> >,
         af::tiny<int, 3> const&,
         af::tiny<int, 3> const&)) set_box, (
      arg("value"),
      arg("map_data_to"),
      arg("start"),
      arg("end")));

    def("set_box",
      (void(*)
        (af::const_ref<double, af::c_grid<3> > const&,
         af::ref<double, af::c_grid<3> >,
         af::tiny<int, 3> const&,
         af::tiny<int, 3> const&)) set_box, (
      arg("map_data_from"),
      arg("map_data_to"),
      arg("start"),
      arg("end")));

    def("copy_box",
      (void(*)
        (af::const_ref<double, af::c_grid<3> > const&,
         af::ref<double, af::c_grid<3> >,
         af::tiny<int, 3> const&,
         af::tiny<int, 3> const&)) copy_box, (
      arg("map_data_from"),
      arg("map_data_to"),
      arg("start"),
      arg("end")));

    def("eight_point_interpolation",
      (double(*)
        (af::const_ref<double, af::flex_grid<> > const&,
         scitbx::vec3<double> const&)) eight_point_interpolation);
    def("eight_point_interpolation",
      (double(*)
        (af::const_ref<double, af::c_grid_padded<3> > const&,
         scitbx::vec3<double> const&)) eight_point_interpolation);
    def("eight_point_interpolation",
      (double(*)
        (af::const_ref<double, af::c_grid<3> > const&,
         scitbx::vec3<double> const&)) eight_point_interpolation);
    def("eight_point_interpolation_with_gradients",
      (af::tiny<double, 4>(*)
        (af::const_ref<double, af::c_grid_padded<3> > const&,
         scitbx::vec3<double> const&,
         scitbx::vec3<double> const&)) eight_point_interpolation_with_gradients);
    def("quadratic_interpolation_with_gradients",
      (af::tiny<double, 4>(*)
        (//af::const_ref<double, af::flex_grid<> > const&,
         af::const_ref<double, af::c_grid_padded<3> > const&,
         scitbx::vec3<double> const&,
         scitbx::vec3<double> const&)) quadratic_interpolation_with_gradients);
    def("closest_grid_point",
      (af::c_grid_padded<3>::index_type(*)
        (af::flex_grid<> const&,
         fractional<double> const&)) closest_grid_point);
    def("tricubic_interpolation_with_gradients",
      (af::tiny<double, 4>(*)
        (af::const_ref<double, af::c_grid_padded<3> > const&,
         scitbx::vec3<double> const&,
         scitbx::vec3<double> const&)) tricubic_interpolation_with_gradients);
    def("tricubic_interpolation",
      (double(*)
        (af::const_ref<double, af::c_grid_padded<3> > const&,
         scitbx::vec3<double> const&)) tricubic_interpolation);
    def("non_crystallographic_eight_point_interpolation",
      (double(*)
        (af::const_ref<double, af::flex_grid<> > const&,
         scitbx::mat3<double> const&,
         scitbx::vec3<double> const&,
         bool,
         double const&))
           non_crystallographic_eight_point_interpolation, (
             arg("map"),
             arg("gridding_matrix"),
             arg("site_cart"),
             arg("allow_out_of_bounds")=false,
             arg("out_of_bounds_substitute_value")=0));
    def("asu_eight_point_interpolation",
      (double(*)
        (af::const_ref<double, af::flex_grid<> > const&,
         crystal::direct_space_asu::asu_mappings<double> &,
         fractional<double> const&)) asu_eight_point_interpolation);

    def("standard_deviations_around_sites",
      standard_deviations_around_sites, (
        arg("unit_cell"),
        arg("density_map"),
        arg("sites_cart"),
        arg("site_radii")));
  }

} // namespace <anonymous>
}}} // namespace cctbx::maptbx::boost_python

BOOST_PYTHON_MODULE(cctbx_maptbx_ext)
{
  cctbx::maptbx::boost_python::init_module();
}


#include "MeshViewerCore.h"
#include "MeshCuboidEvaluator.h"
#include "MeshCuboidFusion.h"
#include "MeshCuboidParameters.h"
#include "MeshCuboidTrainer.h"

#include <Eigen/Core>
#include <Eigen/Geometry>
#include <QDir>
#include <QFileInfo>

void get_bounding_cylinder(const MeshCuboidStructure &_cuboid_structure,
	Eigen::MatrixXd &_sample_points, Eigen::VectorXd &_bbox_center,
	Real &_xy_size, Real &_z_size)
{
	const unsigned int num_input_points = _cuboid_structure.sample_points_.size();
	assert(num_input_points > 0);
	_sample_points = Eigen::MatrixXd(3, num_input_points);

	for (SamplePointIndex sample_point_index = 0; sample_point_index < num_input_points;
		++sample_point_index)
	{
		const MeshSamplePoint *sample_point = _cuboid_structure.sample_points_[sample_point_index];
		assert(sample_point);
		for (int i = 0; i < 3; ++i)
			_sample_points.col(sample_point_index)[i] = sample_point->point_[i];
	}

	Eigen::VectorXd input_bbox_min = _sample_points.rowwise().minCoeff();
	Eigen::VectorXd input_bbox_max = _sample_points.rowwise().maxCoeff();
	Eigen::VectorXd input_bbox_size = input_bbox_max - input_bbox_min;

	_bbox_center = 0.5 * (input_bbox_min + input_bbox_max);
	_xy_size = std::sqrt(input_bbox_size[0] * input_bbox_size[0] + input_bbox_size[1] * input_bbox_size[1]);
	_z_size = input_bbox_size[2];
}

void get_transformed_sample_points(const MeshCuboidStructure &_cuboid_structure,
	const Real _xy_size, const Real _z_size, const Real _angle, Eigen::MatrixXd &_transformed_sample_points)
{
	Eigen::MatrixXd sample_points;
	Eigen::VectorXd bbox_center;
	Real xy_size, z_size;
	get_bounding_cylinder(_cuboid_structure, sample_points, bbox_center, xy_size, z_size);

	_transformed_sample_points = sample_points.colwise() - bbox_center;
	for (SamplePointIndex sample_point_index = 0; sample_point_index < _transformed_sample_points.cols();
		++sample_point_index)
	{
		_transformed_sample_points.col(sample_point_index)[0] *= (_xy_size / xy_size);
		_transformed_sample_points.col(sample_point_index)[1] *= (_xy_size / xy_size);
		_transformed_sample_points.col(sample_point_index)[2] *= (_z_size / z_size);
	}

	if (_angle != 0)
	{
		Eigen::AngleAxisd axis_rotation(_angle, Eigen::Vector3d::UnitZ());
		_transformed_sample_points = axis_rotation.toRotationMatrix() * _transformed_sample_points;
	}

	_transformed_sample_points = _transformed_sample_points.colwise() + bbox_center;
}

void MeshViewerCore::run_part_assembly_align_database(const std::string _mesh_filepath,
	Real &_xy_size, Real &_z_size, Real &_angle)
{
	QFileInfo mesh_file_info(_mesh_filepath.c_str());
	std::string mesh_name(mesh_file_info.baseName().toLocal8Bit());

	Eigen::MatrixXd input_points;
	Eigen::VectorXd input_bbox_center;
	get_bounding_cylinder(cuboid_structure_, input_points, input_bbox_center,
		_xy_size, _z_size);

	ANNpointArray input_ann_points;
	ANNkd_tree* input_ann_kd_tree = ICP::create_kd_tree(input_points, input_ann_points);

	const unsigned int num_angles = 360;
	Real angle_unit = 2 * M_PI / static_cast<Real>(num_angles);
	std::vector<Real> angle_scores(num_angles, 0);

	MyMesh example_mesh;
	MeshCuboidStructure example_cuboid_structure(&example_mesh);

	// For every file in the base path.
	QDir input_dir((FLAGS_data_root_path + FLAGS_mesh_path).c_str());
	assert(input_dir.exists());
	input_dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks);
	input_dir.setSorting(QDir::Name);

	QFileInfoList dir_list = input_dir.entryInfoList();
	for (int i = 0; i < dir_list.size(); i++)
	{
		QFileInfo example_file_info = dir_list.at(i);
		if (example_file_info.exists() &&
			(example_file_info.suffix().compare("obj") == 0
			|| example_file_info.suffix().compare("off") == 0))
		{
			std::string example_mesh_filepath = std::string(example_file_info.filePath().toLocal8Bit());
			std::string example_mesh_name(example_file_info.baseName().toLocal8Bit());

			// Skip if the mesh is the same with the input mesh.
			if (example_mesh_name.compare(mesh_name) == 0)
				continue;

			bool ret = load_object_info(example_mesh, example_cuboid_structure,
				example_mesh_filepath.c_str(), LoadSamplePoints, NULL, false);
			if (!ret) continue;

			Eigen::MatrixXd scaled_example_points;
			get_transformed_sample_points(example_cuboid_structure, _xy_size, _z_size, 0, scaled_example_points);

			for (unsigned int angle_index = 0; angle_index < num_angles; ++angle_index)
			{
				Eigen::AngleAxisd axis_rotation(angle_index * angle_unit, Eigen::Vector3d::UnitZ());
				Eigen::MatrixXd rotated_example_points = axis_rotation.toRotationMatrix() * scaled_example_points;

				ANNpointArray rotated_example_ann_points;
				ANNkd_tree* rotated_example_ann_kd_tree = ICP::create_kd_tree(rotated_example_points,
					rotated_example_ann_points);

				Eigen::VectorXd input_to_rotated_example_distances;
				ICP::get_closest_points(rotated_example_ann_kd_tree, input_points, input_to_rotated_example_distances);

				Eigen::VectorXd rotated_example_to_input_distances;
				ICP::get_closest_points(input_ann_kd_tree, rotated_example_points, rotated_example_to_input_distances);

				Real score = std::max(input_to_rotated_example_distances.maxCoeff(),
					rotated_example_to_input_distances.maxCoeff());
				angle_scores[angle_index] += score;

				annDeallocPts(rotated_example_ann_points);
				delete rotated_example_ann_kd_tree;
			}
		}
	}

	annDeallocPts(input_ann_points);
	delete input_ann_kd_tree;

	Real min_score = std::numeric_limits<Real>::max();
	unsigned int min_angle_index = 0;
	for (unsigned int angle_index = 0; angle_index < num_angles; ++angle_index)
	{
		if (angle_scores[angle_index] < min_score)
		{
			min_angle_index = angle_index;
			min_score = angle_scores[angle_index];
		}
	}

	std::cout << "[" << min_angle_index << "]: " << min_score << std::endl;
	_angle = min_angle_index * angle_unit;
}

void MeshViewerCore::run_part_assembly_render_alignment(const std::string _mesh_filepath,
	const Real _xy_size, const Real _z_size, const Real _angle, const std::string _output_filename)
{
	MyMesh example_mesh;
	MeshCuboidStructure example_cuboid_structure(&example_mesh);

	// Load the same object.
	bool ret = load_object_info(example_mesh, example_cuboid_structure,
		_mesh_filepath.c_str(), LoadDenseSamplePoints, NULL, false);
	assert(ret);

	// Render partial scanned input points with error = 0 (for coloring).
	Eigen::MatrixXd aligned_example_points;
	get_transformed_sample_points(example_cuboid_structure, _xy_size, _z_size, _angle, aligned_example_points);

	for (SamplePointIndex sample_point_index = 0; sample_point_index < cuboid_structure_.num_sample_points();
		++sample_point_index)
	{
		MeshSamplePoint *sample_point = cuboid_structure_.sample_points_[sample_point_index];
		sample_point->error_ = 0.0;
	}

	// Render transformed original points with error = 1 (for coloring).
	assert(aligned_example_points.cols() == example_cuboid_structure.num_sample_points());
	for (SamplePointIndex sample_point_index = 0; sample_point_index < example_cuboid_structure.num_sample_points();
		++sample_point_index)
	{
		MeshSamplePoint *sample_point = example_cuboid_structure.sample_points_[sample_point_index];
		MyMesh::Normal normal = sample_point->normal_;
		MyMesh::Point point;
		for (int i = 0; i < 3; ++i)
			point[i] = aligned_example_points.col(sample_point_index)[i];
		MeshSamplePoint *new_sample_point = cuboid_structure_.add_sample_point(point, normal);
		new_sample_point->error_ = 1.0;
	}

	setDrawMode(COLORED_POINT_SAMPLES);
	updateGL();
	snapshot(_output_filename);

	setDrawMode(CUSTOM_VIEW);
}

void get_consistent_matching_parts(
	const MeshCuboidStructure &_cuboid_structure,
	const MeshCuboidTrainer &_trainer,
	std::vector< std::pair<Real, std::string> > &_label_matched_object_scores,
	std::vector<std::string> &_label_matched_objects)
{
	unsigned int num_labels = _cuboid_structure.num_labels();
	assert(_label_matched_object_scores.size() == num_labels);

	_label_matched_objects.clear();
	_label_matched_objects.resize(num_labels, "");

	// Remove conflicted labels.
	std::vector< std::list<LabelIndex> > conflicted_labels;
	_trainer.get_conflicted_labels(conflicted_labels);
	assert(conflicted_labels.size() == num_labels);

	bool *is_label_index_visited = new bool[num_labels];
	memset(is_label_index_visited, false, num_labels * sizeof(bool));

	while (true)
	{
		Real max_score = 0;
		LabelIndex max_score_label_index = 0;

		for (LabelIndex label_index = 0; label_index < num_labels; ++label_index)
		{
			Real score = _label_matched_object_scores[label_index].first;
			if (!is_label_index_visited[label_index] && score > max_score)
			{
				max_score = _label_matched_object_scores[label_index].first;
				max_score_label_index = label_index;
			}
		}

		if (max_score == 0)
			break;

		std::string other_mesh_filepath = _label_matched_object_scores[max_score_label_index].second;
		_label_matched_objects[max_score_label_index] = other_mesh_filepath;
		is_label_index_visited[max_score_label_index] = true;

		for (std::list<LabelIndex>::iterator it = conflicted_labels[max_score_label_index].begin();
			it != conflicted_labels[max_score_label_index].end(); ++it)
		{
			assert(*it < num_labels);
			is_label_index_visited[*it] = true;
		}
	}

	delete[] is_label_index_visited;

	// NOTE:
	// Select the same 3D model for symmetric parts.
	for (std::vector< MeshCuboidSymmetryGroupInfo >::const_iterator it = _cuboid_structure.symmetry_group_info_.begin();
		it != _cuboid_structure.symmetry_group_info_.end(); ++it)
	{
		const MeshCuboidSymmetryGroupInfo &symmetry_group = (*it);
		for (std::vector< std::pair<LabelIndex, LabelIndex> >::const_iterator jt = symmetry_group.pair_label_indices_.begin();
			jt != symmetry_group.pair_label_indices_.end(); ++jt)
		{
			LabelIndex label_index_1 = (*jt).first;
			LabelIndex label_index_2 = (*jt).second;

			// If either one of symmetric cuboids exists.
			if (_label_matched_objects[label_index_1] != ""
				|| _label_matched_objects[label_index_2] != "")
			{
				if (_label_matched_object_scores[label_index_1].first >= _label_matched_object_scores[label_index_2].first)
					_label_matched_objects[label_index_2] = _label_matched_objects[label_index_1];
				else
					_label_matched_objects[label_index_1] = _label_matched_objects[label_index_2];
			}
		}
	}
}

void MeshViewerCore::run_part_assembly_match_parts(const std::string _mesh_filepath,
	const Real _xy_size, const Real _z_size, const Real _angle,
	const MeshCuboidTrainer &_trainer, std::vector<std::string> &_label_matched_objects)
{
	// Parameters.
	const Real part_assembly_window_size = FLAGS_param_part_assembly_window_size *
		cuboid_structure_.mesh_->get_object_diameter();
	const Real part_assembly_voxel_size = FLAGS_param_part_assembly_voxel_size *
		cuboid_structure_.mesh_->get_object_diameter();
	const Real part_assembly_voxel_variance = FLAGS_param_part_assembly_voxel_variance *
		cuboid_structure_.mesh_->get_object_diameter();
	const Real distance_param = 2 * part_assembly_voxel_variance * part_assembly_voxel_variance;
	assert(distance_param > 0);

	QFileInfo mesh_file_info(_mesh_filepath.c_str());
	std::string mesh_name(mesh_file_info.baseName().toLocal8Bit());

	// Create input point KD tree.
	unsigned int num_labels = cuboid_structure_.num_labels();

	Eigen::MatrixXd input_sample_points(3, cuboid_structure_.num_sample_points());
	for (SamplePointIndex sample_point_index = 0; sample_point_index < cuboid_structure_.num_sample_points();
		++sample_point_index)
	{
		MeshSamplePoint *sample_point = cuboid_structure_.sample_points_[sample_point_index];
		assert(sample_point);
		for (unsigned int i = 0; i < 3; ++i)
			input_sample_points.col(sample_point_index)(i) = sample_point->point_[i];
	}

	ANNpointArray input_ann_points;
	ANNkd_tree* input_ann_kd_tree = ICP::create_kd_tree(input_sample_points, input_ann_points);

	// Load database.
	MyMesh example_mesh;
	MeshCuboidStructure example_cuboid_structure(&example_mesh);

	bool ret = true;
	ret = ret & example_cuboid_structure.load_labels((FLAGS_data_root_path +
		FLAGS_label_info_path + FLAGS_label_info_filename).c_str());
	ret = ret & example_cuboid_structure.load_label_symmetries((FLAGS_data_root_path +
		FLAGS_label_info_path + FLAGS_label_symmetry_info_filename).c_str());

	// Load symmetry groups.
	ret = ret & example_cuboid_structure.load_symmetry_groups((FLAGS_data_root_path +
		FLAGS_label_info_path + FLAGS_symmetry_group_info_filename).c_str());

	if (!ret)
	{
		do {
			std::cout << "Error: Cannot open label information files.";
			std::cout << '\n' << "Press the Enter key to continue.";
		} while (std::cin.get() != '\n');
	}

	// For every file in the base path.
	QDir input_dir((FLAGS_data_root_path + FLAGS_mesh_path).c_str());
	assert(input_dir.exists());
	input_dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks);
	input_dir.setSorting(QDir::Name);

	// (Score, mesh_filepath)
	std::vector< std::pair<Real, std::string> > label_matched_object_scores(num_labels);
	for (LabelIndex label_index = 0; label_index < num_labels; ++label_index)
		label_matched_object_scores[label_index].first = 0;

	QFileInfoList dir_list = input_dir.entryInfoList();
	for (int i = 0; i < dir_list.size(); i++)
	{
		QFileInfo example_file_info = dir_list.at(i);
		if (example_file_info.exists() &&
			(example_file_info.suffix().compare("obj") == 0
			|| example_file_info.suffix().compare("off") == 0))
		{
			std::string example_mesh_filepath = std::string(example_file_info.filePath().toLocal8Bit());
			std::string example_mesh_name(example_file_info.baseName().toLocal8Bit());
			std::string example_cuboid_filepath = FLAGS_training_dir + std::string("/")
				+ example_mesh_name + std::string(".arff");

			// Skip if the mesh is the same with the input mesh.
			if (example_mesh_name.compare(mesh_name) == 0)
				continue;

			bool ret = load_object_info(example_mesh, example_cuboid_structure,
				example_mesh_filepath.c_str(), LoadDenseSamplePoints, example_cuboid_filepath.c_str(), false);
			if (!ret) continue;

			std::cout << "mesh: " << example_mesh_name << std::endl;

			Eigen::MatrixXd transformed_example_points;
			get_transformed_sample_points(example_cuboid_structure, _xy_size, _z_size, _angle, transformed_example_points);

			// Assign transformed points.
			for (SamplePointIndex sample_point_index = 0; sample_point_index < example_cuboid_structure.num_sample_points();
				++sample_point_index)
			{
				MeshSamplePoint *sample_point = example_cuboid_structure.sample_points_[sample_point_index];
				for (int i = 0; i < 3; ++i)
					sample_point->point_[i] = transformed_example_points.col(sample_point_index)[i];
			}

			for (LabelIndex label_index = 0; label_index < num_labels; ++label_index)
			{
				MeshCuboid *example_cuboid = NULL;

				// NOTE:
				// The current implementation assumes that there is only one part for each label.
				assert(example_cuboid_structure.label_cuboids_[label_index].size() <= 1);
				if (!example_cuboid_structure.label_cuboids_[label_index].empty())
					example_cuboid = example_cuboid_structure.label_cuboids_[label_index].front();

				if (!example_cuboid) continue;
				else if (example_cuboid->num_sample_points() == 0) continue;

				// Create cuboid KD-tree.
				std::vector<MyMesh::Point> example_sample_points;
				example_cuboid->get_sample_points(example_sample_points);

				Real max_bbox_size = 0;
				for (unsigned int axis_index = 0; axis_index < 3; ++axis_index)
					max_bbox_size = std::max(max_bbox_size, example_cuboid->get_bbox_size()[axis_index]);
				assert(max_bbox_size > 0);

				MyMesh::Point local_coord_bbox_min = example_cuboid->get_bbox_center() - MyMesh::Point(max_bbox_size);
				MyMesh::Point local_coord_bbox_max = example_cuboid->get_bbox_center() + MyMesh::Point(max_bbox_size);

				MeshCuboidVoxelGrid local_coord_voxels(local_coord_bbox_min, local_coord_bbox_max,
					part_assembly_voxel_size);
				int num_voxels = local_coord_voxels.n_voxels();

				// Compute distance maps.
				Eigen::VectorXd input_distance_map;
				local_coord_voxels.get_distance_map(input_ann_points, input_ann_kd_tree, input_distance_map);
				assert(input_distance_map.rows() == num_voxels);
				for (unsigned int i = 0; i < num_voxels; ++i)
					input_distance_map[i] = std::exp(-input_distance_map[i] * input_distance_map[i] / distance_param);

				Eigen::VectorXd example_voxel_occupancies;
				local_coord_voxels.get_voxel_occupancies(example_sample_points, example_voxel_occupancies);
				assert(example_voxel_occupancies.rows() == num_voxels);

				int num_input_occupied_voxels = (input_distance_map.array() > 0.95).count();
				int num_example_occupied_voxels = (example_voxel_occupancies.array() >= 1).count();
				if (num_input_occupied_voxels == 0 || num_example_occupied_voxels == 0)
					continue;

				// Equation (1) ~ (3).
				Real score = input_distance_map.dot(example_voxel_occupancies);
				assert(score >= 0);
				score = (1 - 0.7) * (score / num_example_occupied_voxels)
					+ (0.7) * (score / num_input_occupied_voxels);

				// DEBUG.
				printf("[%s] (%d): %lf\n", example_mesh_name.c_str(), label_index, score);

				if (score > label_matched_object_scores[label_index].first)
				{
					label_matched_object_scores[label_index].first = score;
					label_matched_object_scores[label_index].second = example_mesh_filepath;
				}
			}
		}
	}

	annDeallocPts(input_ann_points);
	delete input_ann_kd_tree;

	get_consistent_matching_parts(cuboid_structure_, _trainer,
		label_matched_object_scores, _label_matched_objects);
}

void MeshViewerCore::run_part_assembly_reconstruction(const std::string _mesh_filepath,
	const Real _xy_size, const Real _z_size, const Real _angle,
	const std::vector<std::string> &_label_matched_objects)
{
	unsigned int num_labels = cuboid_structure_.num_labels();
	assert(_label_matched_objects.size() == num_labels);

	MyMesh example_mesh;
	MeshCuboidStructure example_cuboid_structure(&example_mesh);

	bool ret = example_cuboid_structure.load_labels((FLAGS_data_root_path +
		FLAGS_label_info_path + FLAGS_label_info_filename).c_str());

	if (!ret)
	{
		do {
			std::cout << "Error: Cannot open label information files.";
			std::cout << '\n' << "Press the Enter key to continue.";
		} while (std::cin.get() != '\n');
	}

	cuboid_structure_.clear_sample_points();
	cuboid_structure_.clear_cuboids();

	for (LabelIndex label_index = 0; label_index < num_labels; ++label_index)
	{
		std::string mesh_filepath = _label_matched_objects[label_index];
		if (mesh_filepath == "")
			continue;

		QFileInfo file_info(mesh_filepath.c_str());
		std::string mesh_name(file_info.baseName().toLocal8Bit());
		std::string cuboid_filepath = FLAGS_training_dir + std::string("/") + mesh_name + std::string(".arff");

		std::cout << "--------" << std::endl;
		std::cout << "Label (" << label_index << "):" << std::endl;
		std::cout << "Mesh name: " << mesh_name << std::endl;

		bool ret = load_object_info(example_mesh, example_cuboid_structure,
			mesh_filepath.c_str(), LoadDenseSamplePoints, cuboid_filepath.c_str());
		assert(ret);

		Eigen::MatrixXd transformed_example_points;
		get_transformed_sample_points(example_cuboid_structure, _xy_size, _z_size, _angle, transformed_example_points);

		// Assign transformed points.
		for (SamplePointIndex sample_point_index = 0; sample_point_index < example_cuboid_structure.num_sample_points();
			++sample_point_index)
		{
			MeshSamplePoint *sample_point = example_cuboid_structure.sample_points_[sample_point_index];
			for (int i = 0; i < 3; ++i)
				sample_point->point_[i] = transformed_example_points.col(sample_point_index)[i];
		}

		assert(example_cuboid_structure.label_cuboids_[label_index].size() <= 1);
		MeshCuboid *example_cuboid = NULL;
		if (!example_cuboid_structure.label_cuboids_[label_index].empty())
			example_cuboid = example_cuboid_structure.label_cuboids_[label_index].front();
		assert(example_cuboid);

		const int num_points = example_cuboid->num_sample_points();
		std::cout << "# of sample points: " << num_points << std::endl;
		std::cout << "Copying... ";

		MeshCuboid *cuboid = new MeshCuboid(label_index);
		cuboid_structure_.label_cuboids_[label_index].push_back(cuboid);

		for (int point_index = 0; point_index < num_points; ++point_index)
		{
			const MeshSamplePoint* sample_point = example_cuboid->get_sample_point(point_index);

			MyMesh::Point point = sample_point->point_;
			MyMesh::Normal normal = sample_point->normal_;
			MeshSamplePoint *new_sample_point = cuboid_structure_.add_sample_point(point, normal);

			new_sample_point->label_index_confidence_.resize(num_labels, 0.0);
			new_sample_point->label_index_confidence_[label_index] = 1.0;

			cuboid->add_sample_point(new_sample_point);
		}
	}
}

void MeshViewerCore::render_part_assembly_cuboids()
{
	// Load basic information.
	bool ret = true;

	ret = ret & cuboid_structure_.load_labels((FLAGS_data_root_path +
		FLAGS_label_info_path + FLAGS_label_info_filename).c_str());
	ret = ret & cuboid_structure_.load_label_symmetries((FLAGS_data_root_path +
		FLAGS_label_info_path + FLAGS_label_symmetry_info_filename).c_str());

	// Load symmetry groups.
	ret = ret & cuboid_structure_.load_symmetry_groups((FLAGS_data_root_path +
		FLAGS_label_info_path + FLAGS_symmetry_group_info_filename).c_str());

	if (!ret)
	{
		do {
			std::cout << "Error: Cannot open label information files.";
			std::cout << '\n' << "Press the Enter key to continue.";
		} while (std::cin.get() != '\n');
	}

	// Initialize basic information.
	unsigned int num_labels = cuboid_structure_.num_labels();
	cuboid_structure_.clear_cuboids();
	cuboid_structure_.clear_sample_points();
	setDrawMode(CUSTOM_VIEW);

	std::string mesh_filepath = FLAGS_data_root_path + FLAGS_mesh_path + std::string("/") + FLAGS_mesh_filename;
	QFileInfo mesh_file(mesh_filepath.c_str());
	std::string mesh_name = std::string(mesh_file.baseName().toLocal8Bit());
	std::string mesh_output_path = FLAGS_part_assembly_dir + std::string("/") + mesh_name;

	std::string sample_filepath = mesh_output_path + std::string("/") + mesh_name + std::string("_assembly.pts");
	std::string sample_label_filepath = mesh_output_path + std::string("/") + mesh_name + std::string("_assembly_label.arff");

	ret = open_mesh(mesh_filepath.c_str());
	assert(ret);

	std::cout << " - Load sample points." << std::endl;
	ret = cuboid_structure_.load_sample_points(sample_filepath.c_str(), false);
	assert(ret);

	std::cout << " - Load sample point labels." << std::endl;
	ret = cuboid_structure_.load_sample_point_labels(sample_label_filepath.c_str());
	assert(ret);

	cuboid_structure_.compute_label_cuboids();

	std::string input_sample_filepath = FLAGS_output_dir + std::string("/") + mesh_name + std::string("/") + mesh_name + std::string("_input.pts");
	cuboid_structure_.clear_sample_points();
	ret = cuboid_structure_.load_sample_points(input_sample_filepath.c_str());
	assert(ret);

	mesh_.clear_colors();
	open_modelview_matrix_file(FLAGS_pose_filename.c_str());
	updateGL();

	std::stringstream output_filename_sstr;
	output_filename_sstr << mesh_output_path + std::string("/") + mesh_name + std::string("_assembly_cuboid");
	snapshot(output_filename_sstr.str().c_str());
}

void MeshViewerCore::run_part_assembly()
{
	// Load basic information.
	bool ret = true;

	ret = ret & cuboid_structure_.load_labels((FLAGS_data_root_path +
		FLAGS_label_info_path + FLAGS_label_info_filename).c_str());
	ret = ret & cuboid_structure_.load_label_symmetries((FLAGS_data_root_path +
		FLAGS_label_info_path + FLAGS_label_symmetry_info_filename).c_str());

	// Load symmetry groups.
	ret = ret & cuboid_structure_.load_symmetry_groups((FLAGS_data_root_path +
		FLAGS_label_info_path + FLAGS_symmetry_group_info_filename).c_str());

	if (!ret)
	{
		do {
			std::cout << "Error: Cannot open label information files.";
			std::cout << '\n' << "Press the Enter key to continue.";
		} while (std::cin.get() != '\n');
	}

	ret = true;
	MeshCuboidTrainer trainer;
	ret = ret & trainer.load_object_list(FLAGS_training_dir + std::string("/") + FLAGS_object_list_filename);
	ret = ret & trainer.load_features(FLAGS_training_dir + std::string("/") + FLAGS_feature_filename_prefix);
	ret = ret & trainer.load_transformations(FLAGS_training_dir + std::string("/") + FLAGS_transformation_filename_prefix);

	if (!ret)
	{
		do {
			std::cout << "Error: Cannot open training files.";
			std::cout << '\n' << "Press the Enter key to continue.";
		} while (std::cin.get() != '\n');
	}

	// Check file paths.
	setDrawMode(CUSTOM_VIEW);

	std::string mesh_filepath = FLAGS_data_root_path + FLAGS_mesh_path + std::string("/") + FLAGS_mesh_filename;
	QFileInfo mesh_file(mesh_filepath.c_str());
	std::string mesh_name = std::string(mesh_file.baseName().toLocal8Bit());

	if (!mesh_file.exists())
	{
		std::cerr << "Error: The mesh file does not exist (" << mesh_filepath << ")." << std::endl;
		return;
	}
	else if (!open_mesh(mesh_filepath.c_str()))
	{
		std::cerr << "Error: The mesh file is not opened (" << mesh_filepath << ")." << std::endl;
		return;
	}

	// View plane mask.
	if (FLAGS_use_view_plane_mask)
	{
		std::string mesh_output_path = FLAGS_output_dir + std::string("/") + mesh_name;
		std::string view_plane_mask_filename = mesh_output_path + std::string("/view_mask.txt");
		std::ifstream view_plane_mask_file(view_plane_mask_filename.c_str());

		if (!view_plane_mask_file.is_open())
		{
			std::cerr << "Error: The view plane mask file is not opened (" << view_plane_mask_filename << ")." << std::endl;
			return;
		}

		std::string buffer;
		std::getline(view_plane_mask_file, buffer); FLAGS_param_view_plane_mask_min_x = std::atof(buffer.c_str());
		std::getline(view_plane_mask_file, buffer); FLAGS_param_view_plane_mask_min_y = std::atof(buffer.c_str());
		std::getline(view_plane_mask_file, buffer); FLAGS_param_view_plane_mask_max_x = std::atof(buffer.c_str());
		std::getline(view_plane_mask_file, buffer); FLAGS_param_view_plane_mask_max_y = std::atof(buffer.c_str());
		view_plane_mask_file.close();
	}

	std::string filename_prefix = std::string("/") + mesh_name + std::string("_");
	std::stringstream output_filename_sstr;

	QDir output_dir;
	std::string mesh_output_path = FLAGS_part_assembly_dir + std::string("/") + mesh_name;
	output_dir.mkpath(FLAGS_part_assembly_dir.c_str());
	output_dir.mkpath(mesh_output_path.c_str());

	// Initialize basic information.
	unsigned int num_labels = cuboid_structure_.num_labels();
	cuboid_structure_.clear_cuboids();
	cuboid_structure_.clear_sample_points();

	// Load files.
	double snapshot_modelview_matrix[16];
	double occlusion_modelview_matrix[16];

	open_modelview_matrix_file(FLAGS_pose_filename.c_str());
	memcpy(snapshot_modelview_matrix, modelview_matrix(), 16 * sizeof(double));

	if (FLAGS_occlusion_pose_filename == "")
		set_random_view_direction(true);
	else
	{
		ret = open_modelview_matrix_file(FLAGS_occlusion_pose_filename.c_str());
		assert(ret);
	}
	memcpy(occlusion_modelview_matrix, modelview_matrix(), 16 * sizeof(double));
	//save_modelview_matrix_file((mesh_output_path + std::string("/occlusion_pose.txt")).c_str());

	ret = load_object_info(mesh_, cuboid_structure_, mesh_filepath.c_str(), LoadSamplePoints);
	if (!ret) return;
	set_modelview_matrix(occlusion_modelview_matrix, false);
	remove_occluded_points();
	set_modelview_matrix(snapshot_modelview_matrix);

	std::cout << "Align database... " << std::endl;
	Real xy_size, z_size, angle;
	run_part_assembly_align_database(mesh_filepath, xy_size, z_size, angle);

	std::cout << "Match parts... " << std::endl;
	std::vector<std::string> label_matched_objects;
	run_part_assembly_match_parts(mesh_filepath, xy_size, z_size, angle,
		trainer, label_matched_objects);

	ret = load_object_info(mesh_, cuboid_structure_, mesh_filepath.c_str(), LoadDenseSamplePoints);
	if (!ret) return;
	set_modelview_matrix(occlusion_modelview_matrix, false);
	remove_occluded_points();

	updateGL();
	output_filename_sstr.clear(); output_filename_sstr.str("");
	output_filename_sstr << mesh_output_path << filename_prefix << std::string("view");
	snapshot(output_filename_sstr.str().c_str());
	set_modelview_matrix(snapshot_modelview_matrix);
	updateGL();

	std::cout << "Render alignment... " << std::endl;
	output_filename_sstr.clear(); output_filename_sstr.str("");
	output_filename_sstr << mesh_output_path << filename_prefix << std::string("assembly");
	run_part_assembly_render_alignment(mesh_filepath, xy_size, z_size, angle, output_filename_sstr.str());

	std::cout << "Assemble parts... " << std::endl;
	run_part_assembly_reconstruction(mesh_filepath, xy_size, z_size, angle, label_matched_objects);

	// Evaluation.
	MyMesh ground_truth_mesh;
	MeshCuboidStructure ground_truth_cuboid_structure(&ground_truth_mesh);

	ret = true;
	ret = ret & ground_truth_cuboid_structure.load_labels((FLAGS_data_root_path +
		FLAGS_label_info_path + FLAGS_label_info_filename).c_str());
	ret = ret & ground_truth_cuboid_structure.load_label_symmetries((FLAGS_data_root_path +
		FLAGS_label_info_path + FLAGS_label_symmetry_info_filename).c_str());

	if (!ret)
	{
		do {
			std::cout << "Error: Cannot open label information files.";
			std::cout << '\n' << "Press the Enter key to continue.";
		} while (std::cin.get() != '\n');
	}

	ret = load_object_info(ground_truth_mesh, ground_truth_cuboid_structure,
		mesh_filepath.c_str(), LoadGroundTruthCuboids);
	assert(ret);
	MeshCuboidEvaluator evaluator(&ground_truth_cuboid_structure);

	evaluator.evaluate_point_to_point_distances(&cuboid_structure_, output_filename_sstr.str().c_str());
	cuboid_structure_.save_sample_points_to_ply(output_filename_sstr.str().c_str());
	cuboid_structure_.save_sample_points((output_filename_sstr.str() + std::string(".pts")).c_str());
	cuboid_structure_.save_sample_point_labels((output_filename_sstr.str() + std::string("_label.arff")).c_str());

	setDrawMode(COLORED_POINT_SAMPLES);

	updateGL();
	output_filename_sstr.clear(); output_filename_sstr.str("");
	output_filename_sstr << mesh_output_path << filename_prefix << std::string("assembly_accuracy");
	snapshot(output_filename_sstr.str().c_str());

	cuboid_structure_ = ground_truth_cuboid_structure;
	updateGL();
	output_filename_sstr.clear(); output_filename_sstr.str("");
	output_filename_sstr << mesh_output_path << filename_prefix << std::string("assembly_completeness");
	snapshot(output_filename_sstr.str().c_str()); updateGL();
	snapshot(output_filename_sstr.str().c_str());

	setDrawMode(CUSTOM_VIEW);
}


#include <map>
#include <fstream>
#include <boost/algorithm/string.hpp>

#include <iostream>
#include <ctime>

struct	per_day
{
	unsigned	cases,
			deaths;

	double		cases_inc, deaths_inc;

	double	mortality() const
	{
		if (cases == 0) return 0.0;
		return double(deaths) /cases;
	}
};

struct	country
{
	unsigned			popData2018;
	std::map<unsigned,per_day>	evolution;

	unsigned	cases = 0, deaths = 0;

	double		cases_inc_3 = 0, deaths_inc_3 = 0,
			cases_inc_7 = 0, deaths_inc_7 = 0;

	unsigned	cases_10days_ago = 0;

	const	per_day&last_day() const
	{
		return evolution.rbegin()->second;
	}

	void	analyse()
	{
	int	last_day = evolution.rbegin()->first;

		for(auto&d : evolution) // note: increment could be more than a day, should consider this
		{
		auto&D = d.second;

			cases  += D.cases;
			deaths += D.deaths;

			D.cases_inc  =  cases==0?0: D.cases  / double(cases) ;
			D.deaths_inc = deaths==0?0: D.deaths / double(deaths) ;

			if ( d.first < last_day-10)
				cases_10days_ago += d.second.cases;
		}

	unsigned	days = 0;
		for(auto r = evolution.rbegin();
		    r != evolution.rend(); r++)
		{
		auto&D = r->second;
			if (days<4)
			{
				cases_inc_3  += D.cases_inc;
				deaths_inc_3 += D.deaths_inc;
			}

			if (days<8)
			{
				cases_inc_7  += D.cases_inc;
				deaths_inc_7 += D.deaths_inc;
			}
			else break;

			days++;
		}

		cases_inc_3  /= 3;
		deaths_inc_3 /= 3;
		cases_inc_7  /= 7;
		deaths_inc_7 /= 7;
	}
};

main()
{
std::ifstream in("edc.europa.eu.csv");
std::map<std::string, country> world;

std::string line;
	getline(in, line);
unsigned linenr = 0;
	while(getline(in, line))
	{
	//	0        1    2    3     4     5               6             7        8                  9
	//	dateRep,day,month,year,cases,deaths,countriesAndTerritories,geoId,countryterritoryCode,popData2018
	std::vector<std::string> fields;
		boost::split(fields, line, boost::is_any_of(","));

		if (fields[8] == "N/A") continue; // cruise ship

		linenr++;
	int	popData2018 = 0;
		try
		{
			popData2018 = std::stol(fields[9]);
		}
		catch(...)
		{
			std::cout << "Line Number " << linenr << ": "
				  <<  fields[8] << " -->" << fields[9] << std::endl;
			continue;
		}

	int	day    = std::stoi( fields[1] ),
		month  = std::stoi( fields[2] ),
		year   = std::stoi( fields[3] ),
		cases  = std::stoi( fields[4] ),
		deaths = std::stoi( fields[5] );

	country&C = world[ fields[8] ];
		C.popData2018 = popData2018;

	std::tm time = {};
		time. tm_mday = day;
		time. tm_mon  = month;
		time. tm_year = year-1900;
		mktime( &time);

	per_day&pd = C.evolution[ time.tm_yday ];
		pd.cases = cases;
		pd.deaths = deaths;
	}

	for(auto&C : world)
	{
//		std::cout << C.first << ",";
		C.second.analyse();

#if	0
		if (C.first == "AUT")
		{
			for(const auto&d : C.second.evolution)
				std::cout << d.first << " --> " << d.second.cases << std::endl;
		}
#endif
	}

std::ofstream out("result.csv");
	out << "Country,"
		"Cases,Daily Cases,Case Inc,3-day Case Inc,7-day Case inc,"
		"Diff Case %,Cases/1k,"
		"Deaths,Daily Deaths,Death Inc,3-day Death Inc,7-day Death inc,"
		"Diff Deaths %, Deaths/1k,"
		"Mortality,Delayed Mortality,Population 2018"
	    << std::endl;
	for(const auto&w : world)
	{
	const country&C = w.second;

		out
		    << w.first
		    << "," << C.cases
		    << "," << C.last_day().cases
		    << "," << C.last_day().cases_inc*100.0
		    << "," << C.cases_inc_3*100.0
		    << "," << C.cases_inc_7*100.0
		    << "," << (C.cases_inc_3-C.cases_inc_7)*100.0
		    << "," << 1000*double(C.cases) / C.popData2018

		    << ",\t" << C.deaths
		    << "," << C.last_day().deaths
		    << "," << C.last_day().deaths_inc*100.0
		    << "," << C.deaths_inc_3*100.0
		    << "," << C.deaths_inc_7*100.0
		    << "," << (C.deaths_inc_3-C.deaths_inc_7)*100.0
		    << "," << 1000*double(C.deaths) / C.popData2018

		    << ",\t" << C.last_day().mortality()*100
		    << "," << (C.cases_10days_ago>0?100*double(C.deaths) / C.cases_10days_ago:0)

		    << ",\t" << C.popData2018

		    << std::endl;
	}

	return 0;
}


// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008 Gael Guennebaud <g.gael@free.fr>
//
// Eigen is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// Alternatively, 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 2 of
// the License, or (at your option) any later version.
//
// Eigen 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 Lesser General Public License or the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License and a copy of the GNU General Public License along with
// Eigen. If not, see <http://www.gnu.org/licenses/>.

#include "main.h"
#include <Eigen/Dense>

#define NUMBER_DIRECTIONS 16
#include <unsupported/Eigen/AdolcForward>

int adtl::ADOLC_numDir;

template<typename Vector>
EIGEN_DONT_INLINE typename Vector::Scalar foo(const Vector& p)
{
  typedef typename Vector::Scalar Scalar;
  return (p-Vector(Scalar(-1),Scalar(1.))).norm() + (p.array().sqrt().abs() * p.array().sin()).sum() + p.dot(p);
}

template<typename _Scalar, int NX=Dynamic, int NY=Dynamic>
struct TestFunc1
{
  typedef _Scalar Scalar;
  enum {
    InputsAtCompileTime = NX,
    ValuesAtCompileTime = NY
  };
  typedef Matrix<Scalar,InputsAtCompileTime,1> InputType;
  typedef Matrix<Scalar,ValuesAtCompileTime,1> ValueType;
  typedef Matrix<Scalar,ValuesAtCompileTime,InputsAtCompileTime> JacobianType;

  int m_inputs, m_values;

  TestFunc1() : m_inputs(InputsAtCompileTime), m_values(ValuesAtCompileTime) {}
  TestFunc1(int inputs, int values) : m_inputs(inputs), m_values(values) {}

  int inputs() const { return m_inputs; }
  int values() const { return m_values; }

  template<typename T>
  void operator() (const Matrix<T,InputsAtCompileTime,1>& x, Matrix<T,ValuesAtCompileTime,1>* _v) const
  {
    Matrix<T,ValuesAtCompileTime,1>& v = *_v;

    v[0] = 2 * x[0] * x[0] + x[0] * x[1];
    v[1] = 3 * x[1] * x[0] + 0.5 * x[1] * x[1];
    if(inputs()>2)
    {
      v[0] += 0.5 * x[2];
      v[1] += x[2];
    }
    if(values()>2)
    {
      v[2] = 3 * x[1] * x[0] * x[0];
    }
    if (inputs()>2 && values()>2)
      v[2] *= x[2];
  }

  void operator() (const InputType& x, ValueType* v, JacobianType* _j) const
  {
    (*this)(x, v);

    if(_j)
    {
      JacobianType& j = *_j;

      j(0,0) = 4 * x[0] + x[1];
      j(1,0) = 3 * x[1];

      j(0,1) = x[0];
      j(1,1) = 3 * x[0] + 2 * 0.5 * x[1];

      if (inputs()>2)
      {
        j(0,2) = 0.5;
        j(1,2) = 1;
      }
      if(values()>2)
      {
        j(2,0) = 3 * x[1] * 2 * x[0];
        j(2,1) = 3 * x[0] * x[0];
      }
      if (inputs()>2 && values()>2)
      {
        j(2,0) *= x[2];
        j(2,1) *= x[2];

        j(2,2) = 3 * x[1] * x[0] * x[0];
        j(2,2) = 3 * x[1] * x[0] * x[0];
      }
    }
  }
};

template<typename Func> void adolc_forward_jacobian(const Func& f)
{
    typename Func::InputType x = Func::InputType::Random(f.inputs());
    typename Func::ValueType y(f.values()), yref(f.values());
    typename Func::JacobianType j(f.values(),f.inputs()), jref(f.values(),f.inputs());

    jref.setZero();
    yref.setZero();
    f(x,&yref,&jref);
//     std::cerr << y.transpose() << "\n\n";;
//     std::cerr << j << "\n\n";;

    j.setZero();
    y.setZero();
    AdolcForwardJacobian<Func> autoj(f);
    autoj(x, &y, &j);
//     std::cerr << y.transpose() << "\n\n";;
//     std::cerr << j << "\n\n";;

    VERIFY_IS_APPROX(y, yref);
    VERIFY_IS_APPROX(j, jref);
}

void test_forward_adolc()
{
  adtl::ADOLC_numDir = NUMBER_DIRECTIONS;

  for(int i = 0; i < g_repeat; i++) {
    CALL_SUBTEST(( adolc_forward_jacobian(TestFunc1<double,2,2>()) ));
    CALL_SUBTEST(( adolc_forward_jacobian(TestFunc1<double,2,3>()) ));
    CALL_SUBTEST(( adolc_forward_jacobian(TestFunc1<double,3,2>()) ));
    CALL_SUBTEST(( adolc_forward_jacobian(TestFunc1<double,3,3>()) ));
    CALL_SUBTEST(( adolc_forward_jacobian(TestFunc1<double>(3,3)) ));
  }

  {
    // simple instanciation tests
    Matrix<adtl::adouble,2,1> x;
    foo(x);
    Matrix<adtl::adouble,Dynamic,Dynamic> A(4,4);;
    A.selfadjointView<Lower>().eigenvalues();
  }
}


#include <boost/polygon/polygon.hpp>


//////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Alejandro Cabrera 2011.
// 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)
//
// See http://www.boost.org/libs/bloom_filter for documentation.
//
//////////////////////////////////////////////////////////////////////////////

#ifndef BOOST_DETAIL_GENERATOR_HPP
#define BOOST_DETAIL_GENERATOR_HPP

#include <boost/random/linear_congruential.hpp>

namespace boost {
  namespace detail {
    template <typename T>
    struct generator {
      T operator()() {
	return gen();
      }

    private:
      boost::minstd_rand gen;
    };
  }
}
#endif


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

    @copyright 2016 NumScale SAS

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

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

  /*!
    @ingroup group-ieee
    This function object returns the exponent bits of its arguments as an integer value,
    the other bits (sign and mantissa) being just masked.

    @par Header <boost/simd/function/exponentbits.hpp>

    @par Example:

      @snippet exponentbits.cpp exponentbits

    @par Possible output:

      @snippet exponentbits.txt exponentbits

  **/
  as_integer_t<IEEEValue> exponentbits(IEEEValue const& x);
} }
#endif

#include <boost/simd/function/scalar/exponentbits.hpp>
#include <boost/simd/function/simd/exponentbits.hpp>

#endif


/**
 *  @file
 *  @copyright defined in fio/LICENSE
 */
#include <eosio/chain/block_timestamp.hpp>

#include <boost/test/unit_test.hpp>

#include <fc/time.hpp>
#include <fc/exception/exception.hpp>

using namespace eosio;
using namespace chain;

BOOST_AUTO_TEST_SUITE(block_timestamp_tests)

    BOOST_AUTO_TEST_CASE(constructor_test) {
        block_timestamp_type bt;
        BOOST_TEST(bt.slot == 0u, "Default constructor gives wrong value");

        fc::time_point t(fc::seconds(978307200));
        block_timestamp_type bt2(t);
        BOOST_TEST(bt2.slot == (978307200u - 946684800u) * 2, "Time point constructor gives wrong value");
    }

    BOOST_AUTO_TEST_CASE(conversion_test) {
        block_timestamp_type bt;
        fc::time_point t = (fc::time_point) bt;
        BOOST_TEST(t.time_since_epoch().to_seconds() == 946684800ll, "Time point conversion failed");

        block_timestamp_type bt1(200);
        t = (fc::time_point) bt1;
        BOOST_TEST(t.time_since_epoch().to_seconds() == 946684900ll, "Time point conversion failed");

    }

BOOST_AUTO_TEST_SUITE_END()


/*
  ==============================================================================

    This file was auto-generated!

    It contains the basic framework code for a JUCE plugin processor.

  ==============================================================================
*/

#include <cstring>

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

#include "PluginProcessor.h"
#include "PluginEditor.h"

//==============================================================================
ATKJUCEAudioProcessor::ATKJUCEAudioProcessor()
:
#ifndef JucePlugin_PreferredChannelConfigurations
    AudioProcessor (BusesProperties()
                     #if ! JucePlugin_IsMidiEffect
                      #if ! JucePlugin_IsSynth
                       .withInput  ("Input",  AudioChannelSet::stereo(), true)
                      #endif
                       .withOutput ("Output", AudioChannelSet::stereo(), true)
                     #endif
                       ),
#endif
inL(nullptr, 1, 0, false), inR(nullptr, 1, 0, false), outL(nullptr, 1, 0, false), outR(nullptr, 1, 0, false)
{
  outL.set_input_port(0, &inL, 0);
  bufferFilterL.set_input_port(0, &inL, 0);
  outR.set_input_port(0, &inR, 0);
  bufferFilterR.set_input_port(0, &inR, 0);
  pipeline.add_filter(&outL);
  pipeline.add_filter(&outR);
  pipeline.add_filter(&bufferFilterL);
  pipeline.add_filter(&bufferFilterR);
}

ATKJUCEAudioProcessor::~ATKJUCEAudioProcessor()
{
}

//==============================================================================
const String ATKJUCEAudioProcessor::getName() const
{
    return JucePlugin_Name;
}

bool ATKJUCEAudioProcessor::acceptsMidi() const
{
   #if JucePlugin_WantsMidiInput
    return true;
   #else
    return false;
   #endif
}

bool ATKJUCEAudioProcessor::producesMidi() const
{
   #if JucePlugin_ProducesMidiOutput
    return true;
   #else
    return false;
   #endif
}

double ATKJUCEAudioProcessor::getTailLengthSeconds() const
{
    return 0.0;
}

int ATKJUCEAudioProcessor::getNumPrograms()
{
    return 1;   // NB: some hosts don't cope very well if you tell them there are 0 programs,
                // so this should be at least 1, even if you're not really implementing programs.
}

int ATKJUCEAudioProcessor::getCurrentProgram()
{
    return 0;
}

void ATKJUCEAudioProcessor::setCurrentProgram (int index)
{
}

const String ATKJUCEAudioProcessor::getProgramName (int index)
{
    return {};
}

void ATKJUCEAudioProcessor::changeProgramName (int index, const String& newName)
{
}

//==============================================================================
void ATKJUCEAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
  sampling_rate = std::lround(sampleRate);

	inL.set_input_sampling_rate(sampling_rate);
	inL.set_output_sampling_rate(sampling_rate);
	inR.set_input_sampling_rate(sampling_rate);
	inR.set_output_sampling_rate(sampling_rate);

	outL.set_input_sampling_rate(sampling_rate);
	outL.set_output_sampling_rate(sampling_rate);
  outR.set_input_sampling_rate(sampling_rate);
	outR.set_output_sampling_rate(sampling_rate);
  bufferFilterL.set_input_sampling_rate(sampling_rate);
  bufferFilterL.set_output_sampling_rate(sampling_rate);
  bufferFilterR.set_input_sampling_rate(sampling_rate);
  bufferFilterR.set_output_sampling_rate(sampling_rate);

  pipeline.set_input_sampling_rate(sampling_rate);
}

void ATKJUCEAudioProcessor::releaseResources()
{
    // When playback stops, you can use this as an opportunity to free up any
    // spare memory, etc.
}

#ifndef JucePlugin_PreferredChannelConfigurations
bool ATKJUCEAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
  #if JucePlugin_IsMidiEffect
    ignoreUnused (layouts);
    return true;
  #else
    // This is the place where you check if the layout is supported.
    if (layouts.getMainOutputChannelSet() != AudioChannelSet::stereo())
        return false;

    // This checks if the input layout matches the output layout
   #if ! JucePlugin_IsSynth
    if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
        return false;
   #endif

    return true;
  #endif
}
#endif

void ATKJUCEAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
  auto nb_samples = buffer.getNumSamples();

  inL.set_pointer(buffer.getReadPointer(0), nb_samples);
  inR.set_pointer(buffer.getReadPointer(1), nb_samples);
  outL.set_pointer(buffer.getWritePointer(0), nb_samples);
  outR.set_pointer(buffer.getWritePointer(1), nb_samples);

  pipeline.process(nb_samples);
}

//==============================================================================
bool ATKJUCEAudioProcessor::hasEditor() const
{
    return true; // (change this to false if you choose to not supply an editor)
}

AudioProcessorEditor* ATKJUCEAudioProcessor::createEditor()
{
    return new ATKJUCEAudioProcessorEditor (*this);
}

//==============================================================================
void ATKJUCEAudioProcessor::getStateInformation (MemoryBlock& destData)
{
    // You should use this method to store your parameters in the memory block.
    // You could do that either as raw data, or use the XML or ValueTree classes
    // as intermediaries to make it easy to save and load complex data.
}

void ATKJUCEAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    // You should use this method to restore your parameters from this memory block,
    // whose contents will have been created by the getStateInformation() call.
}

int ATKJUCEAudioProcessor::get_sampling_rate() const
{
  return sampling_rate;
}

std::size_t ATKJUCEAudioProcessor::get_nb_channels() const
{
  return 2;
}

const std::vector<double>& ATKJUCEAudioProcessor::get_last_slice(std::size_t index, bool& process)
{
  if(index == 0)
  {
    process = process_slice(bufferFilterL, windowedDataL);
    return windowedDataL;
  }
  else
  {
    process = process_slice(bufferFilterR, windowedDataR);
    return windowedDataR;
  }
}

bool ATKJUCEAudioProcessor::process_slice(ATK::OutCircularPointerFilter<float>& filter, std::vector<double>& windowedData)
{
  bool process = false;

  const auto& data = filter.get_last_slice(process);
  if(process)
  {
    if(window.size() != data.size())
    {
      build_window(data.size());
    }
    for(std::size_t i = 0; i < data.size(); ++i)
    {
      windowedData[i] = window[i] * data[i];
    }
  }
  return process;
}

void ATKJUCEAudioProcessor::build_window(std::size_t size)
{
  window.resize(size, 0);
  windowedDataL.resize(size);
  windowedDataR.resize(size);
  for(size_t i = 0; i < size; ++i)
  {
    window[i] = .5 * (1 - std::cos(2 * boost::math::constants::pi<float>() * i / (size / 2 - 1)));
  }
}

//==============================================================================
// This creates new instances of the plugin..
AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
  return new ATKJUCEAudioProcessor();
}


#pragma once

#include "modules/GenericModule.hpp"
#include "MicroPackets.hpp"

#include "motion-control/DribblerController.hpp"
#include "motion-control/RobotController.hpp"
#include "motion-control/RobotEstimator.hpp"

#include <Eigen/Dense>

class MotionControlModule : public GenericModule {
public:
    // How many times per second this module should run
    static constexpr float freq = 200.0f; // Hz
    static constexpr uint32_t period = static_cast<uint32_t>(1000000L / freq);

    // How long a single call to this module takes
    static constexpr uint32_t runtime = 133; // us

    MotionControlModule(BatteryVoltage *const batteryVoltage,
                        IMUData *const imuData,
                        MotionCommand *const motionCommand,
                        MotorFeedback *const motorFeedback,
                        MotorCommand *const motorCommand);

    virtual void entry(void);

private:
    /**
     * Checks the update time versus the current time and timout given below
     */
    bool isRecentUpdate(uint32_t lastUpdateTime);

    BatteryVoltage *const batteryVoltage;
    IMUData *const imuData;
    MotionCommand *const motionCommand;
    MotorFeedback *const motorFeedback;
    MotorCommand *const motorCommand;

    DribblerController dribblerController;
    RobotController robotController;
    RobotEstimator robotEstimator;

    Eigen::Matrix<double, 4, 1> prevCommand;

    /**
     * Max amount of time that can elapse from the latest
     * command from the radio
     *
     * This is a safety feature to prevent the motors from moving
     * if the motion control dies
     */
    static constexpr uint32_t COMMAND_TIMEOUT = 250; // ms
};


#ifndef PYTHONIC_NUMPY_LDEXP_HPP
#define PYTHONIC_NUMPY_LDEXP_HPP

#include "pythonic/include/numpy/ldexp.hpp"

#include "pythonic/utils/functor.hpp"
#include "pythonic/types/ndarray.hpp"
#include "pythonic/types/numpy_broadcast.hpp"
#include "pythonic/utils/numpy_traits.hpp"

#include <boost/simd/function/ldexp.hpp>

PYTHONIC_NS_BEGIN

namespace numpy
{
  namespace wrapper
  {
    template <class T, class U>
    double ldexp(T const &v, U const &u)
    {
      return boost::simd::ldexp(v, u);
    }
  }

#define NUMPY_NARY_FUNC_NAME ldexp
#define NUMPY_NARY_FUNC_SYM wrapper::ldexp
#include "pythonic/types/numpy_nary_expr.hpp"
}
PYTHONIC_NS_END

#endif


/* Copyright (C) 2012-2020 IBM Corp.
 * This program is 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. See accompanying LICENSE file.
 */
/**
 * @file binaryCompare.cpp
 * @brief Implementing integer comparison in binary representation.
 */
#include <algorithm>

#include <NTL/BasicThreadPool.h>
#include <helib/binaryArith.h>

#define BPL_ESTIMATE (30)
// FIXME: this should really be dynamic

#ifdef HELIB_DEBUG
#include <helib/debugging.h>
#endif

namespace helib {

// returns new v[i] = \sum_{j>=i} old v[i]
void runningSums(CtPtrs& v)
{
  HELIB_TIMER_START;
  for (long i = lsize(v) - 1; i > 0; i--)
    *v[i - 1] += *v[i];
}

// a recursive function that computes
//      e*[i] = prod_{j>=i} e[i]  and  g*[i] = e*[i+1] \cdot g[i]
// This function is optimized, so that instead of all the e*[i]'s
// it only computes e*[0] and the e*[i]'s that are used in the
// computation of the g*[i]'d
static void compProducts(const CtPtrs_slice& e, const CtPtrs_slice& g)
{
  long n = lsize(e);
  if (n <= 1)
    return; // nothing to do
#ifdef HELIB_DEBUG
  std::cout << "compProducts(g[" << g.start << ".." << (g.start + g.sz - 1)
            << "],e[" << e.start << ".." << (e.start + e.sz - 1) << "])"
            << std::endl;
#endif

  // split the array in two, second part has size the largest 2^l < n,
  // and first part is the rest

  long ell = NTL::NumBits(n - 1) - 1; // n/2 <= 2^l < n
  long n1 = n - (1UL << ell);         // n1 \in [1, n/2]

  // Call the recursive procedure separately on the first and second parts
  compProducts(CtPtrs_slice(e, 0, n1), CtPtrs_slice(g, 0, n1)); // first half
  compProducts(CtPtrs_slice(e, n1, n - n1),
               CtPtrs_slice(g, n1, n - n1)); // second half

  // Multiply the first product in the 2nd part into every product in the 1st
  NTL_EXEC_RANGE(1 + n1, first, last)
  for (long i = first; i < last; i++) {
    if (i == 0)
      e[0]->multiplyBy(*e[n1]);
    else if (i - 1 < g.size())
      g[i - 1]->multiplyBy(*e[n1]);
  }
  NTL_EXEC_RANGE_END
#ifdef HELIB_DEBUG
  std::cout << " g[" << g.start << ".." << (g.start + g.sz - 1) << "], "
            << " e[" << e.start << ".." << (e.start + e.sz - 1)
            << "]:" << std::endl;
  for (long i = 0; i < g.size(); i++)
    decryptAndPrint((std::cout << "   g[" << (i + g.start) << "] ("
                               << ((void*)g[i]) << "): "),
                    *g[i],
                    *dbgKey,
                    *dbgEa,
                    FLAG_PRINT_POLY);
  for (long i = 0; i < e.size(); i++)
    decryptAndPrint((std::cout << "   e[" << (i + e.start) << "] ("
                               << ((void*)e[i]) << "): "),
                    *e[i],
                    *dbgKey,
                    *dbgEa,
                    FLAG_PRINT_POLY);

  std::cout << std::endl;
#endif
}

// Compute aeqb[i] = (a==b upto bit i), agtb[i] = (aeqb[i+1] and ai>bi)
// We assume that b.size()>a.size()
static void compEqGt(CtPtrs& aeqb,
                     CtPtrs& agtb,
                     const CtPtrs& a,
                     const CtPtrs& b)
{
  HELIB_TIMER_START;
  const Ctxt zeroCtxt(ZeroCtxtLike, *(b.ptr2nonNull()));
  const Context& context = zeroCtxt.getContext();
  DoubleCRT one(context, context.allPrimes());
  one += 1L;

  resize(aeqb, lsize(b), zeroCtxt);
  resize(agtb, lsize(a), zeroCtxt);

  // First compute the local bits e[i]=(a[i]==b[i]), gt[i]=(a[i]>b[i])
  HELIB_NTIMER_START(compEqGt1);
  long aSize = lsize(a);
  NTL_EXEC_RANGE(aSize, first, last)
  for (long i = first; i < last; i++) {
    *aeqb[i] = *b[i];               // b
    aeqb[i]->addConstant(one, 1.0); // b+1
    *agtb[i] = *aeqb[i];            // b+1
    *aeqb[i] += *a[i];              // a+b+1
    agtb[i]->multiplyBy(*a[i]);     // a(b+1)
  }
  NTL_EXEC_RANGE_END
  HELIB_NTIMER_STOP(compEqGt1);

  // NOTE: Usually there isn't much gain in multi-threading the loop below,
  //    but computing b[i] can be expensive in some implementations of CtPtrs
  HELIB_NTIMER_START(compEqGt2);
  if (lsize(b) - aSize > 1) {
    NTL_EXEC_RANGE(lsize(b) - aSize, first, last)
    for (long i = first; i < last; i++) {
      *aeqb[i + aSize] = *b[i + aSize];       // b
      aeqb[i + aSize]->addConstant(one, 1.0); // b+1
    }
    NTL_EXEC_RANGE_END
  } else if (lsize(b) - aSize == 1) {
    *aeqb[aSize] = *b[aSize];           // b
    aeqb[aSize]->addConstant(one, 1.0); // b+1
  }
  HELIB_NTIMER_STOP(compEqGt2);

#ifdef HELIB_DEBUG
  for (long i = 0; i < lsize(b); i++)
    decryptAndPrint((std::cout << " e[" << i << "]: "),
                    *aeqb[i],
                    *dbgKey,
                    *dbgEa,
                    FLAG_PRINT_POLY);
  for (long i = 0; i < lsize(a); i++)
    decryptAndPrint((std::cout << " ag[" << i << "]: "),
                    *agtb[i],
                    *dbgKey,
                    *dbgEa,
                    FLAG_PRINT_POLY);
  std::cout << std::endl;
#endif

  // Call a recursive function to compute:
  // e*_i = \prod_{j>=i} aeqb_i, g*_i = aeqb*_{i+1} \cdot agtb_i
  HELIB_NTIMER_START(compEqGt3);
  compProducts(CtPtrs_slice(aeqb, 0), CtPtrs_slice(agtb, 0));
  runningSums(agtb); // now ag[i] = (a>b upto bit i)
  HELIB_NTIMER_STOP(compEqGt3);
}

// Compares two integers in binary a,b.
// Returns max(a,b), min(a,b) and indicator bits mu=(a>b) and ni=(a<b)
void compareTwoNumbersImplementation(CtPtrs& max,
                                     CtPtrs& min,
                                     Ctxt& mu,
                                     Ctxt& ni,
                                     const CtPtrs& aa,
                                     const CtPtrs& bb,
                                     bool twosComplement,
                                     std::vector<zzX>* unpackSlotEncoding,
                                     bool cmp_only)
{
  HELIB_TIMER_START;
  // make sure that lsize(b) >= lsize(a)
  const CtPtrs& a = (lsize(bb) >= lsize(aa)) ? aa : bb;
  const CtPtrs& b = (lsize(bb) >= lsize(aa)) ? bb : aa;
  long aSize = lsize(a);
  long bSize = lsize(b);
  if (aSize < 1) { // a is empty
    mu.clear();
    ni.clear();
    ni.addConstant(NTL::ZZ(1L));
    vecCopy(max, b);
    setLengthZero(min);
    return;
  }

  // Check that we have enough levels, try to bootstrap otherwise
  if (findMinBitCapacity({&a, &b}) <
      (NTL::NumBits(bSize + 1) + 2) * mu.getContext().BPL())
    packedRecrypt(a, b, unpackSlotEncoding);
  if (findMinBitCapacity({&a, &b}) <
      (NTL::NumBits(bSize) + 1) * mu.getContext().BPL())
    // the bare minimum
    throw LogicError("not enough levels for comparison");

  // NOTE: this procedure minimizes the number of multiplications,
  //       but it may use one level too many. Can we optimize it?

  /* We first compute for each position i the values
   *   e[i] = (a==b upto position i)
   *   ag[i] = (a>b upto position i)
   */

  // We use max, min to hold the intermediate values e, ag
  CtPtrs& e = max;
  CtPtrs& ag = min;
  compEqGt(e, ag, a, b);

  // We are now ready to compute the bits of the result.

  HELIB_NTIMER_START(compResults);
  mu = *ag[0]; // a > b
  ni = *ag[0];
  ni.addConstant(NTL::ZZ(1L)); // a <= b
  ni += *e[0];                 // a < b

  if (twosComplement) {
    // mu, ni and ag need to be inverted iff the sign bits of a and b differ.
    // Perform this by adding both sign bits.
    const auto flipIfDifferentSign = [&](Ctxt& ctxt) {
      (ctxt += *aa[aa.size() - 1]) += *bb[bb.size() - 1];
    };
    flipIfDifferentSign(mu);
    flipIfDifferentSign(ni);
    for (long i = 0; i < ag.size(); ++i)
      flipIfDifferentSign(*ag[i]);
  }
  if (cmp_only) {
    return;
  }

  NTL_EXEC_RANGE(aSize, first, last)
  for (long i = first; i < last; i++) {
    *max[i] = *a[i];
    *max[i] -= *b[i];
    max[i]->multiplyBy(*ag[i]);

    *min[i] = *max[i];
    *max[i] += *b[i];
    *min[i] -= *a[i];
  }
  NTL_EXEC_RANGE_END
  for (long i = aSize; i < bSize; i++)
    *max[i] = *b[i];
  HELIB_NTIMER_STOP(compResults);
}

void compareTwoNumbers(CtPtrs& max,
                       CtPtrs& min,
                       Ctxt& mu,
                       Ctxt& ni,
                       const CtPtrs& aa,
                       const CtPtrs& bb,
                       bool twosComplement,
                       std::vector<zzX>* unpackSlotEncoding)
{
  compareTwoNumbersImplementation(max,
                                  min,
                                  mu,
                                  ni,
                                  aa,
                                  bb,
                                  twosComplement,
                                  unpackSlotEncoding,
                                  false);
}

void compareTwoNumbers(Ctxt& mu,
                       Ctxt& ni,
                       const CtPtrs& aa,
                       const CtPtrs& bb,
                       bool twosComplement,
                       std::vector<zzX>* unpackSlotEncoding)
{
  NTL::Vec<Ctxt> aeqb;
  NTL::Vec<Ctxt> agtb;
  CtPtrs_VecCt eq(aeqb);
  CtPtrs_VecCt gr(agtb);
  compareTwoNumbersImplementation(eq,
                                  gr,
                                  mu,
                                  ni,
                                  aa,
                                  bb,
                                  twosComplement,
                                  unpackSlotEncoding,
                                  true);
}

} // namespace helib