/*
 * This file is subject to the terms and conditions defined in
 * file 'LICENSE', which is part of this source code package.
 *
 * Partialy financiated as part of the protocol between UTAustin I Portugal - UTA-P.
 * [2017] - [2018] University of Minho, Filipe Costa Oliveira
 * All Rights Reserved.
 */

/* BEGIN BOOST */
#include <boost/process.hpp>
#include <boost/filesystem/operations.hpp>                // for directory_iterator
#include <boost/filesystem/path.hpp>                      // for path, operator==, oper...
#include <boost/iterator/iterator_facade.hpp>             // for iterator_facade_base
#include <boost/thread.hpp>                               // for thread
#include <boost/tokenizer.hpp>
#include <boost/program_options.hpp>                      // for error
/* END BOOST */

#include <cassert>                                        // for assert
#include <cmath>                                          // for fabs, round
#include <cstdlib>                                        // for div_t, div
#include <exception>                                      // for exception
#include <iostream>                                       // for string, str...
#include <iomanip>                                        // std::setw

#include <opencv2/core/hal/interface.h>                   // for CV_8UC1
#include <opencv2/imgcodecs/imgcodecs_c.h>                // for ::CV_LOAD_I...
#include <opencv2/core.hpp>                               // for RNG
#include <opencv2/core/cvstd.inl.hpp>                     // for String::String
#include <opencv2/core/mat.hpp>                           // for Mat
#include <opencv2/core/mat.inl.hpp>                       // for Mat::Mat
#include <opencv2/core/operations.hpp>                    // for RNG::RNG
#include <opencv2/core/types.hpp>                         // for Rect, Point3d
#include <opencv2/imgcodecs.hpp>                          // for imread

// project classes
#include "global_def.hpp"
#include "celslc_prm.hpp"                                 // for CELSLC_prm
#include "mc_driver.hpp"                                  // for MC_Driver
#include "msa_prm.hpp"                                    // for MSA_prm
#include "simgrid_steplength.hpp"                         // for SIMGRID_wav...
#include "structure.hpp"                                  // for Structure
#include "image_crystal.hpp"                              // for Image and Crystal
#include "td_map.hpp"                                     // for TDMap
#include "super_cell.hpp"                                 // for Super_Cell
#include "unit_cell.hpp"                                  // for UnitCell
#include "wavimg_prm.hpp"                                 // for WAVIMG_prm

using namespace cv;

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

    // Experimental Image info
  cv::Mat experimental_image;
  cv::Mat experimental_image_roi;
  cv::Mat experimental_working;

  Rect roi_rectangle;
  int roi_x_size;
  int roi_y_size;
  int roi_center_x;
  int roi_center_y;

  double sampling_rate_experimental_x_nm_per_pixel;
  double sampling_rate_experimental_y_nm_per_pixel;

  int edge_detection_threshold = 85;
  int max_thresh = 255;
  RNG rng(12345);
  int max_contour_distance_px = 19;
  int max_contour_distance_thresh_px = 255;

  UnitCell unit_cell;
  Super_Cell super_cell;
  Image_Crystal image_crystal;
  //TDMap td_map;

  cv::Point3d  zone_axis_vector_uvw;
  cv::Point3d  upward_vector_hkl;

  /////////////////////////
  // Simulation info
  /////////////////////////

  //std::cout << System::Environment::GetEnvironmentVariable("CELSLC_BIN_PATH");
  //std::cout << std::getenv("CELSLC_BIN_PATH");
  // Specifies the input super-cell file containing the atomic structure data in CIF file format.
  std::string super_cell_cif_file;
  // Specifies the output slice file name prefix.
  std::string slc_file_name_prefix;
  double projection_dir_h;
  double projection_dir_k;
  double projection_dir_l;
  std::string prj_hkl;
  double perpendicular_dir_u;
  double perpendicular_dir_v;
  double perpendicular_dir_w;
  std::string prj_uvw;
  double super_cell_size_a;
  double super_cell_size_b;
  double super_cell_size_c;
  std::string super_abc;
  bool hkl_switch = false;
  bool uvw_switch = false;
  bool abc_switch = false;

  int nx_simulated_horizontal_samples;
  int ny_simulated_vertical_samples;
  int nz_simulated_partitions;
  bool nx_ny_switch = false;
  bool nz_switch = false;
  double ht_accelaration_voltage;

  // based on super_cell_size_a and nx_simulated_horizontal_samples
  double sampling_rate_super_cell_x_nm_pixel;
  // based on super_cell_size_b and ny_simulated_vertical_samples
  double sampling_rate_super_cell_y_nm_pixel;
  // based on super_cell_size_c and nz_simulated_partitions;
  double super_cell_z_nm_slice;

  // wavimg defocus aberration coefficient
  double coefficient_aberration_defocus;
  // wavimg spherical aberration coefficient
  double coefficient_aberration_spherical;

  int number_image_aberrations = 0;
  bool cd_switch = false;
  bool cs_switch = false;

  // Switch for applying Debye-Waller factors which effectively dampen the atomic scattering potentials.
  // Use this option for conventional HR-TEM, STEM bright-field, or STEM annular bright-field image simulations only.
  bool dwf_switch=false;

  // Switch for applying absorption potentials (imaginary part) according to Weickenmeier and Kohl [Acta Cryst. A47 (1991) p. 590-597].
  // This absorption calculation considers the loss of intensity in the elastic channel due to thermal diffuse scattering.
  bool abs_switch=false;

  bool celslc_switch = true;
  bool celslc_cel_switch = true;
  bool msa_switch = true;
  bool msa_cel_switch = true;
  bool wavimg_switch = true;
  bool wavimg_cel_switch = true;
  bool im2model_switch = true;
  bool debug_switch = false;
  bool roi_gui_switch = false;
  bool vis_gui_switch = false;
  bool sim_cmp_gui_switch = false;
  bool sim_grid_switch = false;
  bool phase_comparation_switch = false;
  bool user_estimated_defocus_nm_switch = false;
  bool user_estimated_thickness_nm_switch = false;
  bool user_estimated_thickness_slice_switch = false;
  bool cleanup_switch = false;

  /////////////////////////
  // Simulated Thickness info
  /////////////////////////
  int slices_load;
  int slice_samples;
  int slices_lower_bound;
  int slices_upper_bound;
  double nm_lower_bound;
  double nm_upper_bound;
  int number_slices_to_max_thickness;
  int slice_period;
  double user_estimated_thickness_nm;
  int user_estimated_thickness_slice;
  std::vector<double> celslc_accum_nm_slice_vec;

  /////////////////////////
  // Simulated Defocus info
  /////////////////////////
  int defocus_samples;
  int defocus_lower_bound;
  int defocus_upper_bound;
  int defocus_period;
  int user_estimated_defocus_nm;

  /////////////////////////
  // Super-Cell Simulated Defocus info
  /////////////////////////
  int super_cell_defocus_samples;
  double super_cell_defocus_interval;
  double super_cell_defocus_period;
  double super_cell_defocus_lower_bound;
  double super_cell_defocus_upper_bound;

  /////////////////////////
  // Experimental Image info
  /////////////////////////
  int roi_pixel_size;
  int ignore_edge_pixels;
  std::string experimental_image_path;

  /////////////////////////
  // Simulated vs Experimental Image info
  /////////////////////////
  bool simulated_image_needs_reshape = false;
  double reshape_factor_from_supper_cell_to_experimental_x = 1.0f;
  double reshape_factor_from_supper_cell_to_experimental_y = 1.0f;
  double max_scale_diff = 0.0005f;
  double diff_super_cell_and_simulated_x;
  double diff_super_cell_and_simulated_y;
  // rectangle without the ignored edge pixels of the simulated image
  Rect ignore_edge_pixels_rectangle;

  int initial_simulated_image_width;
  int initial_simulated_image_height;
  int reshaped_simulated_image_width;
  int reshaped_simulated_image_height;

  /////////////////////////
  // Dr Probe CEL file parameters
  /////////////////////////
  double cel_margin_nm;

  MC::MC_Driver cif_driver;

  /////////////////////////
  // Super Cell parameters
  /////////////////////////
  int super_cell_nz_simulated_partitions;
  int super_cell_number_slices_to_max_thickness;
  int super_cell_slices_load;
  int super_cell_slices_upper_bound;
  int super_cell_slices_lower_bound;
  int super_cell_slice_period;
  std::vector<double> super_cell_celslc_accum_nm_slice_vec;

  try{
    /** Define and parse the program options
    */
    boost::program_options::options_description desc("Options");
    desc.add_options()
      ("help,h", "Print help message")
      ("no_celslc", "switch for skipping celslc execution.")
      ("no_celslc_cel", "switch for skipping celslc execution on super-cell.")
      ("no_msa", "switch for skipping msa execution.")
      ("no_msa_cel", "switch for skipping msa execution on super-cell.")
      ("no_wavimg", "switch for skipping wavimg execution.")
      ("no_wavimg_cel", "switch for skipping wavimg execution on super-cell.")
      ("no_im2model", "switch for skipping im2model execution.")
      ("clean","switch for enabling temporary files cleaning after execution.")
      ("debug,g", "switch for enabling debug info for celslc, msa, and wavimg execution.")
      ("cif", boost::program_options::value<std::string>(&super_cell_cif_file)->required(), "specifies the input super-cell file containing the atomic structure data in CIF file format.")
      ("slc", boost::program_options::value<std::string>(&slc_file_name_prefix)->required(), "specifies the output slice file name prefix. Absolute or relative path names can be used. Enclose the file name string using quotation marks if the file name prefix or the disk path contains space characters. The slice file names will be suffixed by '_###.sli', where ### is a 3 digit number denoting the sequence of slices generated from the supercell.")
      ("prj_h",  boost::program_options::value<double>(&projection_dir_h), "projection direction h of [hkl].")
      ("prj_k",  boost::program_options::value<double>(&projection_dir_k), "projection direction k of [hkl].")
      ("prj_l",  boost::program_options::value<double>(&projection_dir_l), "projection direction l of [hkl].")
      ("prj_hkl",  boost::program_options::value<std::string>(&prj_hkl), "projection direction [hkl].")
      ("prp_u",  boost::program_options::value<double>(&perpendicular_dir_u), "perpendicular direction u for the new y-axis of the projection [uvw].")
      ("prp_v",  boost::program_options::value<double>(&perpendicular_dir_v), "perpendicular direction v for the new y-axis of the projection [uvw].")
      ("prp_w",  boost::program_options::value<double>(&perpendicular_dir_w), "perpendicular direction w for the new y-axis of the projection [uvw].")
      ("prj_uvw",  boost::program_options::value<std::string>(&prj_uvw), "projected y axis direction [uvw].")
      ("super_a",  boost::program_options::value<double>(&super_cell_size_a), "the size(in nanometers) of the new orthorhombic super-cell along the axis x.")
      ("super_b",  boost::program_options::value<double>(&super_cell_size_b), "the size(in nanometers) of the new orthorhombic super-cell along the axis y.")
      ("super_c",  boost::program_options::value<double>(&super_cell_size_c), "the size(in nanometers) of the new orthorhombic super-cell along the axis z, where z is the projection direction of the similation.")
      ("super_abc",  boost::program_options::value<std::string>(&super_abc), "the size(in nanometers) of the new orthorhombic super-cell along the axis xyz.")
      ("nx", boost::program_options::value<int>(&nx_simulated_horizontal_samples), "number of horizontal samples for the phase grating. The same number of pixels is used to sample the wave function in multislice calculations based on the calculated phase gratings.")
      ("ny", boost::program_options::value<int>(&ny_simulated_vertical_samples), "number of vertical samples for the phase grating. The same number of pixels is used to sample the wave function in multislice calculations based on the calculated phase gratings.")
      ("nz", boost::program_options::value<int>(&nz_simulated_partitions), "simulated partitions")
      ("ht", boost::program_options::value<double>(&ht_accelaration_voltage)->required(), "accelerating voltage defining the kinetic energy of the incident electron beam in kV.")
      ("cd", boost::program_options::value<double>(&coefficient_aberration_defocus), "Defocus Aberration definition by two coefficient values given in [nm]. This is the first coefficient value. The second coefficient value is considered to be 0.")
      ("cs", boost::program_options::value<double>(&coefficient_aberration_spherical), "Spherical Aberration definition by two coefficient values given in [nm]. This is the first coefficient value. The second coefficient value is considered to be 0.")
      ("dwf", "switch for applying Debye-Waller factors which effectively dampen the atomic scattering potentials. Use this option for conventional HR-TEM, STEM bright-field, or STEM annular bright-field image simulations only.")
      ("abs", "switch for applying absorption potentials (imaginary part) according to Weickenmeier and Kohl [Acta Cryst. A47 (1991) p. 590-597]. This absorption calculation considers the loss of intensity in the elastic channel due to thermal diffuse scattering.")
      ("slices_load", boost::program_options::value<int>(&slices_load), "number of slice files to be loaded.")
      ("slices_samples", boost::program_options::value<int>(&slice_samples)->required(), "slices samples")
      ("slices_lower_bound", boost::program_options::value<int>(&slices_lower_bound), "slices lower bound")
      ("nm_lower_bound", boost::program_options::value<double>(&nm_lower_bound), "nm lower bound. the nearest slice will be calculated and used as parameter.")
      ("nm_upper_bound", boost::program_options::value<double>(&nm_upper_bound), "nm upper bound. the nearest slice will be calculated and used as parameter.")
      ("slices_upper_bound", boost::program_options::value<int>(&slices_upper_bound), "slices Upper Bound")
      ("slices_max", boost::program_options::value<int>(&number_slices_to_max_thickness), "number of slices used to describe the full object structure up to its maximum thickness.")
      ("defocus_samples", boost::program_options::value<int>(&defocus_samples)->required(), "defocus samples")
      ("defocus_lower_bound", boost::program_options::value<int>(&defocus_lower_bound)->required(), "defocus lower bound")
      ("defocus_upper_bound", boost::program_options::value<int>(&defocus_upper_bound)->required(), "defocus upper bound")
      ("exp_image_path,i", boost::program_options::value<std::string>(&experimental_image_path)->required(), "experimental image path")
      ("estimated_defocus_nm", boost::program_options::value<int>(&user_estimated_defocus_nm), "user prediction of estimated defocus value in nanometers for the experimental image. If an user estimation is given the search algorithm will consider the nearest best match as an interest point even if there are larger match values on other defocus values. The other larger defocus value points will also be considered points of interest for the following iterations (as usual).")
      ("estimated_thickness_nm", boost::program_options::value<double>(&user_estimated_thickness_nm), "user prediction of estimated thickness value in nanometers for the experimental image. An aproximation to the nearest slice number is computed. If an user estimation is given the search algorithm will consider the nearest best match as an interest point even if there are larger match values on other thickness values. The other larger thickness points will also be considered points of interest for the following iterations (as usual).")
      ("estimated_thickness_slice", boost::program_options::value<int>(&user_estimated_thickness_slice), "user prediction of estimated thickness value in slice number for the experimental image. If an user estimation is given the search algorithm will consider the nearest best match as an interest point even if there are larger match values on other thickness values. The other larger thickness points will also be considered points of interest for the following iterations (as usual).")
      ("exp_nx", boost::program_options::value<double>(&sampling_rate_experimental_x_nm_per_pixel)->required(), "number of horizontal samples for the loaded experimental image (nm/pixel).")
      ("exp_ny", boost::program_options::value<double>(&sampling_rate_experimental_y_nm_per_pixel)->required(), "number of vertical samples for the loaded experimental image (nm/pixel).")
      ("roi_size", boost::program_options::value<int>(&roi_pixel_size)->required(), "region of interest size in pixels.")
      ("roi_x", boost::program_options::value<int>(&roi_center_x)->required(), "region center in x axis.")
      ("roi_y", boost::program_options::value<int>(&roi_center_y)->required(), "region center in y axis.")
      ("roi_gui", "switch for enabling gui region of interest selection prior to im2model execution.")
      ("vis_gui", "switch for enabling supercell visualization.")
      ("sim_cmp_gui", "switch for enabling gui im2model simullated and experimental comparation visualization.")
      ("sim_grid", "switch for enable simmulated image grid generation.")
      ("ignore_edge_pixels", boost::program_options::value<int>(&ignore_edge_pixels)->default_value(0), "number of pixels to ignore from the outter limit of the simulated image.")
      ("cel_margin_nm", boost::program_options::value<double>(&cel_margin_nm)->default_value(0.0f), "supercell margin in nanometers for the cel file creation.")
      ("cel_defocus_samples", boost::program_options::value<int>(&super_cell_defocus_samples)->default_value(5), "number of defocus samples for the super-cell.")
      ("cel_defocus_interval", boost::program_options::value<double>(&super_cell_defocus_interval)->default_value(10), "defocus search interval for the super-cell. The center of the interval will be the calculated best-match on the T-Focus map.")
      ;

    boost::program_options::variables_map vm;
    try{
      boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc),vm); // can throw
    }
    catch(boost::program_options::error& e){
      std::cerr << "ERROR: " << e.what() << std::endl << std::endl;
      std::cerr << desc << std::endl;
      return -1;
    }
    boost::program_options::notify(vm);

    if ( vm.count("help")  )
    {
      std::cout << "\n\n********************************************************************************\n\n" <<
        "Im2Model -- Atomic models for TEM image simulation and matching\n"
        <<
        "Command Line Parameters:" << std::endl
        << desc << std::endl;
      return 0;
    }

    if ( vm.count("cd") ){
      cd_switch = true;
      number_image_aberrations++;
    }
    if ( vm.count("prj_h") && vm.count("prj_k") && vm.count("prj_l") ){
      hkl_switch = true;
    }
    if ( vm.count("prj_hkl") ){
      typedef boost::tokenizer<boost::char_separator<char>> tokenizer;
      boost::char_separator<char> sep{","};
      tokenizer tok{prj_hkl, sep};
      tokenizer::iterator itt_h,itt_k,itt_l;
      itt_h = tok.begin();
      itt_k = tok.begin();
      itt_l = tok.begin();
      std::advance(itt_k,1);
      std::advance(itt_l,2);
      projection_dir_h = boost::lexical_cast<double> (*itt_h);
      projection_dir_k = boost::lexical_cast<double> (*itt_k);
      projection_dir_l = boost::lexical_cast<double> (*itt_l);
      hkl_switch = true;
    }
    if ( vm.count("prj_u") && vm.count("prj_v") && vm.count("prj_w") ){
      uvw_switch = true;
    }

    if ( vm.count("prj_uvw") ){
      typedef boost::tokenizer<boost::char_separator<char>> tokenizer;
      boost::char_separator<char> sep{","};
      tokenizer tok{prj_uvw, sep};
      tokenizer::iterator itt_u,itt_v,itt_w;
      itt_u = tok.begin();
      itt_v = tok.begin();
      itt_w = tok.begin();
      std::advance(itt_v,1);
      std::advance(itt_w,2);
      perpendicular_dir_u = boost::lexical_cast<double> (*itt_u);
      perpendicular_dir_v = boost::lexical_cast<double> (*itt_v);
      perpendicular_dir_w = boost::lexical_cast<double> (*itt_w);
      uvw_switch = true;
    }
    if ( vm.count("super_a") && vm.count("super_b") && vm.count("super_c") ){
      abc_switch = true;
    }

    if ( vm.count("super_abc") ){
      typedef boost::tokenizer<boost::char_separator<char>> tokenizer;
      boost::char_separator<char> sep{","};
      tokenizer tok{super_abc, sep};
      tokenizer::iterator itt_a,itt_b,itt_c;
      itt_a = tok.begin();
      itt_b = tok.begin();
      itt_c = tok.begin();
      std::advance(itt_b,1);
      std::advance(itt_c,2);
      super_cell_size_a = boost::lexical_cast<double> (*itt_a);
      super_cell_size_b = boost::lexical_cast<double> (*itt_b);
      super_cell_size_c = boost::lexical_cast<double> (*itt_c);
      abc_switch = true;
    }

    if ( vm.count("nz")){
      nz_switch = true;
    }
    if ( vm.count("ny") ){
      assert( vm.count("nx") );
      nx_ny_switch = true;
    }
    if ( vm.count("nx") ){
      assert( vm.count("ny") );
      nx_ny_switch = true;
    }

    if ( vm.count("cs") ){
      number_image_aberrations++;
      cs_switch = true;
    }
    if ( vm.count("clean") ){
      cleanup_switch = true;
    }

    if ( vm.count("dwf")  ){
      dwf_switch=true;
    }
    if ( vm.count("abs")  ){
      abs_switch=true;
    }
    if ( vm.count("no_celslc")  ){
      celslc_switch=false;
    }
    if ( vm.count("no_msa")  ){
      msa_switch=false;
    }
    if ( vm.count("no_wavimg")  ){
      wavimg_switch=false;
    }
    if ( vm.count("no_im2model")  ){
      im2model_switch=false;
    }
    if ( vm.count("no_celslc_cel")  ){
      celslc_cel_switch=false;
    }
    if ( vm.count("no_msa_cel")  ){
      msa_cel_switch=false;
    }
    if ( vm.count("no_wavimg_cel")  ){
      wavimg_cel_switch=false;
    }

    if ( vm.count("debug")  ){
      debug_switch=true;
    }
    if ( vm.count("roi_gui")  ){
      roi_gui_switch=true;
    }
    if ( vm.count("vis_gui")  ){
      vis_gui_switch=true;
    }
    if ( vm.count("sim_cmp_gui")  ){
      sim_cmp_gui_switch=true;
    }
    if ( vm.count("sim_grid")  ){
      sim_grid_switch=true;
    }
    if ( vm.count("estimated_defocus_nm")  ){
      user_estimated_defocus_nm_switch=true;
    }
    if ( vm.count("estimated_thickness_nm")  ){
      user_estimated_thickness_nm_switch=true;
    }
    if ( vm.count("estimated_thickness_slice")  ){
      user_estimated_thickness_slice_switch=true;
    }
    // there are any problems
    /* CIF file parser */
    //simulation_data->unit_cell.set_cif_file_name( super_cell_cif_file.c_str() );
    cif_driver.parse( super_cell_cif_file.c_str() );
    cif_driver.populate_unit_cell();
    cif_driver.populate_atom_site_unit_cell();
    cif_driver.populate_symetry_equiv_pos_as_xyz_unit_cell();
    unit_cell = cif_driver.get_unit_cell();

    zone_axis_vector_uvw = cv::Point3d( perpendicular_dir_u, perpendicular_dir_v , perpendicular_dir_w );
    upward_vector_hkl = cv::Point3d( projection_dir_h, projection_dir_k , projection_dir_l );
    unit_cell.set_zone_axis_vector( zone_axis_vector_uvw );
    unit_cell.set_upward_vector( upward_vector_hkl );
    unit_cell.form_matrix_from_miller_indices();

    // stage 3 of dev (in migration process)
    //tfocus_map_simulation_step->set_unit_cell( &unit_cell );

    // Simulated image sampling rate
    if ( nx_ny_switch ){
      sampling_rate_super_cell_x_nm_pixel = super_cell_size_a / nx_simulated_horizontal_samples;
      sampling_rate_super_cell_y_nm_pixel = super_cell_size_b / ny_simulated_vertical_samples;
    }
    else{
      nx_simulated_horizontal_samples = (int) ( super_cell_size_a / sampling_rate_experimental_x_nm_per_pixel );
      ny_simulated_vertical_samples = (int) ( super_cell_size_b / sampling_rate_experimental_y_nm_per_pixel );
      sampling_rate_super_cell_x_nm_pixel = sampling_rate_experimental_x_nm_per_pixel;
      sampling_rate_super_cell_y_nm_pixel = sampling_rate_experimental_y_nm_per_pixel;
      std::cout << "automatic nx and ny. -nx " << nx_simulated_horizontal_samples << " -ny " << ny_simulated_vertical_samples << std::endl;
    }

    if( nz_switch ){
      super_cell_z_nm_slice = super_cell_size_c / nz_simulated_partitions;
    }
    diff_super_cell_and_simulated_x = fabs(sampling_rate_super_cell_x_nm_pixel - sampling_rate_experimental_x_nm_per_pixel);
    diff_super_cell_and_simulated_y = fabs(sampling_rate_super_cell_y_nm_pixel - sampling_rate_experimental_y_nm_per_pixel);

    // Check if the numbers are really close -- needed
    // when comparing numbers near zero.

    std::cout << "sampling rate simulated [ " << sampling_rate_super_cell_x_nm_pixel << " , " << sampling_rate_super_cell_y_nm_pixel << " ]" << std::endl;
    std::cout << "sampling rate experimental [ " << sampling_rate_experimental_x_nm_per_pixel << " , " << sampling_rate_experimental_y_nm_per_pixel << " ]" << std::endl;

    initial_simulated_image_width = nx_simulated_horizontal_samples - ( 2 * ignore_edge_pixels );
    initial_simulated_image_height = nx_simulated_horizontal_samples - ( 2 * ignore_edge_pixels );
    reshaped_simulated_image_width = initial_simulated_image_width;
    reshaped_simulated_image_height = initial_simulated_image_height;

    if (( diff_super_cell_and_simulated_x >= max_scale_diff ) ||  (diff_super_cell_and_simulated_y >= max_scale_diff )){
      simulated_image_needs_reshape = true;
      reshape_factor_from_supper_cell_to_experimental_x = fabs(sampling_rate_super_cell_x_nm_pixel) / fabs(sampling_rate_experimental_x_nm_per_pixel);
      reshape_factor_from_supper_cell_to_experimental_y = fabs(sampling_rate_super_cell_y_nm_pixel) / fabs(sampling_rate_experimental_y_nm_per_pixel);
      reshaped_simulated_image_width = (int) round ( reshape_factor_from_supper_cell_to_experimental_x * initial_simulated_image_width );
      reshaped_simulated_image_height = (int) round ( reshape_factor_from_supper_cell_to_experimental_y * initial_simulated_image_height );

      std::cout << "WARNING : simulated and experimental images have different sampling rates"
        << ". Reshaping simulated images by a factor of [ "
        << reshape_factor_from_supper_cell_to_experimental_x << " , " << reshape_factor_from_supper_cell_to_experimental_y
        << " ]" << "\n\tThe simulated image portion of size [" << initial_simulated_image_width << "," << initial_simulated_image_height
        << "]" << " will be resized to ["<< reshaped_simulated_image_width << "," << reshaped_simulated_image_height << "]"
        << std::endl;
    }

    ignore_edge_pixels_rectangle.x = ignore_edge_pixels;
    ignore_edge_pixels_rectangle.y = ignore_edge_pixels;
    ignore_edge_pixels_rectangle.width = initial_simulated_image_width;
    ignore_edge_pixels_rectangle.height = initial_simulated_image_height;

    // Simulation Defocus Period (in nm)
    defocus_period = ( defocus_upper_bound - defocus_lower_bound) / ( defocus_samples - 1 );
    std::cout << "defocus period " <<  defocus_period << std::endl;
      boost::process::ipstream _sim_tdmap_ostream_buffer;

    CELSLC_prm* celslc_parameters =  new CELSLC_prm( _sim_tdmap_ostream_buffer ); //tfocus_map_simulation_step->get_celslc_parameters();
    celslc_parameters->set_prp_dir_uvw( perpendicular_dir_u, perpendicular_dir_v, perpendicular_dir_w );
    celslc_parameters->set_prj_dir_hkl( projection_dir_h, projection_dir_k, projection_dir_l );
    celslc_parameters->set_super_cell_size_abc( super_cell_size_a, super_cell_size_b, super_cell_size_c );
    celslc_parameters->set_cif_file(super_cell_cif_file.c_str());
    celslc_parameters->set_slc_filename_prefix (slc_file_name_prefix.c_str());
    celslc_parameters->set_nx_simulated_horizontal_samples(nx_simulated_horizontal_samples);
    celslc_parameters->set_ny_simulated_vertical_samples(ny_simulated_vertical_samples);
    if( nz_switch ){
      celslc_parameters->set_nz_simulated_partitions(nz_simulated_partitions);
    }
    celslc_parameters->set_ht_accelaration_voltage(ht_accelaration_voltage);
    celslc_parameters->set_dwf_switch(dwf_switch);
    celslc_parameters->set_abs_switch(abs_switch);
    //celslc_parameters->set_bin_path( celslc_bin_string );
    if (celslc_switch == true ){
      std::cout << "Running ceslc" << std::endl;
      celslc_parameters->call_bin_ssc();
      //celslc_parameters->call_boost_bin();
    }

    nz_simulated_partitions = celslc_parameters->get_nz_simulated_partitions();
    assert( nz_simulated_partitions >= 1 );
    celslc_accum_nm_slice_vec = celslc_parameters->get_slice_params_accum_nm_slice_vec();
    number_slices_to_max_thickness = nz_simulated_partitions;
    slices_load = nz_simulated_partitions;

    if ( vm.count("nm_upper_bound")  ){
      slices_upper_bound = celslc_parameters->get_slice_number_from_nm_floor( nm_upper_bound );
      std::cout << "Calculated slice # " << slices_upper_bound << " as the upper bound for the maximum thickness of: " << nm_upper_bound << " nm" << std::endl;
    }

    if ( vm.count("nm_lower_bound")  ){
      slices_lower_bound = celslc_parameters->get_slice_number_from_nm_ceil( nm_lower_bound );
      std::cout << "Calculated slice # " << slices_lower_bound << " as the lower bound for the minimum thickness of: " << nm_lower_bound << " nm" << std::endl;
    }

    if (slices_lower_bound == 0){
      std::cout << "WARNING: Defined slice lower bound as 0. Going to define slice lower bound as: " << slice_period << std::endl;
      slices_lower_bound = (int) slice_period;
    }

    // Simulation Thickness Period (in slices)
    int slice_interval = slices_upper_bound - slices_lower_bound;
    std::div_t divresult;
    divresult = div (slice_interval, (slice_samples -1) );
    slice_period = divresult.quot;
    assert(slice_period >= 1);
    std::cout << "Calculated slice period of " << slice_period << std::endl;
    const int remainder_slices = divresult.rem;
    const int slices_to_period = (slice_samples -1) - remainder_slices;
    if ( remainder_slices > 0 ){
      std::cout << "WARNING: an adjustment needs to be made in the slices lower or upper bound." << std::endl;
      const int increase_top_range = slices_lower_bound + (slice_samples * slice_period );
      const int decrease_top_range = slices_lower_bound + ((slice_samples-1) * slice_period );
      const int decrease_bot_range = slices_lower_bound-slices_to_period + (slice_samples * slice_period );

      if ( increase_top_range <= number_slices_to_max_thickness ){
        std::cout << "Increasing top range to slice #" << increase_top_range << std::endl;
        std::cout << "Going to use one more sample than the requested " << slice_samples << " samples. Using " << (slice_samples+1) << " samples." << std::endl;
        slices_upper_bound = increase_top_range;
        slice_samples++;
      }
      else{
        if( decrease_bot_range >= 1 ){
          std::cout << "Decreasing bot range to slice #" << decrease_bot_range << std::endl;
          slices_lower_bound -= slices_to_period;
          slice_samples++;
        }
        else{
          std::cout << "Decreasing top range to slice #" << decrease_top_range << std::endl;
          slices_upper_bound = decrease_top_range;
        }
      }
    }

    std::cout << "MSA: Number slices to load " << slices_load << std::endl;
    std::cout << "MSA: Number slices to max thickness " << slices_load << std::endl;

    MSA_prm msa_parameters;
    // Since the release of MSA version 0.64 you may alternatively specify the electron energy in keV in line 6
    msa_parameters.set_electron_wavelength( ht_accelaration_voltage );
    msa_parameters.set_internal_repeat_factor_of_super_cell_along_x ( 1 );
    msa_parameters.set_internal_repeat_factor_of_super_cell_along_y ( 1 );
    std::stringstream input_prefix_stream;
    input_prefix_stream << "'" << slc_file_name_prefix << "'";
    std::string input_prefix_string = input_prefix_stream.str();
    msa_parameters.set_slice_filename_prefix ( input_prefix_string );
    msa_parameters.set_number_slices_to_load ( slices_load );
    msa_parameters.set_number_frozen_lattice_variants_considered_per_slice( 1 );
    msa_parameters.set_minimum_number_frozen_phonon_configurations_used_generate_wave_functions ( 1 );
    msa_parameters.set_period_readout_or_detection_in_units_of_slices ( 1 ); // bug
    msa_parameters.set_number_slices_used_describe_full_object_structure_up_to_its_maximum_thickness ( number_slices_to_max_thickness );
    msa_parameters.set_linear_slices_for_full_object_structure();
    msa_parameters.set_prm_file_name("temporary_msa_im2model.prm");
    msa_parameters.set_wave_function_name ("wave.wav");
    msa_parameters.produce_prm();
    if( msa_switch == true ){
      //msa_parameters.set_bin_path( msa_bin_string );
      msa_parameters.set_debug_switch(debug_switch);
      msa_parameters.call_bin();
    }

    if (cleanup_switch == true ){
      std::cout << " cleaning up celslc temporary files. { " << slc_file_name_prefix << "_***.sli && '"<<slc_file_name_prefix<<"'.prm }" << std::endl;
      celslc_parameters->cleanup_bin();
    }

    WAVIMG_prm wavimg_parameters;

    std::string wave_function_name =  "'wave_sl.wav'";
    std::string wavimg_prm_name = "temporary_wavimg_im2model.prm";
    std::string file_name_output_image_wave_function = "'image.dat'";
    // setters line 1
    wavimg_parameters.set_file_name_input_wave_function( wave_function_name );
    // setters line 2
    wavimg_parameters.set_n_columns_samples_input_wave_function_pixels( ny_simulated_vertical_samples );
    wavimg_parameters.set_n_rows_samples_input_wave_function_pixels( nx_simulated_horizontal_samples );
    // setters line 3
    wavimg_parameters.set_physical_columns_sampling_rate_input_wave_function_nm_pixels( sampling_rate_super_cell_x_nm_pixel );
    wavimg_parameters.set_physical_rows_sampling_rate_input_wave_function_nm_pixels( sampling_rate_super_cell_y_nm_pixel );
    // setters line 4
    wavimg_parameters.set_primary_electron_energy( ht_accelaration_voltage );
    // setters line 5
    wavimg_parameters.set_type_of_output( 0 );
    // setters line 6
    wavimg_parameters.set_file_name_output_image_wave_function( file_name_output_image_wave_function );
    // setters line 7
    wavimg_parameters.set_n_columns_samples_output_image( nx_simulated_horizontal_samples );
    wavimg_parameters.set_n_rows_samples_output_image( ny_simulated_vertical_samples );
    // setters line 8
    wavimg_parameters.set_image_data_type( 0 );
    wavimg_parameters.set_image_vacuum_mean_intensity( 3000.0f );
    wavimg_parameters.set_conversion_rate( 1.0f );
    wavimg_parameters.set_readout_noise_rms_amplitude( 0.0f ); // colocar a zero
    // setters line 9
    wavimg_parameters.set_switch_option_extract_particular_image_frame( 1 );
    // setters line 10
    wavimg_parameters.set_image_sampling_rate_nm_pixel( sampling_rate_super_cell_x_nm_pixel );
    // setters line 11
    wavimg_parameters.set_image_frame_offset_x_pixels_input_wave_function( 0.0f );
    wavimg_parameters.set_image_frame_offset_y_pixels_input_wave_function( 0.0f );
    // setters line 12
    wavimg_parameters.set_image_frame_rotation( 0.0f );
    // setters line 13
    wavimg_parameters.set_switch_coherence_model( 1 ); // colocar a zero
    // setters line 14
    wavimg_parameters.set_partial_temporal_coherence_switch( 1 );
    wavimg_parameters.set_partial_temporal_coherence_focus_spread( 4.0f );
    // setters line 15
    wavimg_parameters.set_partial_spacial_coherence_switch( 1 ); // colocar a zero
    wavimg_parameters.set_partial_spacial_coherence_semi_convergence_angle( 0.2f );
    // setters line 16
    wavimg_parameters.set_mtf_simulation_switch( 1 ); // alterar aqui para 0
    wavimg_parameters.set_k_space_scaling( 1.0f );
    wavimg_parameters.set_file_name_simulation_frequency_modulated_detector_transfer_function( "'../simulation/mtf/MTF-US2k-300.mtf'" );
    // setters line 17
    wavimg_parameters.set_simulation_image_spread_envelope_switch( 0 );
    wavimg_parameters.set_isotropic_one_rms_amplitude( 0.03 ); // colocar a zero
    //  wavimg_parameters.set_anisotropic_second_rms_amplitude( 0.0f );
    // wavimg_parameters.set_azimuth_orientation_angle( 0.0f );
    // setters line 18
    wavimg_parameters.set_number_image_aberrations_set( number_image_aberrations );
    // setters line 19
    // check for wavimg defocus aberration coefficient
    if( cd_switch == true ){
      //Defocus (a20, C1,0, C1)
      wavimg_parameters.add_aberration_definition ( 1, coefficient_aberration_defocus, 0.0f );
    }
    // check for wavimg spherical aberration coefficient
    if( cs_switch == true ){
      //Spherical aberration (a40, C3,0, C3)
      wavimg_parameters.add_aberration_definition ( 5, coefficient_aberration_spherical, 0.0f );
    }
    // setters line 19 + aberration_definition_index_number
    wavimg_parameters.set_objective_aperture_radius( 5500.0f );
    // setters line 20 + aberration_definition_index_number
    wavimg_parameters.set_center_x_of_objective_aperture( 0.0f );
    wavimg_parameters.set_center_y_of_objective_aperture( 0.0f );
    // setters line 21 + aberration_definition_index_number
    wavimg_parameters.set_number_parameter_loops( 2 );

    std::cout << "WAVIMG: parameter loop DEFOCUS: lower bound " << defocus_lower_bound << ", upper bound " << defocus_upper_bound << ", defocus samples " << defocus_samples << std::endl;
    std::cout << "WAVIMG: parameter loop SLICES: lower bound " << slices_lower_bound << ", upper bound " << slices_upper_bound << ", slice samples " << slice_samples << std::endl;

    wavimg_parameters.add_parameter_loop ( 1 , 1 , 1, defocus_lower_bound, defocus_upper_bound, defocus_samples, "'foc'" );
    wavimg_parameters.add_parameter_loop ( 3 , 1 , 1, slices_lower_bound, slices_upper_bound, slice_samples, "'_sl'" );
    wavimg_parameters.set_prm_file_name("temporary_wavimg_im2model.prm");

    if(wavimg_switch == true ){
      wavimg_parameters.produce_prm();
      //wavimg_parameters.set_bin_path( wavimg_bin_string );
      wavimg_parameters.set_debug_switch(debug_switch);
      wavimg_parameters.call_bin();
    }

    if (cleanup_switch == true ){
      std::cout << " cleaning up msa temporary files. { *.wav }" << std::endl;
      msa_parameters.cleanup_bin();
    }

    if (im2model_switch == true ){

      // Read the experimental image from file
      experimental_image = imread( experimental_image_path , CV_LOAD_IMAGE_GRAYSCALE );

      // initialize your temp images
      experimental_working = experimental_image.clone();

      //if fail to read the image
      if ( experimental_image.empty() ){
        std::cout << "Error loading the experimental image from: " << experimental_image_path << std::endl;
        return -1;
      }

      // Experimental image info
      // experimental image roi
      roi_x_size = roi_pixel_size;
      roi_y_size = roi_pixel_size;

      std::cout << "Experimental image width : " << experimental_image.cols << "px , heigth : " << experimental_image.rows << "px "<<  std::endl;
      roi_rectangle.x = roi_center_x  - ( roi_x_size / 2 );
      roi_rectangle.y = roi_center_y - ( roi_y_size / 2) ;
      roi_rectangle.width = roi_x_size;
      roi_rectangle.height = roi_y_size;
      experimental_working = experimental_image.clone();
      //copy the roi
      cv::Mat roi = experimental_image(roi_rectangle);
      experimental_image_roi.create( roi_x_size, roi_y_size , CV_8UC1 );
      roi.copyTo(experimental_image_roi);

      /////////////////////////////////////////////////
      // STEP LENGTH SIMULATION PROCESS STARTS HERE  //
      /////////////////////////////////////////////////

      std::cout << "Starting step length" << std::endl;
      SIMGRID_wavimg_steplength wavimg_simgrid_steps;

      wavimg_simgrid_steps.set_sampling_rate_super_cell_x_nm_pixel( sampling_rate_experimental_x_nm_per_pixel );
      wavimg_simgrid_steps.set_sampling_rate_super_cell_y_nm_pixel( sampling_rate_experimental_y_nm_per_pixel );
      wavimg_simgrid_steps.set_experimental_image_roi( experimental_image_roi );

      // defocus setters
      wavimg_simgrid_steps.set_defocus_lower_bound( defocus_lower_bound );
      wavimg_simgrid_steps.set_defocus_upper_bound( defocus_upper_bound );
      wavimg_simgrid_steps.set_defocus_samples( defocus_samples );
      wavimg_simgrid_steps.set_defocus_period( defocus_period );

      // thickness/slice setters

      wavimg_simgrid_steps.set_celslc_accum_nm_slice_vec( celslc_accum_nm_slice_vec );
      wavimg_simgrid_steps.set_slice_samples(slice_samples);
      wavimg_simgrid_steps.set_slice_period( slice_period );
      wavimg_simgrid_steps.set_number_slices_to_max_thickness( number_slices_to_max_thickness );
      wavimg_simgrid_steps.set_slices_lower_bound( slices_lower_bound );
      wavimg_simgrid_steps.set_slices_upper_bound( slices_upper_bound );

      wavimg_simgrid_steps.set_n_rows_simulated_image(nx_simulated_horizontal_samples);
      wavimg_simgrid_steps.set_n_cols_simulated_image(ny_simulated_vertical_samples);

      wavimg_simgrid_steps.set_reshaped_simulated_image_width(reshaped_simulated_image_width);
      wavimg_simgrid_steps.set_reshaped_simulated_image_height(reshaped_simulated_image_height);

      wavimg_simgrid_steps.set_ignore_edge_pixels_rectangle( ignore_edge_pixels_rectangle );
      wavimg_simgrid_steps.set_simulated_image_needs_reshape( simulated_image_needs_reshape );
      wavimg_simgrid_steps.set_reshape_factor_from_supper_cell_to_experimental_x (reshape_factor_from_supper_cell_to_experimental_x);
      wavimg_simgrid_steps.set_reshape_factor_from_supper_cell_to_experimental_y (reshape_factor_from_supper_cell_to_experimental_y);
      wavimg_simgrid_steps.set_sim_grid_switch(sim_grid_switch);
      wavimg_simgrid_steps.set_debug_switch(debug_switch);

      // user estimation of defocus and thickness
      if ( user_estimated_defocus_nm_switch ){
        wavimg_simgrid_steps.set_user_estimated_defocus_nm_switch( user_estimated_defocus_nm_switch );
        wavimg_simgrid_steps.set_user_estimated_defocus_nm( user_estimated_defocus_nm );
      }

      if ( user_estimated_thickness_nm_switch ){
        wavimg_simgrid_steps.set_user_estimated_thickness_nm_switch( user_estimated_thickness_nm_switch );
        wavimg_simgrid_steps.set_user_estimated_thickness_nm( user_estimated_thickness_nm );
      }

      if ( user_estimated_thickness_slice_switch ){
        wavimg_simgrid_steps.set_user_estimated_thickness_slice_switch( user_estimated_thickness_slice_switch );
        wavimg_simgrid_steps.set_user_estimated_thickness_slice( user_estimated_thickness_slice );
      }

      wavimg_simgrid_steps.set_iteration_number(1);
      wavimg_simgrid_steps.set_step_length_minimum_threshold ( 87.5f );
      wavimg_simgrid_steps.set_step_size( defocus_period, slice_period );
      wavimg_simgrid_steps.simulate_from_dat_file();

      if (cleanup_switch == true ){
        std::cout << " cleaning up wavimg temporary files. { *.dat }" << std::endl;
        wavimg_parameters.cleanup_bin();
      }

      //////////////////////////////////////////////////////
      // UNIT CELL AND SUPER CELL GENERATION STARTS HERE  //
      //////////////////////////////////////////////////////
      unit_cell.create_atoms_from_site_and_symetry();
      super_cell = Super_Cell::Super_Cell( &unit_cell );

      /** SUPER CELL setters **/
      const double best_match_thickness_nm = wavimg_simgrid_steps.get_simgrid_best_match_thickness_nm();
      const double best_match_defocus_nm = wavimg_simgrid_steps.get_simgrid_best_match_defocus_nm();

      super_cell.set_experimental_image( experimental_image , sampling_rate_experimental_x_nm_per_pixel, sampling_rate_experimental_y_nm_per_pixel );
      super_cell.set_simgrid_best_match_thickness_nm( best_match_thickness_nm );
      super_cell.set_super_cell_margin_nm( cel_margin_nm );
      /** SUPER CELL calculations **/
      super_cell.calculate_supercell_boundaries_from_experimental_image( cv::Point2f(roi_center_x, roi_center_y), edge_detection_threshold , max_contour_distance_px );
      super_cell.calculate_atomic_columns_position_w_boundary_polygon();
      super_cell.create_atoms_from_unit_cell();
      super_cell.orientate_atoms_from_matrix();
      super_cell.remove_z_out_of_range_atoms();
      super_cell.remove_xy_out_of_range_atoms();
      /** SUPER CELL exporting **/
      super_cell.create_fractional_positions_atoms();
      super_cell.generate_super_cell_file( "test_im2model.cel" );

      int _super_cell_nx = super_cell.get_super_cell_nx_px();
      int _super_cell_ny = super_cell.get_super_cell_ny_px();

      /*
       *
       * MILESTONE 3
       *
       * */
      CELSLC_prm* celslc_cel = new CELSLC_prm( _sim_tdmap_ostream_buffer );
      std::string super_cell_slc_filename_prefix = "cel_slc";
      celslc_cel->set_cel_file( "test_im2model.cel" );
      celslc_cel->set_slc_filename_prefix ( super_cell_slc_filename_prefix );
      celslc_cel->set_nx_simulated_horizontal_samples( _super_cell_nx );
      celslc_cel->set_ny_simulated_vertical_samples( _super_cell_ny );
      celslc_cel->set_ht_accelaration_voltage(ht_accelaration_voltage);
      celslc_cel->set_dwf_switch(dwf_switch);
      celslc_cel->set_abs_switch(abs_switch);
      std::cout << "preparing for single slice parallel calculation";
      //celslc_cel->set_bin_path( celslc_bin_string );

      if(celslc_cel_switch == true ){
        celslc_cel->call_bin_ssc( );
      }

      super_cell_nz_simulated_partitions = celslc_cel->get_nz_simulated_partitions();
      assert( super_cell_nz_simulated_partitions >= 1 );

      MSA_prm msa_cel;
      msa_cel.set_electron_wavelength( ht_accelaration_voltage );
      msa_cel.set_internal_repeat_factor_of_super_cell_along_x ( 1 );
      msa_cel.set_internal_repeat_factor_of_super_cell_along_y ( 1 );
      std::stringstream input_prefix_stream;
      input_prefix_stream << "'" << super_cell_slc_filename_prefix << "'";
      std::string input_prefix_string = input_prefix_stream.str();
      msa_cel.set_slice_filename_prefix ( input_prefix_string );
      msa_cel.set_number_slices_to_load ( super_cell_nz_simulated_partitions );
      msa_cel.set_number_frozen_lattice_variants_considered_per_slice( 1 );
      msa_cel.set_minimum_number_frozen_phonon_configurations_used_generate_wave_functions ( 1 );
      // only use the last *.wav file
      msa_cel.set_period_readout_or_detection_in_units_of_slices ( 0 ); // bug
      msa_cel.set_number_slices_used_describe_full_object_structure_up_to_its_maximum_thickness ( super_cell_nz_simulated_partitions );
      msa_cel.set_linear_slices_for_full_object_structure();
      msa_cel.set_prm_file_name("temporary_msa_im2model.prm");
      msa_cel.set_wave_function_name ("wave_cel.wav");
      msa_cel.produce_prm();
      //msa_cel.set_bin_path( msa_bin_string );
      msa_cel.set_debug_switch( true );
      if(msa_cel_switch == true ){
        msa_cel.call_bin( );
      }

      /* Calculate the defocus period of the super-cell */
      super_cell_defocus_period = super_cell_defocus_interval / ((double) (super_cell_defocus_samples -1));
      super_cell_defocus_lower_bound = best_match_defocus_nm - (super_cell_defocus_interval / 2.0f);
      super_cell_defocus_upper_bound = best_match_defocus_nm + (super_cell_defocus_interval / 2.0f);

      WAVIMG_prm wavimg_cel;

      std::stringstream wave_function_name_stream;
      wave_function_name_stream << "'wave_cel_sl" << std::setw(3) << std::setfill('0') << std::to_string( super_cell_nz_simulated_partitions ) << ".wav'";

      std::string wavimg_prm_name = "temporary_wavimg_im2model.prm";
      std::string file_name_output_image_wave_function = "'image.dat'";
      // setters line 1
      wavimg_cel.set_file_name_input_wave_function( wave_function_name_stream.str() );
      // setters line 2
      wavimg_cel.set_n_columns_samples_input_wave_function_pixels( _super_cell_nx );
      wavimg_cel.set_n_rows_samples_input_wave_function_pixels( _super_cell_ny );
      // setters line 3
      wavimg_cel.set_physical_columns_sampling_rate_input_wave_function_nm_pixels( sampling_rate_super_cell_x_nm_pixel );
      wavimg_cel.set_physical_rows_sampling_rate_input_wave_function_nm_pixels( sampling_rate_super_cell_y_nm_pixel );
      // setters line 4
      wavimg_cel.set_primary_electron_energy( ht_accelaration_voltage );
      // setters line 5
      wavimg_cel.set_type_of_output( 0 );
      // setters line 6
      wavimg_cel.set_file_name_output_image_wave_function( file_name_output_image_wave_function );
      // setters line 7
      wavimg_cel.set_n_columns_samples_output_image( _super_cell_nx );
      wavimg_cel.set_n_rows_samples_output_image( _super_cell_ny );
      // setters line 8
      wavimg_cel.set_image_data_type( 0 );
      wavimg_cel.set_image_vacuum_mean_intensity( 3000.0f );
      wavimg_cel.set_conversion_rate( 1.0f );
      wavimg_cel.set_readout_noise_rms_amplitude( 0.0f ); // colocar a zero
      // setters line 9
      wavimg_cel.set_switch_option_extract_particular_image_frame( 1 );
      // setters line 10
      wavimg_cel.set_image_sampling_rate_nm_pixel( sampling_rate_super_cell_x_nm_pixel );
      // setters line 11
      wavimg_cel.set_image_frame_offset_x_pixels_input_wave_function( 0.0f );
      wavimg_cel.set_image_frame_offset_y_pixels_input_wave_function( 0.0f );
      // setters line 12
      wavimg_cel.set_image_frame_rotation( 0.0f );
      // setters line 13
      wavimg_cel.set_switch_coherence_model( 1 ); // colocar a zero
      // setters line 14
      wavimg_cel.set_partial_temporal_coherence_switch( 1 );
      wavimg_cel.set_partial_temporal_coherence_focus_spread( 4.0f );
      // setters line 15
      wavimg_cel.set_partial_spacial_coherence_switch( 1 ); // colocar a zero
      wavimg_cel.set_partial_spacial_coherence_semi_convergence_angle( 0.2f );
      // setters line 16
      wavimg_cel.set_mtf_simulation_switch( 1 ); // alterar aqui para 0
      wavimg_cel.set_k_space_scaling( 1.0f );
      wavimg_cel.set_file_name_simulation_frequency_modulated_detector_transfer_function( "'../simulation/mtf/MTF-US2k-300.mtf'" );
      // setters line 17
      wavimg_cel.set_simulation_image_spread_envelope_switch( 0 );
      wavimg_cel.set_isotropic_one_rms_amplitude( 0.03 ); // colocar a zero
      //  wavimg_parameters.set_anisotropic_second_rms_amplitude( 0.0f );
      // wavimg_parameters.set_azimuth_orientation_angle( 0.0f );
      // setters line 18
      wavimg_cel.set_number_image_aberrations_set( number_image_aberrations );
      // setters line 19
      // check for wavimg defocus aberration coefficient
      if( cd_switch == true ){
        //Defocus (a20, C1,0, C1)
        wavimg_cel.add_aberration_definition ( 1, coefficient_aberration_defocus, 0.0f );
      }
      // check for wavimg spherical aberration coefficient
      if( cs_switch == true ){
        //Spherical aberration (a40, C3,0, C3)
        wavimg_cel.add_aberration_definition ( 5, coefficient_aberration_spherical, 0.0f );
      }
      // setters line 19 + aberration_definition_index_number
      wavimg_cel.set_objective_aperture_radius( 5500.0f );
      // setters line 20 + aberration_definition_index_number
      wavimg_cel.set_center_x_of_objective_aperture( 0.0f );
      wavimg_cel.set_center_y_of_objective_aperture( 0.0f );
      // setters line 21 + aberration_definition_index_number
      wavimg_cel.set_prm_file_name("temporary_wavimg_im2model.prm");
      wavimg_cel.set_number_parameter_loops( 1 );
      wavimg_cel.add_parameter_loop ( 1 , 1 , 1, super_cell_defocus_lower_bound, super_cell_defocus_upper_bound, super_cell_defocus_samples, "'foc'" );
      wavimg_cel.produce_prm();
      //wavimg_cel.set_bin_path( wavimg_bin_string );
      wavimg_cel.set_debug_switch( true );

      if(wavimg_cel_switch == true ){
        wavimg_cel.call_bin();
      }
      super_cell.set_file_name_input_dat( "image" );
      super_cell.set_super_cell_simulated_defocus_lower_bound( super_cell_defocus_lower_bound );
      super_cell.set_super_cell_simulated_defocus_upper_bound( super_cell_defocus_upper_bound );
      super_cell.set_super_cell_simulated_defocus_samples( super_cell_defocus_samples );
      super_cell.set_super_cell_simulated_defocus_period( super_cell_defocus_period );
      super_cell.read_simulated_super_cells_from_dat_files();
      super_cell.match_experimental_simulated_super_cells();

    }
    if( vis_gui_switch ){
      /* VIS */
      //      AppVis();
    }
  }
  catch(std::exception& e){
    std::cerr << "Unhandled Exception reached the top of main: "
      << e.what() << ", application will now exit" << std::endl;
    return -1;
  }
  return 0;
}


/*
  main.cpp
  Takes two tables of weights data - one with baseline data, the other with users/items for which we want features to be outputted (users table)
  Loads weights from the two tables, using item_ids and translating users table user IDs to avoid overlap
  Performs SVD on the resultant weights matrix and outputs the features and item predictions for the desired users into an output table.
  For up-to-date help: svd --help
*/

#include <boost/program_options.hpp>
namespace options = boost::program_options;
using namespace std;

#include <mysql++.h>
using namespace mysqlpp;
using namespace std;

#include <string>

#include "insert_queue.hpp"
#include "svd.hpp"

void loadBaseline(SVD* svd, Connection& db, std::string baseline_table, std::string item_id_col) {
  // Loads all of the weights in the baseline table.

  cout << "Loading baseline data." << endl;
  unsigned int compact_id = 0, user_id = 0, item_id = 0, i = 0, baseline_count = 0;
  float weight = 0;
  UseQueryResult weights_itr;
  Row weight_row;

  float min_weight = svd->MinWeight(), max_weight = svd->MaxWeight();

  // load the training data row by row.
  try {
    Query weights_query = db.query();
    weights_query << "SELECT COUNT(*) FROM " << baseline_table << " WHERE score BETWEEN " <<  min_weight << " AND " << max_weight;
    baseline_count = (unsigned int) atoi(weights_query.store()[0]["COUNT(*)"]);
    cout << "Found " << baseline_count << " baseline weights." << endl;
    weights_query.reset();
    weights_query << "SELECT user_id, " << item_id_col << ", score FROM " << baseline_table << " WHERE score BETWEEN " <<  min_weight << " AND " << max_weight << " ORDER BY user_id ASC, " << item_id_col << " ASC";
    weights_itr = weights_query.use();
  } catch (BadQuery er) {
    cerr << "Error loading baseline weights." << endl;
    throw er;
  }

  cout << "Loading baseline weights into memory..." << endl;

  for (i = 0; i < baseline_count; i++) {
    weight_row = weights_itr.fetch_row();
    try {
      user_id = (unsigned int) atoi(weight_row["user_id"]);
    } catch (BadFieldName er) {
      // we've reached the end of the data.
      cout << "user id not in fields for row " << i << "." << endl;
      break;
    }
    item_id = (unsigned int) strtoul(weight_row[item_id_col.c_str()].c_str(), NULL, 0);
    weight = atof(weight_row["score"]);

    // If weight exceeds our boundaries, throw an exception.
    if (weight < min_weight || weight > max_weight) {
      std::ostringstream error_text;
      error_text << "Weight in baseline table exceeds specified bounds: " << weight_row["score"] << " ~> " << weight;
      throw std::out_of_range(error_text.str());
    }
    svd->LoadBaselineRow(user_id, item_id, weight);
  }
  cout << "Finished loading baseline data: " << baseline_count << " weights." << endl;
}

void loadWeights(SVD* svd, Connection& db, std::string weights_table, std::string item_id_col) {
  // Loads all of the weights in the weights table.
  cout << "Counting training weights..." << endl;

  unsigned int i = 0, compact_id = 0, user_id = 0, item_id = 0, WeightsCount = 0;
  float weight = 0;
  UseQueryResult weights_itr;
  Row weight_row;

  float min_weight = svd->MinWeight(), max_weight = svd->MaxWeight();

  // load the training data row by row.
  // TODO: alter query to get unique latest list for each user.
  try {
    Query weights_query = db.query();
    weights_query << "SELECT COUNT(*) FROM " << weights_table << " WHERE score BETWEEN " <<  min_weight << " AND " << max_weight;
    WeightsCount = (unsigned int) atoi(weights_query.store()[0]["COUNT(*)"]);
    cout << "Found " << WeightsCount << " weights." << endl;
    weights_query.reset();
    weights_query << "SELECT user_id, " << item_id_col << ", score FROM " << weights_table << " WHERE score BETWEEN " <<  min_weight << " AND " << max_weight << " ORDER BY user_id ASC, " << item_id_col << " ASC";
    weights_itr = weights_query.use();
  } catch (BadQuery er) {
    cerr << "Error loading weights." << endl;
    throw er;
  }

  cout << "Loading training weights into memory..." << endl;

  for (i = 0; i < WeightsCount; i++) {
    weight_row = weights_itr.fetch_row();
    try {
      user_id = (unsigned int) atoi(weight_row["user_id"]);
    } catch (BadFieldName er) {
      // we've reached the end of the data.
      cout << "user id not in fields for row " << i << "." << endl;
      break;
    }
    item_id = (unsigned int) strtoul(weight_row[item_id_col.c_str()].c_str(), NULL, 0);
    weight = atof(weight_row["score"]);

    // If weight exceeds our boundaries, throw an exception.
    if (weight < min_weight || weight > max_weight) {
      std::ostringstream error_text;
      error_text << "Weight in training table exceeds specified bounds: " << weight_row["score"] << " ~> " << weight;
      throw std::out_of_range(error_text.str());
    }
    svd->LoadWeightRow(user_id, item_id, weight);
  }

  cout << "Finished loading training weight data: " << WeightsCount << " weights." << endl;
}

void loadTests(SVD* svd, Connection& db, std::string test_table, std::string item_id_col) {
  // Loads all of the weights in the test table.
  // TODO: auto-partition baseline into baseline/test sets, making this unnecessary.
  cout << "Loading test data." << endl;

  unsigned int i = 0, user_id = 0, item_id = 0, TestCount = 0;
  float weight = 0;
  UseQueryResult weights_itr;
  Row weight_row;

  float min_weight = svd->MinWeight(), max_weight = svd->MaxWeight();

  // load the test data row by row.
  try {
    Query weights_query = db.query();
    weights_query << "SELECT COUNT(*) FROM " << test_table << " WHERE score BETWEEN " <<  min_weight << " AND " << max_weight;
    TestCount = (unsigned int) atoi(weights_query.store()[0]["COUNT(*)"]);
    cout << "Found " << TestCount << " test weights." << endl;
    weights_query.reset();
    weights_query << "SELECT user_id, " << item_id_col << ", score FROM " << test_table << " WHERE score BETWEEN " <<  min_weight << " AND " << max_weight << " ORDER BY user_id ASC, " << item_id_col << " ASC";
    weights_itr = weights_query.use();
  } catch (BadQuery er) {
    cerr << "Error loading test weights." << endl;
    throw er;
  }

  cout << "Loading test weights into memory..." << endl;
  for (i = 0; i < TestCount; i++) {
    weight_row = weights_itr.fetch_row();
    try {
      user_id = (unsigned int) atoi(weight_row["user_id"]);
    } catch (BadFieldName er) {
      // we've reached the end of the test data.
      cout << "user_id not in fields for row " << i << endl;
      break;
    }
    item_id = (unsigned int) strtoul(weight_row[item_id_col.c_str()].c_str(), NULL, 0);
    weight = atof(weight_row["score"]);
    // If weight exceeds our boundaries, throw an exception.
    if (weight < min_weight || weight > max_weight) {
      std::ostringstream error_text;
      error_text << "Weight in testing table exceeds specified bounds: " << weight_row["score"] << " ~> " << weight;
      throw std::out_of_range(error_text.str());
    }
    svd->LoadTestRow(user_id, item_id, weight);
  }
  cout << "Finished loading test data: " << TestCount << " weights." << endl;
}

void saveModel(SVD* svd, Connection& db, std::string global_table, std::string means_table, std::string feature_table, std::string item_type) {
  // saves the SVD's features to output tables.
  // saves global mean for this entity pair to global_table,
  // each entity's paired global mean to means_table,
  // and each entity's paired feature list to feature_table.

  cout << "Saving SVD model..." << endl;
  unsigned int compact_id = 0, sparseID = 0, item_id = 0;
  float prediction = 0.0;

  IdMap UserIDs = svd->UserIDs();
  vector<User> Users = svd->Users();
  unsigned int Features = svd->FeaturesCount();
  vector<unsigned int> SelectedItems = svd->SelectedItems();
  vector<Item> Items = svd->Items();
  vector<float> UserFeatures, ItemFeatures;

  // create insert queues.
  vector<std::string> meanFields, featureFields, predictionFields;
  Query insertRow(db.query()), globalQuery(db.query()), deleteMeansQuery(db.query()), deleteFeaturesQuery(db.query()), deletePredictionsQuery(db.query());

  // std::string meanFieldNames[] = {"id", "type", "source", "compare_type", "mean"};
  // meanFields.assign(meanFieldNames, meanFieldNames+5);

  std::string featureFieldNames[] = {"id", "type", "feature", "value"};
  featureFields.assign(featureFieldNames, featureFieldNames+4);

  // buffer length for inserts. larger values mean faster insertion (so long as the queue can fit in memory)!
  unsigned int maxQueueLength = 100000;

  InsertQueue featureInserts(db, maxQueueLength, feature_table, featureFields);
  // InsertQueue meanInserts(db, maxQueueLength, means_table, meanFields);

  // // update global means for this entity type pair.
  // try {
  //   globalQuery << "INSERT INTO " << global_table << " (type_1, type_2, mean) VALUES (" << item_type1 << "," << item_type2 << "," << GlobalAvg << ") ON DUPLICATE KEY UPDATE mean=" << GlobalAvg;
  //   globalQuery.execute();
  // } catch (BadQuery er) {
  //   cerr << "Error updating global mean." << endl;
  //   throw er;
  // }

  // clear all the means, features, and predictions for this pair of types.
  // try {
  //   deleteMeansQuery << "DELETE FROM " << means_table << " WHERE type=" << item_type1 << " && compare_type=" << item_type2;
  //   deleteMeansQuery.execute();
  // } catch (BadQuery er) {
  //   cerr << "Error deleting entity means." << endl;
  //   throw er;
  // }

  try {
    deleteFeaturesQuery << "DELETE FROM " << feature_table;
    deleteFeaturesQuery.execute();
    // deleteFeaturesQuery << "DELETE FROM " << feature_table << " WHERE type='user'";
    // deleteFeaturesQuery.execute();
  } catch (BadQuery er) {
    cerr << "Error deleting entity and user features." << endl;
    throw er;
  }

  cout << "Saving users..." << endl;
  for (IdMapItr userIterator = UserIDs.begin(); userIterator != UserIDs.end(); ++userIterator) {
    sparseID = userIterator->first;
    compact_id = userIterator->second;
    // update user means.
    // insertRow.reset();
    // insertRow << "(" << quote << sparseID << "," << quote << "user" << "," << quote << Users[compact_id].source << "," << quote << item_type2 << "," << quote << Users[compact_id].regularized_avg << ")";
    // meanInserts.Append(insertRow.str());

    // only update features if this user has them calculated!
    if (Users[compact_id].weights_count == 0) {
      continue;
    }

    // update user features.
    UserFeatures = svd->UserFeatures(sparseID);
    for (unsigned int feature = 0; feature < Features; feature++) {
      insertRow.reset();
      insertRow << "(" << quote << sparseID << "," << quote << "user" << "," << quote << feature << "," << quote << UserFeatures[feature] << ")";
      featureInserts.Append(insertRow.str());
    }
  }
  cout << "Saving items..." << endl;
  for (vector<unsigned int>::iterator itemIterator = SelectedItems.begin(); itemIterator != SelectedItems.end(); ++itemIterator) {
    compact_id = (unsigned int) *itemIterator;
    sparseID = Items[compact_id].sparse_id;

    // update item means.
    // insertRow.reset();
    // insertRow << "(" << quote << Items[compact_id].sparse_id << "," << quote << item_type2 << "," << quote << Items[compact_id].source << "," << quote << item_type1 << "," << quote << Items[compact_id].regularized_avg << ")";
    // meanInserts.Append(insertRow.str());

    // only update features if this item has them calculated!
    if (Items[compact_id].weights_count == 0) {
      continue;
    }
    // update item features.

    ItemFeatures = svd->ItemFeatures(sparseID);
    for (unsigned int feature = 0; feature < Features; feature++) {
      insertRow.reset();
      insertRow << "(" << quote << Items[compact_id].sparse_id << "," << quote << item_type << "," << quote << feature << "," << quote << ItemFeatures[feature] << ")";
      featureInserts.Append(insertRow.str());
    }
  }
  // meanInserts.Flush();
  featureInserts.Flush();
  cout << "SVD saved." << endl;
}

int main(int argc, char* argv[]) {
  // Declare the supported options.
  options::options_description generalOptions("General options");
  generalOptions.add_options()
    ("help", "produce this help message")
    ("host", options::value<std::string>()->default_value("localhost"), "MySQL host")
    ("username", options::value<std::string>()->default_value("root"), "MySQL username")
    ("password", options::value<std::string>()->default_value(""), "MySQL password")
    ("database", options::value<std::string>()->default_value("animurecs"), "MySQL database")
    ("baseline_table", options::value<std::string>(), "table with baseline training data")
    ("weight_table", options::value<std::string>(), "weights table with entities for whom you want features to be outputted")
    ("means_table", options::value<std::string>()->default_value("entity_stats"), "table with entity means")
    ("test_table", options::value<std::string>(), "table with test data")
    ("test_percent", options::value<int>(), "percentage of baseline and weight data to partition into a test set. cannot be used in conjunction with test_table.")
    ("item_id_col", options::value<std::string>(), "column name in tables containing entity IDs")
    ("min_weight", options::value<float>()->default_value(1), "lower floor on weight values in tables")
    ("max_weight", options::value<float>()->default_value(10), "upper ceiling on weight values in tables")
    ("type", options::value<std::string>(), "entity type to calculate and output features for")
  ;
  options::options_description svdOptions("SVD options");
  svdOptions.add_options()
    ("global_table", options::value<std::string>(), "table to store global values (e.g. global weight mean)")
    ("feature_table", options::value<std::string>(), "output table for features")
    ("features", options::value<int>()->default_value(50), "number of features")
    ("num_priors", options::value<int>()->default_value(25), "weight of priors in regularizing means")
    ("f_init", options::value<float>()->default_value(0.01), "initial value of features")
    ("min_epochs", options::value<int>()->default_value(50), "minimal number of epochs to train")
    ("max_epochs", options::value<int>()->default_value(200), "maximal number of epochs to train")
    ("min_improvement", options::value<float>()->default_value(0.0001), "minimal rmse improvement per epoch required to continue training a feature")
    ("min_weights", options::value<int>()->default_value(20), "minimal number of weights to have features calculated")
    ("l_rate", options::value<float>()->default_value(0.001), "learning rate")
    ("tikhonov", options::value<float>()->default_value(0.02), "tikhonov regularization parameter")
  ;

  options::options_description allOptions("Allowed options");
  allOptions.add(generalOptions).add(svdOptions);
  options::variables_map vm;
  options::store(options::parse_command_line(argc, argv, allOptions), vm);
  options::notify(vm);

  if (vm.count("help")) {
    cout << allOptions << endl;
    return 1;
  }

  try {
    Connection database(vm["database"].as<std::string>().c_str(), vm["host"].as<std::string>().c_str(), vm["username"].as<std::string>().c_str(), vm["password"].as<std::string>().c_str());
    if (!database.connected()) {
      throw new ConnectionFailed("Could not connect to database.");
    }

    // perform SVD on weights matrix.
    SVD* svd = new SVD(vm["features"].as<int>(), vm["min_epochs"].as<int>(), vm["max_epochs"].as<int>(), vm["min_improvement"].as<float>(), vm["l_rate"].as<float>(), vm["tikhonov"].as<float>(), vm["f_init"].as<float>(), vm["num_priors"].as<int>(), vm["min_weights"].as<int>(), vm["min_weight"].as<float>(), vm["max_weight"].as<float>());
    if (vm.count("baseline_table")) {
      loadBaseline(svd, database, vm["baseline_table"].as<std::string>(), vm["item_id_col"].as<std::string>());
    }
    if (vm.count("weight_table")) {
      loadWeights(svd, database, vm["weight_table"].as<std::string>(), vm["item_id_col"].as<std::string>());
    }
    if (vm.count("test_table")) {
      loadTests(svd, database, vm["test_table"].as<std::string>(), vm["item_id_col"].as<std::string>());
    } else if (vm.count("test_percent")) {
      svd->PartitionWeights(vm["test_percent"].as<int>());
    }

    svd->Train();

    if (vm.count("test_table")) {
      cout << "Final RMSE: " << svd->TestRMSE() << endl;
    }
    if (vm.count("feature_table")) {
      saveModel(svd, database, vm["global_table"].as<std::string>(), vm["means_table"].as<std::string>(), vm["feature_table"].as<std::string>(), vm["type"].as<std::string>());
    }
    svd->PrintTimings();
    svd->PrintBenchmarks();

    vector<float> feature_improvements = svd->FeatureImprovements();
    cout << "Feature improvements:" << endl;
    cout << "=====================" << endl;
    for (int f = 0; f < feature_improvements.size(); f++) {
      cout << f << ": " << feature_improvements[f] << endl;
    }
  } catch (ConnectionFailed er) {
    cerr << "Could not connect to MySQL database." << endl;
    throw er;
  }
  return 0;
}


#include <iostream>
#include <iomanip>
#include <Eigen/Dense>
#include "eigen_boost_serialization.hpp"
#include "state.h"
#include "test.h"
#include "multi_test.h"
#include "init.h"
#include "loader.h"
#include "dataset.h"
#include "testing.h"
#include <random>
#include <boost/program_options.hpp>

namespace po = boost::program_options;

using namespace std;
using namespace Eigen;

template<typename F, typename I>
void run() {
    MutableState<F,I> ms = Init<F,I>::initMutableState();
    StaticState<F,I> ss = Init<F,I>::initStaticState();
    Buffers<F,I> b = Init<F,I>::initBuffers();

    run(ms, ss, b);
}

void runTesting(int iter) {
    int numRuns = NBSTEPSPERPRESRUN;
    MutableState<double,int> ms = loadMutableState(iter);
    StaticState<double,int> ss = loadStaticState();
    Buffers<double,int> b = Init<double,int>::initBuffers();
    RandomHistorical<double> r = loadRandomHistorical();

    auto tup = run(ms, ss, b, r);

    ms = std::get<0>(tup);
    b = std::get<1>(tup);

    MutableState<double,int> msnext = loadMutableState(iter+numRuns);

    cout << std::setprecision(15);

    int i;

    cout << "wadap err+: " << (ms.wadap - msnext.wadap).maxCoeff(&i) << " @ " << i << endl;
    cout << "wadap err+: " << (ms.wadap - msnext.wadap).minCoeff(&i) << " @ " << i << endl;
    cout << endl;

    cout << "xplastff err+: " << (ms.xplastFF - msnext.xplastFF).maxCoeff(&i) << " @ " << i << endl;
    cout << "xplastff err-: " << (ms.xplastFF - msnext.xplastFF).minCoeff(&i) << " @ " << i << endl;
    cout << endl;

    cout << "xplastlat err+: " << (ms.xplastLat - msnext.xplastLat).maxCoeff(&i) << " @ " << i << endl;
    cout << "xplastlat err-: " << (ms.xplastLat - msnext.xplastLat).minCoeff(&i) << " @ " << i << endl;
    cout << endl;

    VectorX<double> neurLTD = loadNeurLTD(iter+numRuns);

    cout << "neurLTD err+: " << (b.eachNeurLTD - neurLTD).maxCoeff(&i) << " @ " << i << endl;
    cout << "neurLTD err-: " << (b.eachNeurLTD - neurLTD).minCoeff(&i) << " @ " << i << endl;
    cout << endl;

    VectorX<double> neurLTP = loadNeurLTP(iter+numRuns);

    cout << "neurLTP err+: " << (b.eachNeurLTP - neurLTP).maxCoeff(&i) << " @ " << i << endl;
    cout << "neurLTP err-: " << (b.eachNeurLTP - neurLTP).minCoeff(&i) << " @ " << i << endl;
    cout << endl;

    //cout << b.lgnfirings << endl;
    for (int i = 1; i < 251; i++) {
        VectorX<double> lgnfirings = loadLgnfirings(i);
        VectorX<double> lgnfiringsRow = b.lgnfirings.row(i-1).cast<double>();
        VectorX<double> diff = (lgnfirings - lgnfiringsRow).cwiseAbs();
        if (diff.maxCoeff() > 0.001) {
            cout << "lgnfirings don't match" << endl;
            exit(1);
        }
    }

    for (int i = 251; i < 350; i++) {
        VectorX<double> lgnfirings = loadLgnfirings(i);
        if (lgnfirings.maxCoeff() > 0.001) {
            cout << "lgnfirings aren't zero after 250" << i << endl;
            exit(1);
        }
    }
}

int main(int argc, char* argv[]) {
    po::options_description desc("Usage");
    desc.add_options()
        ("help", "produce help message")
        ("multi", "run the simple multi-gpu test")
        ("double,D", "Use Doubles (default Floats)")
        ("testing,T", "Run in testing mode")
        ;

    po::variables_map opts;
    po::store(po::parse_command_line(argc, argv, desc), opts);
    if (opts.count("help")) {
        cout << desc << "\n";
        return 1;
    }

    if (opts.count("multi")) {
        call();
        return 0;
    }

    if(opts.count("testing")) {
        if (opts.count("double")) {
            cout << "Running Doubles, Testing" << endl;
            runTesting(0);
        } else {
            cout << "Running Floats, Testing" << endl;
            cout << "no option to do this, dying" << endl;
            exit(1);
        }
    } else {
        if (opts.count("double")) {
            cout << "Running Doubles" << endl;
            run<double,int>();
        } else {
            cout << "Running Floats" << endl;
            run<float,int>();
        }
    }
}


#ifndef TYPES_HPP_
#define TYPES_HPP_
#include <string>
#include <opencv2/core/core.hpp>

#include <glm.hpp>
#include <Eigen/Core>
#include <glm.hpp>

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Delaunay_triangulation_3.h>
#include <CGAL/Triangulation_hierarchy_3.h>
#include <CGAL/Triangulation_cell_base_with_info_3.h>
#include <CGAL/Triangulation_vertex_base_with_info_3.h>
#include <CGAL/Projection_traits_xy_3.h>
#include <CGAL/intersections.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Polyhedron_3.h>
#include <CGAL/IO/Color.h>
#include <CGAL/algorithm.h>
//This header includes some useful data structure to manage the configuration parameters

struct CameraType {
    glm::mat3 intrinsics;
    glm::mat3 rotation;
    glm::vec3 translation;
    glm::mat4 cameraMatrix;
    glm::mat4 mvp;
    glm::vec3 center;
    std::string cameraPath;
    int imageWidth;
    int imageHeight;
    glm::vec3 distortion_coeff;
};

struct SfMData {
  int numCameras_;
  std::vector<CameraType> camerasList_;
  int imageWidth_, imageHeight_;
};

struct ViewerConfiguration {
    std::string camerasFilePath_;
    std::string correspondencesPath_;
    std::string pathOutDir_;
} ;

enum class classLabel {
  CAR, WALL, GROUND, VEGETATION, POLE,PEDESTRIAN, UNDEFINED,
};
template<class T>
class ClassVote {
public:
  ClassVote() {

    classVote[classLabel::CAR] = static_cast<T>(0);
    classVote[classLabel::GROUND] = static_cast<T>(0);
    classVote[classLabel::POLE] = static_cast<T>(0);
    classVote[classLabel::PEDESTRIAN] = static_cast<T>(0);
    classVote[classLabel::UNDEFINED] = static_cast<T>(0);
    classVote[classLabel::VEGETATION] = static_cast<T>(0);
    classVote[classLabel::WALL] = static_cast<T>(0);
  }
  T maxValue() const {
    double maxval = -1;
    for (auto it = classVote.begin(); it != classVote.end(); it++) {
      if (maxval < it->second) {
        maxval = it->second;
      }
    }
    return maxval;
  }
  classLabel maxClass()const{
    classLabel max;
    double maxval = -1;
    for (auto it = classVote.begin(); it != classVote.end(); it++) {
      if (maxval < it->second) {
        maxval = it->second;
        max = it->first;
      }
    }
    return max;
  }
  ClassVote& operator=(ClassVote other) {
    auto iteratorInput = other.classVote.begin();
    for (auto iterator = this->classVote.begin(); iterator != this->classVote.end() && iteratorInput != other.classVote.end(); iterator++, iteratorInput++) {
      iterator->second = iteratorInput->second;
    }
    return *this;
  }
  std::map<classLabel, T> classVote;
};

class LabelClass {
public:
  LabelClass() :
      label(classLabel::UNDEFINED), id(0) {
  }
  LabelClass(classLabel l) :
      label(l), id(0) {
  }
  LabelClass(classLabel l, size_t i) :
      label(l), id(i) {
  }

  classLabel l() const {
    return label;
  }

  size_t idx() const {
    return id;
  }

  void set_label(classLabel l) {
    label = l;
  }
  void set_id(size_t v) {
    id = v;
  }
  bool operator==(const LabelClass &lab) const {
    return l() == lab.l() && idx() == lab.idx();
  }

  bool operator!=(const LabelClass &l) const {
    return !((*this) == l);
  }

  LabelClass& operator=(const LabelClass &l) {
    label = l.l();
    id = l.idx();
    return *this;
  }

private:
  classLabel label;
  size_t id;
};

// A face type with a color member variable.
template<class Refs>
struct My_face : public CGAL::HalfedgeDS_face_base<Refs> {
  CGAL::Color color;
  int count=0;
  LabelClass label;
};
// An items type using my face.
struct My_items : public CGAL::Polyhedron_items_3 {
  template<class Refs, class Traits>
  struct Face_wrapper {
    typedef My_face<Refs> Face;
  };
};

typedef CGAL::Simple_cartesian<float> Kernelmy;
typedef CGAL::Polyhedron_3<Kernelmy, My_items> Polyhedronmy;
typedef Polyhedronmy::Facet_handle Facet_handlemy;
typedef Polyhedronmy::Halfedge_handle Halfedge_handlemy;
typedef Polyhedronmy::Facet_iterator Facet_iteratormy;
typedef Polyhedronmy::Vertex_handle Vertex_handlemy;
typedef Polyhedronmy::Halfedge_iterator Halfedge_iteratormy;
typedef Polyhedronmy::Point_3 Pointmy;
typedef Kernelmy::Vector_3 Vectormy;

#endif /* TYPES_HPP_ */


// Copyright (c) 2016-2019 Duality Blockchain Solutions Developers
// Copyright (c) 2014-2019 The Dash Core Developers
// Copyright (c) 2009-2019 The Bitcoin Developers
// Copyright (c) 2009-2019 Satoshi Nakamoto
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

/**
 * Copyright (c) 2013-2014 Tomas Dzetkulic
 * Copyright (c) 2013-2014 Pavol Rusnak
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

// Source:
// https://github.com/trezor/trezor-crypto

#include "bip39.h"
#include "wordlists/english.h"
#include "wordlists/chinese_simplified.h"
#include "wordlists/chinese_traditional.h"
#include "wordlists/french.h"
//#include "wordlists/german.h"  //exclude for now, but include later
#include "wordlists/italian.h"
#include "wordlists/japanese.h"
#include "wordlists/korean.h"
#include "wordlists/russian.h"
#include "wordlists/spanish.h"
#include "wordlists/ukrainian.h"
#include "crypto/sha256.h"
#include "random.h"

#include "util.h"

#include <boost/algorithm/string.hpp>

#include <openssl/evp.h>

SecureString CMnemonic::Generate(int strength, Language selectLanguage)
{
    if (strength < 128 || strength > 256) {
        return SecureString();
    }
    SecureVector data(32);
    GetRandBytes(&data[0], 32);
    SecureString mnemonic = FromData(data, strength / 8,selectLanguage);
    return mnemonic;
}

// SecureString CMnemonic::FromData(const uint8_t *data, int len)
SecureString CMnemonic::FromData(const SecureVector& data, int len, Language selectLanguage)
{
    const char* const* refWordList = nullptr; //initialize

    getWordList(refWordList,selectLanguage);

    if (len % 4 || len < 16 || len > 32) {
        return SecureString();
    }

    SecureVector checksum(32);
    CSHA256().Write(&data[0], len).Finalize(&checksum[0]);

    // data
    SecureVector bits(len);
    memcpy(&bits[0], &data[0], len);
    // checksum
    bits.push_back(checksum[0]);

    int mlen = len * 3 / 4;
    SecureString mnemonic;

    int i, j, idx;
    for (i = 0; i < mlen; i++) {
        idx = 0;
        for (j = 0; j < 11; j++) {
            idx <<= 1;
            idx += (bits[(i * 11 + j) / 8] & (1 << (7 - ((i * 11 + j) % 8)))) > 0;
        }
        mnemonic.append(refWordList[idx]);
        if (i < mlen - 1) {
            mnemonic += ' ';
        }
    }

    refWordList = nullptr; //reset
    return mnemonic;
}

void CMnemonic::getWordList(const char* const* &input, Language selectLanguage) {

    switch (selectLanguage) {
        case Language::ENGLISH:
            input = wordlist;
            break;
        case Language::FRENCH:
            input = wordlist_french;
            break;
        case Language::CHINESE_SIMPLIFIED:
            input = wordlist_chinese_simplified;
            break;
        case Language::CHINESE_TRADITIONAL:
            input = wordlist_chinese_traditional;
            break;
        //exclude for now but include later
        // case Language::GERMAN:
        //     input = wordlist_german;
        //     break;
        case Language::ITALIAN:
            input = wordlist_italian;
            break;
        case Language::JAPANESE:
            input = wordlist_japanese;
            break;
        case Language::KOREAN:
            input = wordlist_korean;
            break;
        case Language::RUSSIAN:
            input = wordlist_russian;
            break;
        case Language::SPANISH:
            input = wordlist_spanish;
            break;
        case Language::UKRAINIAN:
            input = wordlist_ukrainian;
            break;
        default:
            input = wordlist;
            break;

    } //switch

}

CMnemonic::Language CMnemonic::getLanguageEnumFromLabel(const std::string &input) {

    if (boost::algorithm::to_lower_copy(input) == "english") return CMnemonic::Language::ENGLISH;
    else if (boost::algorithm::to_lower_copy(input) == "french") return CMnemonic::Language::FRENCH;
    else if (boost::algorithm::to_lower_copy(input) == "chinesesimplified") return CMnemonic::Language::CHINESE_SIMPLIFIED;
    else if (boost::algorithm::to_lower_copy(input) == "chinesetraditional") return CMnemonic::Language::CHINESE_TRADITIONAL;
    //exclude for now but include later
    //else if (boost::algorithm::to_lower_copy(input) == "german") return CMnemonic::Language::GERMAN;
    else if (boost::algorithm::to_lower_copy(input) == "italian") return CMnemonic::Language::ITALIAN;
    else if (boost::algorithm::to_lower_copy(input) == "japanese") return CMnemonic::Language::JAPANESE;
    else if (boost::algorithm::to_lower_copy(input) == "korean") return CMnemonic::Language::KOREAN;
    else if (boost::algorithm::to_lower_copy(input) == "russian") return CMnemonic::Language::RUSSIAN;
    else if (boost::algorithm::to_lower_copy(input) == "spanish") return CMnemonic::Language::SPANISH;
    else if (boost::algorithm::to_lower_copy(input) == "ukrainian") return CMnemonic::Language::UKRAINIAN;
    else return CMnemonic::Language::ENGLISH;

} //getLanguageEnumFromLabel

bool CMnemonic::Check(SecureString mnemonic, Language selectLanguage)
{
    const char* const* refWordList = nullptr; //initialize

    getWordList(refWordList,selectLanguage);

    if (mnemonic.empty()) {
        return false;
    }

    uint32_t nWordCount{};

    for (size_t i = 0; i < mnemonic.size(); ++i) {
        if (mnemonic[i] == ' ') {
            nWordCount++;
        }
    }
    nWordCount++;
    // check number of words
    if (nWordCount != 12 && nWordCount != 18 && nWordCount != 24) {
        return false;
    }

    SecureString ssCurrentWord;
    SecureVector bits(32 + 1);

    uint32_t nWordIndex, ki, nBitsCount{};

    for (size_t i = 0; i < mnemonic.size(); ++i) {
        ssCurrentWord = "";
        while (i + ssCurrentWord.size() < mnemonic.size() && mnemonic[i + ssCurrentWord.size()] != ' ') {
            // if (ssCurrentWord.size() >= 26) { //was 9
            //     return false;
            // }
            ssCurrentWord += mnemonic[i + ssCurrentWord.size()];
        }
        //moved to get count AFTER space
        std::string teststring = "";
        teststring = ssCurrentWord.c_str();
        int characterSize = mbstowcs(NULL,teststring.c_str(), teststring.size());
        if (characterSize >= 25) { //was 9
            return false;
        }
        i += ssCurrentWord.size();
        nWordIndex = 0;
        for (;;) {
            if (!refWordList[nWordIndex]) { // word not found
                return false;
            }
            if (ssCurrentWord == refWordList[nWordIndex]) { // word found on index nWordIndex
                for (ki = 0; ki < 11; ki++) {
                    if (nWordIndex & (1 << (10 - ki))) {
                        bits[nBitsCount / 8] |= 1 << (7 - (nBitsCount % 8));
                    }
                    nBitsCount++;
                }
                break;
            }
            nWordIndex++;
        }
    }

    refWordList = nullptr; //reset

    if (nBitsCount != nWordCount * 11) {
        return false;
    }
    bits[32] = bits[nWordCount * 4 / 3];
    CSHA256().Write(&bits[0], nWordCount * 4 / 3).Finalize(&bits[0]);

    bool fResult = 0;
    if (nWordCount == 12) {
        fResult = (bits[0] & 0xF0) == (bits[32] & 0xF0); // compare first 4 bits
    } else if (nWordCount == 18) {
        fResult = (bits[0] & 0xFC) == (bits[32] & 0xFC); // compare first 6 bits
    } else if (nWordCount == 24) {
        fResult = bits[0] == bits[32]; // compare 8 bits
    }

    return fResult;
}

// passphrase must be at most 256 characters or code may crash
void CMnemonic::ToSeed(SecureString mnemonic, SecureString passphrase, SecureVector& seedRet)
{
    SecureString ssSalt = SecureString("mnemonic") + passphrase;
    SecureVector vchSalt(ssSalt.begin(), ssSalt.end());
    seedRet.resize(64);
    // int PKCS5_PBKDF2_HMAC(const char *pass, int passlen,
    //                    const unsigned char *salt, int saltlen, int iter,
    //                    const EVP_MD *digest,
    //                    int keylen, unsigned char *out);
    PKCS5_PBKDF2_HMAC(mnemonic.c_str(), mnemonic.size(), &vchSalt[0], vchSalt.size(), 2048, EVP_sha512(), 64, &seedRet[0]);
}


#include "dgk.h"
#include "SMP/Matrix.hpp"
#include "SMP/Timer.hpp"
#include "SMP/network/ConcurrentQueue.hpp"
#include "HElib/NumbTh.h"
#include "SMP/literal.hpp"
#include <NTL/matrix.h>
#include <NTL/ZZ.h>
#include <boost/asio.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <iostream>
#include <numeric>
#include <list>
#include <thread>
using boost::asio::ip::tcp;

void get_rand_file( void* buf, int len, char* file )
{
	FILE* fp;
	void* p;

	fp = fopen(file, "r");

	p = buf;
	while( len )
	{
		size_t s;
		s = fread(p, 1, len, fp);
		p += s;
		len -= s;
	}

	fclose(fp);
}

void get_rand_devrandom( void* buf, int len )
{
	get_rand_file(buf, len, "/dev/random");
}

void get_rand_devurandom( void* buf, int len )
{
	get_rand_file(buf, len, "/dev/urandom");
}

using get_rand_t = void (*)( void* buf, int len );

void init_rand( gmp_randstate_t rand, get_rand_t get_rand, int bytes )
{
	void* buf;
	mpz_t s;

	buf = malloc(bytes);
	get_rand(buf, bytes);

	gmp_randinit_default(rand);
	mpz_init(s);
	mpz_import(s, bytes, 1, 1, 0, 0, buf);
	gmp_randseed(rand, s);
	mpz_clear(s);

	free(buf);
}

using EncMatrix = std::vector<mpz_t>;

void randomize_matrix(Matrix &mat)
{
	constexpr long domain = (1 << 16);
	for (long r = 0; r < mat.NumRows(); r++) {
		for (long c = 0; c < mat.NumCols(); c++)
			mat[r][c] = NTL::RandomBnd(domain);
	}
}

void encrypt_matrix(EncMatrix &dst, Matrix const& src, dgk_pubkey_t const* pk)
{
	int rows = src.NumRows();
	int cols = src.NumCols();
	if (dst.size() != rows * cols)
		return;
	gmp_randstate_t gmp_rand;
    init_rand(gmp_rand, get_rand_devurandom, pk->bits / 8 + 1);
	mpz_t pt;
	mpz_init(pt);
	for (long r = 0; r < rows; r++) {
		int offset = r * cols;
		for (long c = 0; c < cols; c++) {
			mpz_set_ui(pt, src[r][c]);
			mpz_init(dst[offset + c]);
			dgk_encrypt_plain(dst[offset + c], pk, pt, gmp_rand);
		}
	}
	mpz_clear(pt);
}

void play_server(std::iostream &conn, long n1, long n2, long n3);
void play_client(std::iostream &conn, dgk_prvkey_t *sk, dgk_pubkey_t *pk, long n1, long n2, long n3);

void run_server(long port, long n1, long n2, long n3)
{
    using connect_t = std::unique_ptr<tcp::iostream>;
    Queue<connect_t> queue;

    auto prgm = [&queue](long n1, long n2, long n3) {
        for (;;) {
            connect_t conn = std::move(queue.pop());
            if (!conn)
                continue;
            play_server(*conn, n1, n2, n3);
            conn->close();
        }
    };

    std::vector<std::thread> workers;
    for (int w = 0; w < 30; ++w) { // 30 workers
        workers.emplace_back(prgm, n1, n2, n3);
    }

    boost::asio::io_service ios;
    tcp::endpoint endpoint(tcp::v4(), port);
    tcp::acceptor acceptor(ios, endpoint);
    for (;;) {
        boost::system::error_code err;
        tcp::iostream *conn= new tcp::iostream();
        acceptor.accept(*(conn->rdbuf()), err);
        if (!err) {
            connect_t _conn(conn);
            queue.push(std::move(_conn));
        } else {
            delete conn;
        }
    }
}

int run_client(std::string const& addr, long port, long n1, long n2, long n3) {
	dgk_prvkey_t *sk;
	dgk_pubkey_t *pk;
	dgk_keygen(1024, 16, &pk, &sk); // 16-bit plaintexts
    tcp::iostream conn(addr, std::to_string(port));
    if (!conn) {
        std::cerr << "Can not connect to server!" << std::endl;
        return -1;
    }
    NetworkLog::bytes_sent = 0;
    NetworkLog::bytes_recev = 0;
    double all_time = 0.;
    do {
        AutoTimer time(&all_time);
        play_client(conn, sk, pk, n1, n2, n3);
    } while(0);
    printf("%.3f\n", all_time);
    conn.close();
	dgk_freeprvkey(sk);
	dgk_freepubkey(pk);
    return 1;
}

int main(int argc, char *argv[]) {
	ArgMapping argmap;
    long role = -1;
    long n1 = 8;
    long n2 = 8;
    long n3 = 8;
    std::string addr = "127.0.0.1";
	long port = 12345;
    argmap.arg("N", n1, "n1");
    argmap.arg("M", n2, "n2");
    argmap.arg("D", n3, "n3");
    argmap.arg("R", role, "role. 0 for server and 1 for client");
	argmap.arg("a", addr, "server address");
	argmap.arg("p", port, "port");
    argmap.parse(argc, argv);
	if (role == 0) {
        run_server(port, n1, n2, n3);
    } else if (role == 1) {
        run_client(addr, port, n1, n2, n3);
    } else {
		argmap.usage("Secure Matrix Multiplication for |N*M| * |M*D|");
		return -1;
	}

	return 0;
}

void play_server(std::iostream &conn, long n1, long n2, long n3)
{
	Matrix A, B;
	NTL::SetSeed(NTL::to_ZZ(123)); // for debugging
	A.SetDims(n1, n2);
	B.SetDims(n2, n3);
	randomize_matrix(A);
	randomize_matrix(B);

	std::vector<uint32_t> buffer(2048 >> 5);
	dgk_pubkey_t pk;
	receive_pk(&pk, buffer, conn);
	gmp_randstate_t gmp_rand;
    init_rand(gmp_rand, get_rand_devurandom, pk.bits / 8 + 1);

    EncMatrix enc_A(n1 * n2);
	for (long r = 0; r < n1; r++) {
		long offset = r * n2;
		for (long c = 0; c < n2; c++) {
			mpz_init(enc_A.at(offset + c));
			receive_mpz(enc_A[offset + c], buffer, conn);
		}
	}

	mpz_t tmp, zero;
	mpz_init(tmp);
	mpz_init(zero);
	mpz_set_ui(zero, 0);

	for (long r = 0; r < n1; r++) {
		long offset = r * n2;
		for (long c = 0; c < n3; c++) {
			mpz_t res;
			mpz_init(res);
			dgk_encrypt_plain(res, &pk, zero, gmp_rand);
			for (long k = 0; k < n2; k++) {
				dgk_hom_mult(tmp, enc_A.at(offset + k), B[k][c], &pk);
				dgk_hom_add(res, res, tmp, &pk);
			}
			send_mpz(res, buffer, conn);
		}
	}
	mpz_clear(tmp);
}

void play_client(std::iostream &conn, dgk_prvkey_t *sk, dgk_pubkey_t *pk, long n1, long n2, long n3)
{
	Matrix A, B;
	NTL::SetSeed(NTL::to_ZZ(123)); // for debugging
	A.SetDims(n1, n2);
	B.SetDims(n2, n3);
	randomize_matrix(A);
	randomize_matrix(B);

	std::vector<uint32_t> buffer(2048 >> 5);
	send_pk(pk, buffer, conn);

	EncMatrix enc_A(n1 * n2);
	encrypt_matrix(enc_A, A, pk);
	for (long r = 0; r < n1; r++) {
		int offset = r * n2;
		for (long c = 0; c < n2; c++) {
			send_mpz(enc_A.at(offset + c), buffer, conn);
		}
	}

	mpz_t plt, ctx;
	mpz_init(plt);
	mpz_init(ctx);
	for (long r = 0; r < n1; r++) {
		for (long c = 0; c < n3; c++) {
			receive_mpz(ctx, buffer, conn);
			dgk_decrypt(plt, pk, sk, ctx);
		}
	}
	mpz_clear(plt);
	mpz_clear(ctx);
}