/*

   Copyright (c) 2006-2010, The Scripps Research Institute

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

   Author: Dr. Oleg Trott <ot14@columbia.edu>,
           The Olson Lab,
           The Scripps Research Institute

*/

#include <boost/filesystem/convenience.hpp>  // filesystem::basename
#include <boost/filesystem/exception.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/program_options.hpp>
#include <boost/thread/thread.hpp>  // hardware_concurrency // FIXME rm ?
#include <cmath>                    // for ceila
#include <exception>
#include <iostream>
#include <string>
#include <vector>  // ligand paths

#include "lib/cache.h"
#include "lib/coords.h"  // add_to_output_container
#include "lib/current_weights.h"
#include "lib/everything.h"
#include "lib/file.h"
#include "lib/naive_non_cache.h"
#include "lib/non_cache.h"
#include "lib/parallel_mc.h"
#include "lib/parse_error.h"
#include "lib/parse_pdbqt.h"
#include "lib/quasi_newton.h"
#include "lib/tee.h"
#include "lib/weighted_terms.h"

using boost::filesystem::path;

path make_path(const std::string& str) { return boost::filesystem::path(str); }

void doing(int verbosity, const std::string& str, tee& log) {
  if (verbosity > 1) {
    log << str << std::string(" ... ");
    log.flush();
  }
}

void done(int verbosity, tee& log) {
  if (verbosity > 1) {
    log << "done.";
    log.endl();
  }
}
std::string default_output(const std::string& input_name) {
  std::string tmp = input_name;
  if (tmp.size() >= 6 && tmp.substr(tmp.size() - 6, 6) == ".pdbqt")
    tmp.resize(tmp.size() - 6);  // FIXME?
  return tmp + "_out.pdbqt";
}

void write_all_output(model& m, const output_container& out, sz how_many,
                      const std::string& output_name,
                      const std::vector<std::string>& remarks) {
  if (out.size() < how_many) how_many = out.size();
  VINA_CHECK(how_many <= remarks.size());
  ofile f(make_path(output_name));
  VINA_FOR(i, how_many) {
    m.set(out[i].c);
    m.write_model(f, i + 1, remarks[i]);  // so that model numbers start with 1
  }
}

void do_randomization(model& m, const std::string& out_name, const vec& corner1,
                      const vec& corner2, int seed, int verbosity, tee& log) {
  conf init_conf = m.get_initial_conf();
  rng generator(static_cast<rng::result_type>(seed));
  if (verbosity > 1) {
    log << "Using random seed: " << seed;
    log.endl();
  }
  const sz attempts = 10000;
  conf best_conf = init_conf;
  fl best_clash_penalty = 0;
  VINA_FOR(i, attempts) {
    conf c = init_conf;
    c.randomize(corner1, corner2, generator);
    m.set(c);
    fl penalty = m.clash_penalty();
    if (i == 0 || penalty < best_clash_penalty) {
      best_conf = c;
      best_clash_penalty = penalty;
    }
  }
  m.set(best_conf);
  if (verbosity > 1) {
    log << "Clash penalty: " << best_clash_penalty;  // FIXME rm?
    log.endl();
  }
  m.write_structure(make_path(out_name));
}

void refine_structure(model& m, const precalculate& prec, non_cache& nc,
                      output_type& out, const vec& cap, sz max_steps = 1000) {
  change g(m.get_size());
  quasi_newton quasi_newton_par;
  quasi_newton_par.max_steps = max_steps;
  const fl slope_orig = nc.slope;
  VINA_FOR(p, 5) {
    nc.slope = 100 * std::pow(10.0, 2.0 * p);
    quasi_newton_par(m, prec, nc, out, g, cap);
    m.set(out.c);  // just to be sure
    if (nc.within(m)) break;
  }
  out.coords = m.get_heavy_atom_movable_coords();
  if (!nc.within(m)) out.e = max_fl;
  nc.slope = slope_orig;
}

std::string vina_remark(fl e, fl lb, fl ub) {
  std::ostringstream remark;
  remark.setf(std::ios::fixed, std::ios::floatfield);
  remark.setf(std::ios::showpoint);
  remark << "REMARK VINA RESULT: " << std::setw(9) << std::setprecision(1) << e
         << "  " << std::setw(9) << std::setprecision(3) << lb << "  "
         << std::setw(9) << std::setprecision(3) << ub << '\n';
  return remark.str();
}

output_container remove_redundant(const output_container& in, fl min_rmsd) {
  output_container tmp;
  VINA_FOR_IN(i, in)
  add_to_output_container(tmp, in[i], min_rmsd, in.size());
  return tmp;
}

void do_search(model& m, const boost::optional<model>& ref,
               const scoring_function& sf, const precalculate& prec,
               const igrid& ig, const precalculate& prec_widened,
               const igrid& ig_widened, non_cache& nc,  // nc.slope is changed
               const std::string& out_name, const vec& corner1,
               const vec& corner2, const parallel_mc& par, fl energy_range,
               sz num_modes, int seed, int verbosity, bool score_only,
               bool local_only, tee& log, const terms& t, const flv& weights) {
  conf_size s = m.get_size();
  conf c = m.get_initial_conf();
  fl e = max_fl;
  const vec authentic_v(1000, 1000, 1000);
  if (score_only) {
    fl intramolecular_energy = m.eval_intramolecular(prec, authentic_v, c);
    naive_non_cache nnc(&prec);  // for out of grid issues
    e = m.eval_adjusted(sf, prec, nnc, authentic_v, c, intramolecular_energy);
    log << "Affinity: " << std::fixed << std::setprecision(5) << e
        << " (kcal/mol)";
    log.endl();
    flv term_values = t.evale_robust(m);
    VINA_CHECK(term_values.size() == 5);
    log << "Intermolecular contributions to the terms, before weighting:\n";
    log << std::setprecision(5);
    log << "    gauss 1     : " << term_values[0] << '\n';
    log << "    gauss 2     : " << term_values[1] << '\n';
    log << "    repulsion   : " << term_values[2] << '\n';
    log << "    hydrophobic : " << term_values[3] << '\n';
    log << "    Hydrogen    : " << term_values[4] << '\n';
    VINA_CHECK(weights.size() == term_values.size() + 1);
    fl e2 = 0;
    VINA_FOR_IN(i, term_values)
    e2 += term_values[i] * weights[i];
    e2 = sf.conf_independent(m, e2);
    if (e < 100 && std::abs(e2 - e) > 0.05) {
      log << "WARNING: the individual terms are inconsisent with the\n";
      log << "WARNING: affinity. Consider reporting this as a bug:\n";
      log << "WARNING: http://vina.scripps.edu/manual.html#bugs\n";
    }
  } else if (local_only) {
    output_type out(c, e);
    doing(verbosity, "Performing local search", log);
    refine_structure(m, prec, nc, out, authentic_v, par.mc.ssd_par.evals);
    done(verbosity, log);
    fl intramolecular_energy = m.eval_intramolecular(prec, authentic_v, out.c);
    e = m.eval_adjusted(sf, prec, nc, authentic_v, out.c,
                        intramolecular_energy);

    log << "Affinity: " << std::fixed << std::setprecision(5) << e
        << " (kcal/mol)";
    log.endl();
    if (!nc.within(m))
      log << "WARNING: not all movable atoms are within the search space\n";

    doing(verbosity, "Writing output", log);
    output_container out_cont;
    out_cont.push_back(new output_type(out));
    std::vector<std::string> remarks(1, vina_remark(e, 0, 0));
    write_all_output(m, out_cont, 1, out_name, remarks);  // how_many == 1
    done(verbosity, log);
  } else {
    rng generator(static_cast<rng::result_type>(seed));
    log << "Using random seed: " << seed;
    log.endl();
    output_container out_cont;
    doing(verbosity, "Performing search", log);
    par(m, out_cont, prec, ig, prec_widened, ig_widened, corner1, corner2,
        generator);
    done(verbosity, log);

    doing(verbosity, "Refining results", log);
    VINA_FOR_IN(i, out_cont)
    refine_structure(m, prec, nc, out_cont[i], authentic_v,
                     par.mc.ssd_par.evals);

    if (!out_cont.empty()) {
      out_cont.sort();
      const fl best_mode_intramolecular_energy =
          m.eval_intramolecular(prec, authentic_v, out_cont[0].c);
      VINA_FOR_IN(i, out_cont)
      if (not_max(out_cont[i].e))
        out_cont[i].e =
            m.eval_adjusted(sf, prec, nc, authentic_v, out_cont[i].c,
                            best_mode_intramolecular_energy);
      // the order must not change because of non-decreasing g (see paper), but
      // we'll re-sort in case g is non strictly increasing
      out_cont.sort();
    }

    const fl out_min_rmsd = 1;
    out_cont = remove_redundant(out_cont, out_min_rmsd);

    done(verbosity, log);

    log.setf(std::ios::fixed, std::ios::floatfield);
    log.setf(std::ios::showpoint);
    log << '\n';
    log << "mode |   affinity | dist from best mode\n";
    log << "     | (kcal/mol) | rmsd l.b.| rmsd u.b.\n";
    log << "-----+------------+----------+----------\n";

    model best_mode_model = m;
    if (!out_cont.empty()) best_mode_model.set(out_cont.front().c);

    sz how_many = 0;
    std::vector<std::string> remarks;
    VINA_FOR_IN(i, out_cont) {
      if (how_many >= num_modes || !not_max(out_cont[i].e) ||
          out_cont[i].e > out_cont[0].e + energy_range)
        break;  // check energy_range sanity FIXME
      ++how_many;
      log << std::setw(4) << i + 1 << "    " << std::setw(9)
          << std::setprecision(1)
          << out_cont[i].e;  // intermolecular_energies[i];
      m.set(out_cont[i].c);
      const model& r = ref ? ref.get() : best_mode_model;
      const fl lb = m.rmsd_lower_bound(r);
      const fl ub = m.rmsd_upper_bound(r);
      log << "  " << std::setw(9) << std::setprecision(3) << lb << "  "
          << std::setw(9) << std::setprecision(3)
          << ub;  // FIXME need user-readable error messages in case of failures

      remarks.push_back(vina_remark(out_cont[i].e, lb, ub));
      log.endl();
    }
    doing(verbosity, "Writing output", log);
    write_all_output(m, out_cont, how_many, out_name, remarks);
    done(verbosity, log);

    if (how_many < 1) {
      log << "WARNING: Could not find any conformations completely within the "
             "search space.\n"
          << "WARNING: Check that it is large enough for all movable atoms, "
             "including those in the flexible side chains.";
      log.endl();
    }
  }
}

void main_procedure(
    model& m, const boost::optional<model>& ref,  // m is non-const (FIXME?)
    const std::string& out_name, bool score_only, bool local_only,
    bool randomize_only, bool no_cache, const grid_dims& gd, int exhaustiveness,
    const flv& weights, int cpu, int seed, int verbosity, sz num_modes,
    fl energy_range, tee& log) {
  doing(verbosity, "Setting up the scoring function", log);

  everything t;
  VINA_CHECK(weights.size() == 6);

  weighted_terms wt(&t, weights);
  precalculate prec(wt);
  const fl left = 0.25;
  const fl right = 0.25;
  precalculate prec_widened(prec);
  prec_widened.widen(left, right);

  done(verbosity, log);

  vec corner1(gd[0].begin, gd[1].begin, gd[2].begin);
  vec corner2(gd[0].end, gd[1].end, gd[2].end);

  parallel_mc par;
  sz heuristic =
      m.num_movable_atoms() + 10 * m.get_size().num_degrees_of_freedom();
  par.mc.num_steps =
      unsigned(70 * 3 * (50 + heuristic) / 2);  // 2 * 70 -> 8 * 20 // FIXME
  par.mc.ssd_par.evals = unsigned((25 + m.num_movable_atoms()) / 3);
  par.mc.min_rmsd = 1.0;
  par.mc.num_saved_mins = 20;
  par.mc.hunt_cap = vec(10, 10, 10);
  par.num_tasks = exhaustiveness;
  par.num_threads = cpu;
  par.display_progress = (verbosity > 1);

  const fl slope = 1e6;  // FIXME: too large? used to be 100
  if (randomize_only) {
    do_randomization(m, out_name, corner1, corner2, seed, verbosity, log);
  } else {
    non_cache nc(m, gd, &prec,
                 slope);  // if gd has 0 n's, this will not constrain anything
    non_cache nc_widened(
        m, gd, &prec_widened,
        slope);  // if gd has 0 n's, this will not constrain anything
    if (no_cache) {
      do_search(m, ref, wt, prec, nc, prec_widened, nc_widened, nc, out_name,
                corner1, corner2, par, energy_range, num_modes, seed, verbosity,
                score_only, local_only, log, t, weights);
    } else {
      bool cache_needed = !(score_only || randomize_only || local_only);
      if (cache_needed) doing(verbosity, "Analyzing the binding site", log);
      cache c("scoring_function_version001", gd, slope, atom_type::XS);
      if (cache_needed)
        c.populate(m, prec, m.get_movable_atom_types(prec.atom_typing_used()));
      if (cache_needed) done(verbosity, log);
      do_search(m, ref, wt, prec, c, prec, c, nc, out_name, corner1, corner2,
                par, energy_range, num_modes, seed, verbosity, score_only,
                local_only, log, t, weights);
    }
  }
}

struct usage_error : public std::runtime_error {
  usage_error(const std::string& message) : std::runtime_error(message) {}
};

struct options_occurrence {
  bool some;
  bool all;
  options_occurrence() : some(false), all(true) {}  // convenience
  options_occurrence& operator+=(const options_occurrence& x) {
    some = some || x.some;
    all = all && x.all;
    return *this;
  }
};

options_occurrence get_occurrence(
    boost::program_options::variables_map& vm,
    boost::program_options::options_description& d) {
  options_occurrence tmp;
  VINA_FOR_IN(i, d.options())
  if (vm.count((*d.options()[i]).long_name()))
    tmp.some = true;
  else
    tmp.all = false;
  return tmp;
}

void check_occurrence(boost::program_options::variables_map& vm,
                      boost::program_options::options_description& d) {
  VINA_FOR_IN(i, d.options()) {
    const std::string& str = (*d.options()[i]).long_name();
    if (!vm.count(str))
      std::cerr << "Required parameter --" << str << " is missing!\n";
  }
}

model parse_bundle(const std::string& rigid_name,
                   const boost::optional<std::string>& flex_name_opt,
                   const std::vector<std::string>& ligand_names) {
  model tmp = (flex_name_opt)
                  ? parse_receptor_pdbqt(make_path(rigid_name),
                                         make_path(flex_name_opt.get()))
                  : parse_receptor_pdbqt(make_path(rigid_name));
  VINA_FOR_IN(i, ligand_names)
  tmp.append(parse_ligand_pdbqt(make_path(ligand_names[i])));
  return tmp;
}

model parse_bundle(const std::vector<std::string>& ligand_names) {
  VINA_CHECK(!ligand_names.empty());  // FIXME check elsewhere
  model tmp = parse_ligand_pdbqt(make_path(ligand_names[0]));
  VINA_RANGE(i, 1, ligand_names.size())
  tmp.append(parse_ligand_pdbqt(make_path(ligand_names[i])));
  return tmp;
}

model parse_bundle(const boost::optional<std::string>& rigid_name_opt,
                   const boost::optional<std::string>& flex_name_opt,
                   const std::vector<std::string>& ligand_names) {
  if (rigid_name_opt)
    return parse_bundle(rigid_name_opt.get(), flex_name_opt, ligand_names);
  else
    return parse_bundle(ligand_names);
}

int main(int argc, char* argv[]) {
  using namespace boost::program_options;
  const std::string version_string = "AutoDock Vina 1.1.2 (May 11, 2011)";
  const std::string error_message = "\nThank you!\n";
  const std::string cite_message = "AutoDock Vina";

  try {
    std::string rigid_name, ligand_name, flex_name, config_name, out_name,
        log_name;
    fl center_x, center_y, center_z, size_x, size_y, size_z;
    int cpu = 0, seed, exhaustiveness, verbosity = 2, num_modes = 9;
    fl energy_range = 2.0;

    // -0.035579, -0.005156, 0.840245, -0.035069, -0.587439, 0.05846
    fl weight_gauss1 = -0.035579;
    fl weight_gauss2 = -0.005156;
    fl weight_repulsion = 0.840245;
    fl weight_hydrophobic = -0.035069;
    fl weight_hydrogen = -0.587439;
    fl weight_rot = 0.05846;
    bool score_only = false, local_only = false, randomize_only = false,
         help = false, help_advanced = false, version = false;  // FIXME

    positional_options_description positional;  // remains empty

    options_description inputs("Input");
    inputs.add_options()("receptor", value<std::string>(&rigid_name),
                         "rigid part of the receptor (PDBQT)")(
        "flex", value<std::string>(&flex_name),
        "flexible side chains, if any (PDBQT)")("ligand",
                                                value<std::string>(
                                                    &ligand_name),
                                                "ligand (PDBQT)");
    // options_description search_area("Search area (required, except with
    // --score_only)");
    options_description search_area("Search space (required)");
    search_area.add_options()
			("center_x", value<fl>(&center_x), "X coordinate of the center")
			("center_y", value<fl>(&center_y), "Y coordinate of the center")
			("center_z", value<fl>(&center_z), "Z coordinate of the center")
			("size_x", value<fl>(&size_x), "size in the X dimension (Angstroms)")
			("size_y", value<fl>(&size_y), "size in the Y dimension (Angstroms)")
			("size_z", value<fl>(&size_z), "size in the Z dimension (Angstroms)")
		;
    // options_description outputs("Output prefixes (optional - by default,
    // input names are stripped of .pdbqt\nare used as prefixes. _001.pdbqt,
    // _002.pdbqt, etc. are appended to the prefixes to produce the output
    // names");
    options_description outputs("Output (optional)");
    outputs.add_options()("out", value<std::string>(&out_name),
                          "output models (PDBQT), the default is chosen based "
                          "on the ligand file name")(
        "log", value<std::string>(&log_name), "optionally, write log file");
    options_description advanced("Advanced options (see the manual)");
    advanced.add_options()
			("score_only",     bool_switch(&score_only),     "score only - search space can be omitted")
			("local_only",     bool_switch(&local_only),     "do local search only")
			("randomize_only", bool_switch(&randomize_only), "randomize input, attempting to avoid clashes")
			("weight_gauss1", value<fl>(&weight_gauss1)->default_value(weight_gauss1),                "gauss_1 weight")
			("weight_gauss2", value<fl>(&weight_gauss2)->default_value(weight_gauss2),                "gauss_2 weight")
			("weight_repulsion", value<fl>(&weight_repulsion)->default_value(weight_repulsion),       "repulsion weight")
			("weight_hydrophobic", value<fl>(&weight_hydrophobic)->default_value(weight_hydrophobic), "hydrophobic weight")
			("weight_hydrogen", value<fl>(&weight_hydrogen)->default_value(weight_hydrogen),          "Hydrogen bond weight")
			("weight_rot", value<fl>(&weight_rot)->default_value(weight_rot),                         "N_rot weight")
		;
    options_description misc("Misc (optional)");
    misc.add_options()
			("cpu", value<int>(&cpu), "the number of CPUs to use (the default is to try to detect the number of CPUs or, failing that, use 1)")
			("seed", value<int>(&seed), "explicit random seed")
			("exhaustiveness", value<int>(&exhaustiveness)->default_value(8), "exhaustiveness of the global search (roughly proportional to time): 1+")
			("num_modes", value<int>(&num_modes)->default_value(9), "maximum number of binding modes to generate")
			("energy_range", value<fl>(&energy_range)->default_value(3.0), "maximum energy difference between the best binding mode and the worst one displayed (kcal/mol)")
		;
    options_description config("Configuration file (optional)");
    config.add_options()("config", value<std::string>(&config_name),
                         "the above options can be put here");
    options_description info("Information (optional)");
    info.add_options()("help", bool_switch(&help), "display usage summary")(
        "help_advanced", bool_switch(&help_advanced),
        "display usage summary with advanced options")("version",
                                                       bool_switch(&version),
                                                       "display program "
                                                       "version");
    options_description desc, desc_config, desc_simple;
    desc.add(inputs)
        .add(search_area)
        .add(outputs)
        .add(advanced)
        .add(misc)
        .add(config)
        .add(info);
    desc_config.add(inputs)
        .add(search_area)
        .add(outputs)
        .add(advanced)
        .add(misc);
    desc_simple.add(inputs)
        .add(search_area)
        .add(outputs)
        .add(misc)
        .add(config)
        .add(info);

    variables_map vm;
    try {
      // store(parse_command_line(argc, argv, desc,
      // command_line_style::default_style ^ command_line_style::allow_guessing),
      // vm);
      store(command_line_parser(argc, argv)
                .options(desc)
                .style(command_line_style::default_style ^
                       command_line_style::allow_guessing)
                .positional(positional)
                .run(),
            vm);
      notify(vm);
    } catch (boost::program_options::error& e) {
      std::cerr << "Command line parse error: " << e.what() << '\n'
                << "\nCorrect usage:\n"
                << desc_simple << '\n';
      return 1;
    }
    if (vm.count("config")) {
      try {
        path name = make_path(config_name);
        ifile config_stream(name);
        store(parse_config_file(config_stream, desc_config), vm);
        notify(vm);
      } catch (boost::program_options::error& e) {
        std::cerr << "Configuration file parse error: " << e.what() << '\n'
                  << "\nCorrect usage:\n"
                  << desc_simple << '\n';
        return 1;
      }
    }
    if (help) {
      std::cout << desc_simple << '\n';
      return 0;
    }
    if (help_advanced) {
      std::cout << desc << '\n';
      return 0;
    }
    if (version) {
      std::cout << version_string << '\n';
      return 0;
    }

    bool search_box_needed = !score_only;  // randomize_only and local_only
                                           // still need the search space
    bool output_produced = !score_only;
    bool receptor_needed = !randomize_only;

    if (receptor_needed) {
      if (vm.count("receptor") <= 0) {
        std::cerr << "Missing receptor.\n"
                  << "\nCorrect usage:\n"
                  << desc_simple << '\n';
        return 1;
      }
    }
    if (vm.count("ligand") <= 0) {
      std::cerr << "Missing ligand.\n"
                << "\nCorrect usage:\n"
                << desc_simple << '\n';
      return 1;
    }
    if (cpu < 1) cpu = 1;
    if (vm.count("seed") == 0) seed = auto_seed();
    if (exhaustiveness < 1)
      throw usage_error("exhaustiveness must be 1 or greater");
    if (num_modes < 1) throw usage_error("num_modes must be 1 or greater");
    sz max_modes_sz = static_cast<sz>(num_modes);

    boost::optional<std::string> rigid_name_opt;
    if (vm.count("receptor")) rigid_name_opt = rigid_name;

    boost::optional<std::string> flex_name_opt;
    if (vm.count("flex")) flex_name_opt = flex_name;

    if (vm.count("flex") && !vm.count("receptor"))
      throw usage_error(
          "Flexible side chains are not allowed without the rest of the "
          "receptor");  // that's the only way parsing works, actually

    tee log;
    if (vm.count("log") > 0) log.init(log_name);

    if (search_box_needed) {
      options_occurrence oo = get_occurrence(vm, search_area);
      if (!oo.all) {
        check_occurrence(vm, search_area);
        std::cerr << "\nCorrect usage:\n" << desc_simple << std::endl;
        return 1;
      }
      if (size_x <= 0 || size_y <= 0 || size_z <= 0)
        throw usage_error("Search space dimensions should be positive");
    }

    log << cite_message << '\n';

    if (search_box_needed && size_x * size_y * size_z > 27e3) {
      log << "WARNING: The search space volume > 27000 Angstrom^3 (See FAQ)\n";
    }

    if (output_produced) {  // FIXME
      if (!vm.count("out")) {
        out_name = default_output(ligand_name);
        log << "Output will be " << out_name << '\n';
      }
    }

    grid_dims gd;  // n's = 0 via default c'tor

    flv weights;
    weights.push_back(weight_gauss1);
    weights.push_back(weight_gauss2);
    weights.push_back(weight_repulsion);
    weights.push_back(weight_hydrophobic);
    weights.push_back(weight_hydrogen);
    weights.push_back(5 * weight_rot / 0.1 -
                      1);  // linearly maps onto a different range, internally.
                           // see everything.cpp

    if (search_box_needed) {
      const fl granularity = 0.375;
      vec span(size_x, size_y, size_z);
      vec center(center_x, center_y, center_z);
      VINA_FOR_IN(i, gd) {
        gd[i].n = sz(std::ceil(span[i] / granularity));
        fl real_span = granularity * gd[i].n;
        gd[i].begin = center[i] - real_span / 2;
        gd[i].end = gd[i].begin + real_span;
      }
    }
    if (vm.count("cpu") == 0) {
      unsigned num_cpus = boost::thread::hardware_concurrency();
      if (verbosity > 1) {
        if (num_cpus > 0)
          log << "Detected " << num_cpus << " CPU"
              << ((num_cpus > 1) ? "s" : "") << '\n';
        else
          log << "Could not detect the number of CPUs, using 1\n";
      }
      if (num_cpus > 0)
        cpu = num_cpus;
      else
        cpu = 1;
    }
    if (cpu < 1) cpu = 1;
    if (verbosity > 1 && exhaustiveness < cpu)
      log << "WARNING: at low exhaustiveness, it may be impossible to utilize "
             "all CPUs\n";

    doing(verbosity, "Reading input", log);

    model m = parse_bundle(rigid_name_opt, flex_name_opt,
                           std::vector<std::string>(1, ligand_name));

    boost::optional<model> ref;
    done(verbosity, log);

    main_procedure(m, ref, out_name, score_only, local_only, randomize_only,
                   false,  // no_cache == false
                   gd, exhaustiveness, weights, cpu, seed, verbosity,
                   max_modes_sz, energy_range, log);
  } catch (file_error& e) {
    std::cerr << "\n\nError: could not open \"" << e.name.filename()
              << "\" for " << (e.in ? "reading" : "writing") << ".\n";
    return 1;
  } catch (boost::filesystem::filesystem_error& e) {
    std::cerr << "\n\nFile system error: " << e.what() << '\n';
    return 1;
  } catch (usage_error& e) {
    std::cerr << "\n\nUsage error: " << e.what() << ".\n";
    return 1;
  } catch (parse_error& e) {
    std::cerr << "\n\nParse error on line " << e.line << " in file \""
              << e.file.filename() << "\": " << e.reason << '\n';
    return 1;
  } catch (std::bad_alloc&) {
    std::cerr << "\n\nError: insufficient memory!\n";
    return 1;
  }

  // Errors that shouldn't happen:

  catch (std::exception& e) {
    std::cerr << "\n\nAn error occurred: " << e.what() << ". " << error_message;
    return 1;
  } catch (internal_error& e) {
    std::cerr << "\n\nAn internal error occurred in " << e.file << "(" << e.line
              << "). " << error_message;
    return 1;
  } catch (...) {
    std::cerr << "\n\nAn unknown error occurred. " << error_message;
    return 1;
  }
}


/*******************************************************************************
 *
 * Construction and management of weak topological orderings (WTOs).
 *
 * The construction of weak topological orderings is based on F. Bourdoncle's
 * paper: "Efficient chaotic iteration strategies with widenings", Formal
 * Methods in Programming and Their Applications, 1993, pages 128-141.
 *
 * Author: Arnaud J. Venet (arnaud.j.venet@nasa.gov)
 * Contributors: Jorge A. Navas (jorge.navas@sri.com)
 *
 * Notices:
 *
 * Copyright (c) 2011 United States Government as represented by the
 * Administrator of the National Aeronautics and Space Administration.
 * All Rights Reserved.
 *
 * Disclaimers:
 *
 * No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF
 * ANY KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED
 * TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS,
 * ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL BE
 * ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED, WILL CONFORM TO
 * THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN
 * ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS,
 * RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS
 * RESULTING FROM USE OF THE SUBJECT SOFTWARE.  FURTHER, GOVERNMENT AGENCY
 * DISCLAIMS ALL WARRANTIES AND LIABILITIES REGARDING THIRD-PARTY SOFTWARE,
 * IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT "AS IS."
 *
 * Waiver and Indemnity:  RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST
 * THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL
 * AS ANY PRIOR RECIPIENT.  IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS
 * IN ANY LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARISING FROM SUCH
 * USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING FROM,
 * RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD
 * HARMLESS THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS,
 * AS WELL AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW.
 * RECIPIENT'S SOLE REMEDY FOR ANY SUCH MATTER SHALL BE THE IMMEDIATE,
 * UNILATERAL TERMINATION OF THIS AGREEMENT.
 *
 ******************************************************************************/

#pragma once

#include <memory>
#include <set>
#include <vector>
#include <forward_list>
#include <unordered_map>

#include <boost/iterator/indirect_iterator.hpp>

#include "crab/cfg_bgl.hpp"
#include "crab/debug.hpp"
#include "crab/interval.hpp"
#include "crab/stats.hpp"
#include "crab/types.hpp"

namespace crab {

using vertex_descriptor_t = typename boost::graph_traits<cfg_t>::vertex_descriptor;

using out_edge_iterator_t = typename boost::graph_traits<cfg_t>::out_edge_iterator;

using edge_descriptor_t = typename boost::graph_traits<cfg_t>::edge_descriptor;

class wto_t;
class wto_vertex_t;
class wto_cycle_t;
class wto_component_visitor_t;

class wto_nesting_t final {

    friend class wto_t;
    friend class wto_vertex_t;
    friend class wto_cycle_t;

  private:
    using node_list_t = std::vector<vertex_descriptor_t>;
    using node_list_ptr = std::shared_ptr<node_list_t>;

    node_list_ptr _nodes;

  public:
    using iterator = typename node_list_t::iterator;
    using const_iterator = typename node_list_t::const_iterator;

  private:
    explicit wto_nesting_t(const node_list_ptr& l) : _nodes(std::make_shared<node_list_t>(*l)) {}

    int compare(wto_nesting_t& other) const {
        auto this_it = this->begin();
        auto other_it = other.begin();
        while (this_it != this->end()) {
            if (other_it == other.end()) {
                return 1;
            } else if (*this_it == *other_it) {
                ++this_it;
                ++other_it;
            } else {
                return 2; // Nestings are not comparable
            }
        }
        if (other_it == other.end()) {
            return 0;
        } else {
            return -1;
        }
    }

  public:
    wto_nesting_t() : _nodes(std::make_shared<node_list_t>()) {}

    void operator+=(const vertex_descriptor_t& n) {
        this->_nodes = std::make_shared<node_list_t>(*(this->_nodes));
        this->_nodes->push_back(n);
    }

    wto_nesting_t operator+(const vertex_descriptor_t& n) {
        wto_nesting_t res(this->_nodes);
        res._nodes->push_back(n);
        return res;
    }

    iterator begin() { return _nodes->begin(); }

    iterator end() { return _nodes->end(); }

    const_iterator begin() const { return _nodes->begin(); }

    const_iterator end() const { return _nodes->end(); }

    wto_nesting_t operator^(wto_nesting_t other) const {
        wto_nesting_t res;
        for (const_iterator this_it = this->begin(), other_it = other.begin();
             this_it != this->end() && other_it != other.end(); ++this_it, ++other_it) {
            if (*this_it == *other_it) {
                res._nodes->push_back(*this_it);
            } else {
                break;
            }
        }
        return res;
    }

    bool operator<=(wto_nesting_t other) const { return this->compare(other) <= 0; }

    bool operator==(wto_nesting_t other) const { return this->compare(other) == 0; }

    bool operator>(wto_nesting_t other) const { return this->compare(other) == 1; }

    void write(std::ostream& o) const {
        o << "[";
        for (auto it = this->begin(); it != this->end();) {
            vertex_descriptor_t n = *it;
            o << n;
            ++it;
            if (it != this->end()) {
                o << ", ";
            }
        }
        o << "]";
    }
}; // class nesting

inline std::ostream& operator<<(std::ostream& o, const wto_nesting_t& n) {
    n.write(o);
    return o;
}

class wto_component_t {

  public:
    virtual void accept(wto_component_visitor_t*) = 0;

    virtual ~wto_component_t() = default;

    virtual void write(std::ostream& os) const = 0;

}; // class wto_component

inline std::ostream& operator<<(std::ostream& o, const wto_component_t& c) {
    c.write(o);
    return o;
}

class wto_component_visitor_t {

  public:
    virtual void visit(wto_vertex_t&) = 0;
    virtual void visit(wto_cycle_t&) = 0;
    virtual ~wto_component_visitor_t() = default;

}; // class wto_component_visitor_t

class wto_vertex_t final : public wto_component_t {

    friend class wto_t;

  private:
    vertex_descriptor_t _node;

    explicit wto_vertex_t(vertex_descriptor_t node) : _node(std::move(node)) {}

  public:
    vertex_descriptor_t node() { return this->_node; }

    void accept(wto_component_visitor_t* v) override { v->visit(*this); }

    void write(std::ostream& o) const override { o << this->_node; }

}; // class wto_vertex

class wto_cycle_t final : public wto_component_t {

    friend class wto_t;
  private:
    using wto_component_ptr = std::shared_ptr<wto_component_t>;
    using wto_component_list_t = std::forward_list<wto_component_ptr>;
    using wto_component_list_ptr = std::shared_ptr<wto_component_list_t>;

    vertex_descriptor_t _head;
    wto_component_list_ptr _wto_components;
    // number of times the wto cycle is analyzed by the fixpoint iterator
    unsigned _num_fixpo;

    wto_cycle_t(vertex_descriptor_t head, wto_component_list_ptr wto_components)
        : _head(std::move(head)), _wto_components(std::move(wto_components)), _num_fixpo(0) {}

  public:
    using iterator = boost::indirect_iterator<typename wto_component_list_t::iterator>;
    using const_iterator = boost::indirect_iterator<typename wto_component_list_t::const_iterator>;

    vertex_descriptor_t head() { return this->_head; }

    void accept(wto_component_visitor_t* v) override { v->visit(*this); }

    iterator begin() { return boost::make_indirect_iterator(_wto_components->begin()); }

    iterator end() { return boost::make_indirect_iterator(_wto_components->end()); }

    const_iterator begin() const { return boost::make_indirect_iterator(_wto_components->begin()); }

    const_iterator end() const { return boost::make_indirect_iterator(_wto_components->end()); }

    void increment_fixpo_visits() { _num_fixpo++; }

    void write(std::ostream& o) const override {
        o << "(" << this->_head;
        if (!this->_wto_components->empty()) {
            o << " ";
            for (const_iterator it = this->begin(); it != this->end();) {
                const wto_component_t& c = *it;
                o << c;
                ++it;
                if (it != this->end()) {
                    o << " ";
                }
            }
        }
        o << ")";
        if (this->_num_fixpo > 0)
            o << "^{" << this->_num_fixpo << "}";
    }

}; // class wto_cycle

class wto_t final {
  private:
    using wto_component_ptr = std::shared_ptr<wto_component_t>;
    using wto_vertex_ptr = std::shared_ptr<wto_vertex_t>;
    using wto_cycle_ptr = std::shared_ptr<wto_cycle_t>;
    using wto_component_list_t = std::forward_list<wto_component_ptr>;
    using wto_component_list_ptr = std::shared_ptr<wto_component_list_t>;
    using dfn_t = bound_t;
    using dfn_table_t = std::unordered_map<vertex_descriptor_t, dfn_t>;
    using dfn_table_ptr = std::shared_ptr<dfn_table_t>;
    using stack_t = std::vector<vertex_descriptor_t>;
    using stack_ptr = std::shared_ptr<stack_t>;
    using nesting_table_t = std::unordered_map<vertex_descriptor_t, wto_nesting_t>;
    using nesting_table_ptr = std::shared_ptr<nesting_table_t>;

    wto_component_list_ptr _wto_components;
    dfn_table_ptr _dfn_table;
    dfn_t _num;
    stack_ptr _stack;
    nesting_table_ptr _nesting_table;

    class nesting_builder : public wto_component_visitor_t {
      private:
        wto_nesting_t _nesting;
        nesting_table_ptr _nesting_table;

      public:
        explicit nesting_builder(nesting_table_ptr nesting_table) : _nesting_table(std::move(nesting_table)) {}

        void visit(wto_cycle_t& cycle) override {
            vertex_descriptor_t head = cycle.head();
            wto_nesting_t previous_nesting = this->_nesting;
            this->_nesting_table->insert(std::make_pair(head, this->_nesting));
            this->_nesting += head;
            for (typename wto_cycle_t::iterator it = cycle.begin(); it != cycle.end(); ++it) {
                it->accept(this);
            }
            this->_nesting = previous_nesting;
        }

        void visit(wto_vertex_t& vertex) override {
            this->_nesting_table->insert(std::make_pair(vertex.node(), this->_nesting));
        }

    }; // class nesting_builder

    dfn_t get_dfn(const vertex_descriptor_t& n) {
        auto it = this->_dfn_table->find(n);
        if (it == this->_dfn_table->end()) {
            return 0;
        } else {
            return it->second;
        }
    }

    void set_dfn(const vertex_descriptor_t& n, const dfn_t& dfn) {
        std::pair<typename dfn_table_t::iterator, bool> res = this->_dfn_table->insert(std::make_pair(n, dfn));
        if (!res.second) {
            (res.first)->second = dfn;
        }
    }

    vertex_descriptor_t pop() {
        if (this->_stack->empty()) {
            CRAB_ERROR("WTO computation: empty stack");
        } else {
            vertex_descriptor_t top = this->_stack->back();
            this->_stack->pop_back();
            return top;
        }
    }

    void push(const vertex_descriptor_t& n) { this->_stack->push_back(n); }

    wto_cycle_ptr component(cfg_t& g, const vertex_descriptor_t& vertex) {
        auto partition = std::make_shared<wto_component_list_t>();
        std::pair<out_edge_iterator_t, out_edge_iterator_t> succ_edges = out_edges(vertex, g);
        for (out_edge_iterator_t it = succ_edges.first, et = succ_edges.second; it != et; ++it) {
            vertex_descriptor_t succ = target(*it, g);
            if (this->get_dfn(succ) == 0) {
                this->visit(g, succ, partition);
            }
        }
        return wto_cycle_ptr(new wto_cycle_t(vertex, partition));
    }

    struct visit_stack_elem {
        using succ_iterator = out_edge_iterator_t;
        vertex_descriptor_t _node;
        succ_iterator _it; // begin iterator for node's successors
        succ_iterator _et; // end iterator for node's successors
        dfn_t _min;        // smallest dfn number of any (direct or
                           // indirect) node's successor through node's
                           // DFS subtree, included node.

        visit_stack_elem(vertex_descriptor_t node, const std::pair<succ_iterator, succ_iterator>& succs, const dfn_t& min)
            : _node(std::move(node)), _it(succs.first), _et(succs.second), _min(min) {}
    };

    void visit(cfg_t& g, const vertex_descriptor_t& vertex, const wto_component_list_ptr& partition) {

        std::vector<visit_stack_elem> visit_stack;
        std::set<vertex_descriptor_t> loop_nodes;

        /* discover vertex */
        push(vertex);
        _num += 1;
        set_dfn(vertex, _num);

        visit_stack.emplace_back(vertex, out_edges(vertex, g), _num);
        CRAB_LOG("wto-nonrec", std::cout << "WTO: Node " << vertex << ": dfs num=" << _num << "\n";);
        while (!visit_stack.empty()) {
            /*
             * Perform dfs.
             *
             * When this loop terminates, visit_stack.back()_node's children
             * have been processed.  For each loop iteration we push in
             * visit_stack one more descendant.
             */
            while (visit_stack.back()._it != visit_stack.back()._et) {
                edge_descriptor_t e = *visit_stack.back()._it++;
                vertex_descriptor_t child = target(e, g);
                dfn_t child_dfn = get_dfn(child);
                if (child_dfn == 0) {
                    /* discover new vertex */
                    push(child);
                    _num += 1;
                    set_dfn(child, _num);
                    visit_stack.emplace_back(child, out_edges(child, g), _num);
                    CRAB_LOG("wto-nonrec", std::cout << "WTO: Node " << child << ": dfs num=" << _num << "\n";);
                } else {
                    if (child_dfn <= visit_stack.back()._min) {
                        visit_stack.back()._min = child_dfn;
                        CRAB_LOG("wto-nonrec", std::cout << "WTO: loop found " << child << "\n";);
                        loop_nodes.insert(child);
                    }
                }
            }

            // propagate min from child to parent
            vertex_descriptor_t visiting_node = visit_stack.back()._node;
            dfn_t min_visiting_node = visit_stack.back()._min;
            bool is_loop = loop_nodes.count(visiting_node) > 0;
            visit_stack.pop_back();
            if (!visit_stack.empty() && visit_stack.back()._min > min_visiting_node) {
                visit_stack.back()._min = min_visiting_node;
            }

            auto dfn_visiting_node = get_dfn(visiting_node);
            CRAB_LOG("wto-nonrec", std::cout << "WTO: popped node " << visiting_node << " dfs num= "
                                             << dfn_visiting_node << ": min=" << min_visiting_node << "\n";);

            if (min_visiting_node == get_dfn(visiting_node)) {
                CRAB_LOG("wto-nonrec",
                         std::cout << "WTO: BEGIN building partition for node " << visiting_node << "\n";);
                set_dfn(visiting_node, dfn_t::plus_infinity());
                vertex_descriptor_t element = pop();
                if (is_loop) {
                    while (!(element == visiting_node)) {
                        set_dfn(element, 0);
                        CRAB_LOG("wto-nonrec", std::cout << "\tWTO: node " << element << ": dfn num=0\n";);
                        element = pop();
                    }
                    CRAB_LOG("wto-nonrec",
                             std::cout << "\tWTO: adding component starting from " << visiting_node << "\n";);
                    partition->push_front(component(g, visiting_node));
                } else {
                    CRAB_LOG("wto-nonrec", std::cout << "\tWTO: adding vertex " << visiting_node << "\n";);
                    partition->push_front(wto_vertex_ptr(new wto_vertex_t(visiting_node)));
                }
                CRAB_LOG("wto-nonrec", std::cout << "WTO: END building partition\n";);
            }
        } // end while (!visit_stack.empty())
    }

    void build_nesting() {
        nesting_builder builder(this->_nesting_table);
        for (iterator it = this->begin(); it != this->end(); ++it) {
            it->accept(&builder);
        }
    }

  public:
    using iterator = boost::indirect_iterator<typename wto_component_list_t::iterator>;
    using const_iterator = boost::indirect_iterator<typename wto_component_list_t::const_iterator>;

    explicit wto_t(cfg_t& g)
        : _wto_components(std::make_shared<wto_component_list_t>()), _dfn_table(std::make_shared<dfn_table_t>()),
          _num(0), _stack(std::make_shared<stack_t>()), _nesting_table(std::make_shared<nesting_table_t>()) {
        ScopedCrabStats __st__("Fixpo.WTO");

        this->visit(g, entry(g), this->_wto_components);
        this->_dfn_table.reset();
        this->_stack.reset();
        this->build_nesting();
    }

    wto_t(const wto_t& other) = delete;

    wto_t(const wto_t&& other) noexcept
        : _wto_components(other._wto_components), _dfn_table(other._dfn_table), _num(other._num),
          _stack(other._stack), _nesting_table(other._nesting_table) {}

    wto_t& operator=(const wto_t& other) {
        if (this != &other) {
            this->_wto_components = other._wto_components;
            this->_dfn_table = other._dfn_table;
            this->_num = other._num;
            this->_stack = other._stack;
            this->_nesting_table = other._nesting_table;
        }
        return *this;
    }

    iterator begin() { return boost::make_indirect_iterator(_wto_components->begin()); }

    iterator end() { return boost::make_indirect_iterator(_wto_components->end()); }

    const_iterator begin() const { return boost::make_indirect_iterator(_wto_components->begin()); }

    const_iterator end() const { return boost::make_indirect_iterator(_wto_components->end()); }

    wto_nesting_t nesting(vertex_descriptor_t n) {
        auto it = this->_nesting_table->find(n);
        if (it == this->_nesting_table->end()) {
            CRAB_ERROR("WTO nesting: node ", n, " not found");
        } else {
            return it->second;
        }
    }

    void accept(wto_component_visitor_t* v) {
        for (iterator it = this->begin(); it != this->end(); ++it) {
            it->accept(v);
        }
    }

    void write(std::ostream& o) const {
        for (const_iterator it = this->begin(); it != this->end();) {
            const wto_component_t& c = *it;
            o << c;
            ++it;
            if (it != this->end()) {
                o << " ";
            }
        }
    }

    friend std::ostream& operator<<(std::ostream& o, const wto_t& wto) {
        wto.write(o);
        return o;
    }

}; // class wto

} // namespace crab


#include <boost/serialization/vector.hpp>
#include <boost/serialization/nvp.hpp>

#include "utilities/munkres/munkres.h"

using boost::serialization::make_nvp;

template<class D>
PDPoint<D>::
PDPoint(RealType x, RealType y, const Data& data)
{
    point_.first().first = x;
    point_.first().second = y;
    point_.second() = data;
}

template<class D>
template<class OtherData>
PersistenceDiagram<D>::
PersistenceDiagram(const PersistenceDiagram<OtherData>& other)
{
    points_.reserve(other.size());
    for (typename PersistenceDiagram<OtherData>::PointVector::const_iterator cur = points_.begin();
                                                                             cur != points_.end(); ++cur)
        push_back(Point(cur->x(), cur->y()));
}

template<class D>
template<class Iterator, class Evaluator>
PersistenceDiagram<D>::
PersistenceDiagram(Iterator bg, Iterator end, const Evaluator& eval)
{
    init(bg, end, eval, Point::Visitor());
}

template<class D>
template<class Iterator, class Evaluator, class Visitor>
PersistenceDiagram<D>::
PersistenceDiagram(Iterator bg, Iterator end, const Evaluator& eval, const Visitor& visitor)
{
    init(bg, end, eval, visitor);
}

template<class D>
template<class Iterator, class Evaluator, class Visitor>
void
PersistenceDiagram<D>::
init(Iterator bg, Iterator end, const Evaluator& evaluator, const Visitor& visitor)
{
    for (Iterator cur = bg; cur != end; ++cur)
        if (cur->sign())
        {
            boost::optional<Point> p = make_point(cur, evaluator, visitor);
            if (p)  push_back(*p);
        }
}

template<class Point, class Iterator, class Evaluator, class Visitor>
boost::optional<Point>
make_point(Iterator i, const Evaluator& evaluator, const Visitor& visitor)
{
    RealType x = evaluator(&*i);
    RealType y = Infinity;
    if (&*(i->pair) != &*i)
        y = evaluator(&*(i->pair));

    Point p(x,y);
    visitor.point(i, p);

    if (x == y) return boost::optional<Point>();

    return p;
}

template<class Diagrams, class Iterator, class Evaluator, class DimensionExtractor>
void    init_diagrams(Diagrams& diagrams,
                      Iterator bg, Iterator end,
                      const Evaluator& evaluator,
                      const DimensionExtractor& dimension)
{
    // FIXME: this is specialized for Diagrams that is std::map
    typedef             typename Diagrams::mapped_type              PDiagram;

    init_diagrams(diagrams, bg, end, evaluator, dimension, typename PDiagram::Point::Visitor());
}

template<class Diagrams, class Iterator, class Evaluator, class DimensionExtractor, class Visitor>
void    init_diagrams(Diagrams& diagrams,
                      Iterator bg, Iterator end,
                      const Evaluator& evaluator,
                      const DimensionExtractor& dimension,
                      const Visitor& visitor)
{
    // FIXME: this is specialized for Diagrams that is std::map
    typedef             typename Diagrams::mapped_type              PDiagram;

    for (Iterator cur = bg; cur != end; ++cur)
        if (cur->sign())
        {
            boost::optional<typename PDiagram::Point> p = make_point<typename PDiagram::Point>(cur, evaluator, visitor);
            if (p)
                diagrams[dimension(&*cur)].push_back(*p);
        }
}

template<class D>
std::ostream&
PersistenceDiagram<D>::
operator<<(std::ostream& out) const
{
    for (const_iterator cur = begin(); cur != end(); ++cur)
        out << *cur << std::endl;
    return out;
}

template<class D>
template<class Archive>
void
PDPoint<D>::
serialize(Archive& ar, version_type )
{
    ar & make_nvp("x", x());
    ar & make_nvp("y", y());
    ar & make_nvp("data", data());
}

template<class D>
template<class Archive>
void
PersistenceDiagram<D>::
serialize(Archive& ar, version_type )
{
    ar & make_nvp("points", points_);
}

/**
 * Some structures to compute bottleneck distance between two persistence diagrams (in bottleneck_distance() function below)
 * by setting up bipartite graphs, and finding maximum cardinality matchings in them using Boost Graph Library.
 */
#include <boost/iterator/counting_iterator.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/max_cardinality_matching.hpp>

struct Edge: public std::pair<unsigned long, unsigned long>
{
    typedef         std::pair<unsigned long, unsigned long>                       Parent;

                    Edge(unsigned long v1, unsigned long v2, RealType d):
                        Parent(v1, v2), distance(d)                     {}

    bool            operator<(const Edge& other) const                  { return distance < other.distance; }

    RealType        distance;
};
typedef std::vector<Edge>               EdgeVector;
typedef EdgeVector::const_iterator      EV_const_iterator;

struct CardinaliyComparison
{
    typedef         boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS>         Graph;
    typedef         std::vector<boost::graph_traits<Graph>::vertex_descriptor>                  MatchingVector;

                    CardinaliyComparison(unsigned long size, EV_const_iterator begin):
                        max_size(size), bg(begin), last(bg), g(2*max_size), mates(2*max_size)
                    { boost::add_edge(bg->first, bg->second, g); }

    bool            operator()(EV_const_iterator i1, EV_const_iterator i2)
    {
        //std::cout << "Max size: " << max_size << std::endl;
        //std::cout << "Comparing: (" << i1->first << ", " << i1->second << ") and "
        //          <<            "(" << i2->first << ", " << i2->second << ")" << std::endl;

        // FIXME: the matching is being recomputed from scratch every time, this should be fixed
        if (i2 > last)
            do
            {
                ++last;
                boost::add_edge(last->first, last->second, g);
            } while (last != i2);
        else
            do
            {
                boost::remove_edge(last->first, last->second, g);
            } while (--last != i2);

        edmonds_maximum_cardinality_matching(g, &mates[0]);
        //std::cout << "Found matching of size: " << matching_size(g, &mates[0]) << std::endl;
        return matching_size(g, &mates[0]) == max_size;
    }

    unsigned long                max_size;
    EV_const_iterator       bg;
    EV_const_iterator       last;
    Graph                   g;
    MatchingVector          mates;
};

// Bottleneck distance
template<class Diagram1, class Diagram2, class Norm>
RealType                bottleneck_distance(const Diagram1& dgm1, const Diagram2& dgm2, const Norm& norm)
{
    typedef         typename Diagram1::const_iterator                   Citer1;
    typedef         typename Diagram2::const_iterator                   Citer2;

    const unsigned long  max_size = dgm1.size() + dgm2.size();

    // Compute all the edges and sort them by distance
    EdgeVector   edges;

    // Connect all diagonal points to each other
    for (unsigned long i = dgm1.size(); i < max_size; ++i)
        for (unsigned long j = max_size + dgm2.size(); j < 2*max_size; ++j)
            edges.push_back(Edge(i, j, 0));

    // Edges between real points
    unsigned long i = 0;
    for (Citer1 cur1 = dgm1.begin(); cur1 != dgm1.end(); ++cur1)
    {
        unsigned long j = max_size;
        for (Citer2 cur2 = dgm2.begin(); cur2 != dgm2.end(); ++cur2)
            edges.push_back(Edge(i,j++, norm(*cur1, *cur2)));

        ++i;
    }

    // Edges between real points and their corresponding diagonal points
    i = 0;
    for (Citer1 cur1 = dgm1.begin(); cur1 != dgm1.end(); ++cur1, ++i)
        edges.push_back(Edge(i, max_size + dgm2.size() + i, norm.diagonal(*cur1)));
    i = max_size;
    for (Citer2 cur2 = dgm2.begin(); cur2 != dgm2.end(); ++cur2, ++i)
        edges.push_back(Edge(dgm1.size() + (i - max_size), i, norm.diagonal(*cur2)));

    std::sort(edges.begin(), edges.end());
    //for (i = 0; i < edges.size(); ++i)
    //    std::cout << "Edge: " << edges[i].first << " " << edges[i].second << " " << edges[i].distance << std::endl;

    // Perform cardinality based binary search
    typedef boost::counting_iterator<EV_const_iterator>         EV_counting_const_iterator;
    EV_counting_const_iterator bdistance = std::upper_bound(EV_counting_const_iterator(edges.begin()),
                                                            EV_counting_const_iterator(edges.end()),
                                                            edges.begin(),
                                                            CardinaliyComparison(max_size, edges.begin()));

    return (*bdistance)->distance;
}

// Wasserstein distance
template<class Diagram>
RealType
wasserstein_distance(const Diagram& dgm1, const Diagram& dgm2, long p)
{
    typedef         RealType                    Distance;
    typedef         typename Diagram::Point     Point;
    typedef         Linfty<Point, Point>        Norm;

    unsigned long size = dgm1.size() + dgm2.size();
    Norm norm;

    // Setup the matrix
    Matrix<Distance>        m(size,size);
    for (unsigned long i = 0; i < dgm1.size(); ++i)
        for (unsigned long j = 0; j < dgm2.size(); ++j)
        {
            const Point& p1 = *(dgm1.begin() + i);
            const Point& p2 = *(dgm2.begin() + j);
            m(i,j) = pow(norm(p1, p2),  p);
            m(j + dgm1.size(), i + dgm2.size()) = 0;
        }

    for (unsigned long i = 0; i < dgm1.size(); ++i)
        for (unsigned long j = dgm2.size(); j < size; ++j)
        {
            const Point& p1 = *(dgm1.begin() + i);
            m(i,j) = pow(norm.diagonal(p1), p);
        }

    for (unsigned long j = 0; j < dgm2.size(); ++j)
        for (unsigned long i = dgm1.size(); i < size; ++i)
        {
            const Point& p2 = *(dgm2.begin() + j);
            m(i,j) = pow(norm.diagonal(p2), p);
        }

    // Compute weighted matching
    Munkres munkres;
    munkres.solve(m);

    // Assume everything is assigned (i.e., that we have a perfect matching)
    Distance sum = 0;
    for (unsigned long i = 0; i < size; i++)
        for (unsigned long j = 0; j < size; j++)
            if (m(i,j) == 0)
            {
                //std::cout << i << ": " << j << '\n';
                //sum += m[i][j];
                if (i >= dgm1.size())
                {
                    if (j >= dgm2.size())
                        sum += 0;
                    else
                    {
                        const Point& p2 = *(dgm2.begin() + j);
                        sum += pow(norm.diagonal(p2), p);
                    }
                } else
                {
                    if (j >= dgm2.size())
                    {
                        const Point& p1 = *(dgm1.begin() + i);
                        sum += pow(norm.diagonal(p1), p);
                    } else
                    {
                        const Point& p1 = *(dgm1.begin() + i);
                        const Point& p2 = *(dgm2.begin() + j);
                        sum += pow(norm(p1, p2),  p);
                    }
                }
                break;
            }

    return sum;
}


/*
For more information, please see: http://software.sci.utah.edu

The MIT License

Copyright (c) 2015 Scientific Computing and Imaging Institute,
University of Utah.

License for the specific language governing rights and limitations under
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.
*/

#include <Core/Algorithms/Base/AlgorithmPreconditions.h>
#include <Core/Algorithms/BrainStimulator/GenerateROIStatisticsAlgorithm.h>
#include <Core/GeometryPrimitives/Vector.h>
#include <Core/Datatypes/Legacy/Field/Field.h>
#include <Core/Datatypes/Legacy/Field/VField.h>
#include <Core/Datatypes/Legacy/Field/FieldInformation.h>
#include <Core/Datatypes/Legacy/Field/VMesh.h>
#include <Core/Datatypes/DenseMatrix.h>
#include <Core/Datatypes/Matrix.h>
#include <Core/Datatypes/String.h>
#include <boost/range/algorithm/count.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/format.hpp>
#include <boost/assign.hpp>
#include <Core/Logging/Log.h>
#include <string>
#include <iostream>

using namespace SCIRun::Core::Datatypes;
using namespace SCIRun::Core::Algorithms;
using namespace SCIRun::Core::Algorithms::BrainStimulator;
using namespace SCIRun::Core::Geometry;
using namespace SCIRun;
using namespace SCIRun::Core::Logging;
using namespace boost::assign;

const AlgorithmInputName GenerateROIStatisticsAlgorithm::MeshDataOnElements("MeshDataOnElements");
const AlgorithmInputName GenerateROIStatisticsAlgorithm::PhysicalUnit("PhysicalUnit");
const AlgorithmInputName GenerateROIStatisticsAlgorithm::AtlasMesh("AtlasMesh");
const AlgorithmInputName GenerateROIStatisticsAlgorithm::AtlasMeshLabels("AtlasMeshLabels");
const AlgorithmInputName GenerateROIStatisticsAlgorithm::CoordinateSpace("CoordinateSpace");
const AlgorithmInputName GenerateROIStatisticsAlgorithm::CoordinateSpaceLabel("CoordinateSpaceLabel");
const AlgorithmInputName GenerateROIStatisticsAlgorithm::SpecifyROI("SpecifyROI");
const AlgorithmOutputName GenerateROIStatisticsAlgorithm::StatisticalResults("StatisticalResults");

ALGORITHM_PARAMETER_DEF(BrainStimulator, ROITableValues);
ALGORITHM_PARAMETER_DEF(BrainStimulator, StatisticsTableValues);
ALGORITHM_PARAMETER_DEF(BrainStimulator, PhysicalUnitStr);
ALGORITHM_PARAMETER_DEF(BrainStimulator, CoordinateSpaceLabelStr);

GenerateROIStatisticsAlgorithm::GenerateROIStatisticsAlgorithm()
{
  using namespace Parameters;
  addParameter(ROITableValues, 0);
  addParameter(StatisticsTableValues, 0);
  addParameter(PhysicalUnitStr, std::string());
  addParameter(CoordinateSpaceLabelStr, std::string());
}

namespace
{
  std::string formatStatistic(double x)
  {
    if (IsNan(x))
      return "NaN";
    return boost::str(boost::format("%.3f") % x);
  }

  std::string formatCount(double x)
  {
    if (IsNan(x))
      return "NaN";
    return boost::str(boost::format("%d") % x);
  }
}

/// the run function can deal with multiple inputs and performs the analysis for all ROIs in the atlas mesh and for the user specified ROI
boost::tuple<DenseMatrixHandle, VariableHandle> GenerateROIStatisticsAlgorithm::run(FieldHandle mesh, FieldHandle AtlasMesh, const FieldHandle CoordinateSpace, const std::string& AtlasMeshLabels, const DenseMatrixHandle specROI) const
{
  DenseMatrixHandle output;

  VField* vfield1 = mesh->vfield();
  VField* vfield2 = AtlasMesh->vfield();

  std::vector<bool> element_selection(vfield2->vmesh()->num_elems(), true);  /// set the default element ROI selection, so all of them are included

  double x=0,y=0,z=0,radius=-1,target_material=-1;
  /// CoordinateSpace is provided and coordinates? if not don't go in this if clause
  if (CoordinateSpace != nullptr && specROI != nullptr)
  {
    if ( (*specROI).ncols()==1 && (*specROI).nrows()==5 ) /// GUI input (as DenseMatrix) has the right sizes (rows, cols)?
    {
      x=(*specROI)(0,0);
      y=(*specROI)(1,0);
      z=(*specROI)(2,0);
      target_material=(*specROI)(3,0);
      radius=(*specROI)(4,0);

      if (radius<0) /// if radius < 0, its invalid input provide a remark and do the statistical analysis for all ROIs instead
      {
        remark("Radius needs to be > 0 and Atlas Material # needs to exist ");
      } else
      {
        if (radius>0)
        {
          element_selection = statistics_based_on_xyz_coodinates(AtlasMesh, CoordinateSpace, x, y, z, radius, target_material); /// redefine the element ROI selection if correct input was delivered
        }
      }
    }

  }

  if(element_selection.size()!=vfield1->vmesh()->num_elems()) /// internal error check if selection vector really matches number of mesh elements
  {
    THROW_ALGORITHM_INPUT_ERROR("Internal Error: Element selection vector does not match number of mesh elements ");
  }

  size_t number_of_atlas_materials;

  std::set<int> labelSet;

  if (target_material==-1 || radius==0) /// if default consider all materials
  {
    for (VMesh::Elem::index_type i=0; i < vfield2->vmesh()->num_elems(); i++) // loop over all tetrahedral elements (mesh)
    {
      int Label;
      vfield2->get_value(Label, i);
      labelSet.insert(Label);
    }
  } else
  {
    labelSet.insert(static_cast<int>(target_material));
  }

  number_of_atlas_materials = labelSet.size();

  std::vector<int> labelVector(labelSet.begin(), labelSet.end());

  std::ostringstream ostr; /// sort element labels ascending
  std::copy(labelSet.begin(), labelSet.end(), std::ostream_iterator<int>(ostr, ", "));
  LOG_DEBUG("Sorted set of label numbers: " << ostr.str() << std::endl);

  std::vector<double> value_avr(number_of_atlas_materials);
  std::vector<int> value_count(number_of_atlas_materials);
  std::vector<double> value_min(number_of_atlas_materials);
  std::vector<double> value_max(number_of_atlas_materials);
  std::vector<double> value_std(number_of_atlas_materials);
  std::vector<double> Sxsqr(number_of_atlas_materials);
  std::vector<double> stddev(number_of_atlas_materials);
  std::vector<double> var(number_of_atlas_materials);

  /// to do the actual statistics we need to precompute parts of it to make it efficient (don't loop over elements twice)
  for (VMesh::Elem::index_type i=0; i < vfield1->vmesh()->num_elems(); i++) /// loop over all tetrahedral elements (AtlasMesh)
  {

    if(element_selection[i]) ///is an particular element selected?
    {
      double value = 0;
      vfield1->get_value(value, i);

      int Label = 0;
      vfield2->get_value(Label, i);

      for (VMesh::Elem::index_type j=0; j < number_of_atlas_materials; j++) /// loop over determined materials
      {
        if (Label==labelVector[j] || (target_material==0 && number_of_atlas_materials==1) ) /// if label is known or if default situation precalculate sum, min, max, sum^2, number of selected elements
        {
          value_avr[j] += value;
          value_count[j]++;
          if (value>value_max[j]) value_max[j]=value;
          if (value<value_min[j] || value_min[j]==0) value_min[j]=value;
          Sxsqr[j]+=value*value;
        }
      }
    }
  }

  output = DenseMatrixHandle(new DenseMatrix(number_of_atlas_materials, 5)); /// instantiate output
  const double invalidDouble = std::numeric_limits<double>::quiet_NaN();

  /// efficient way to compute std dev. in just one loop over all mesh elements: sqrt ( 1/(n-1) (Sx^2 - avr Sx + n avr^2 )
  for (VMesh::Elem::index_type j=0; j < number_of_atlas_materials; j++)
  {
    double Sx=value_avr[j];

    if (value_count[j]!=0)
    {
      value_avr[j]/=value_count[j];
      if (value_count[j]>1)
      {
        var[j]=static_cast<double>(1./(value_count[j]-1)*(Sxsqr[j]-2*value_avr[j]*Sx+value_count[j]*value_avr[j]*value_avr[j]));
        stddev[j]=static_cast<double>(std::sqrt(var[j])); /// compute standard deviation, average, variance
      } else
      {
        var[j]=invalidDouble;
        stddev[j]=invalidDouble;
      }

      (*output)(j,0)=value_avr[j]; /// save statistical measures in output (DenseMatrix)
      (*output)(j,1)=stddev[j];
      (*output)(j,2)=value_min[j];
      (*output)(j,3)=value_max[j];
      (*output)(j,4)=value_count[j];
    } else
    {
      (*output)(j,0)=invalidDouble;  /// if the number of elements is 0, provide NaN as output
      (*output)(j,1)=invalidDouble;
      (*output)(j,2)=invalidDouble;
      (*output)(j,3)=invalidDouble;
      (*output)(j,4)=invalidDouble;
    }
  }

  std::vector<std::string> AtlasMeshLabels_vector;
  if (!AtlasMeshLabels.empty())
  {
    AtlasMeshLabels_vector = ConvertInputAtlasStringIntoVector(AtlasMeshLabels); /// cut the atlas ROI labels into pieces (std::vector)
  }

  if (AtlasMeshLabels_vector.size()==0 && number_of_atlas_materials>0)
  {
    AtlasMeshLabels_vector.resize(number_of_atlas_materials);
    for (int i=0; i<number_of_atlas_materials; i++)
    {
      AtlasMeshLabels_vector[i]=boost::lexical_cast<std::string>(i+1); /// if no atlas ROI labels are provided use consecutive numbers
    }
  }
  else
    if (AtlasMeshLabels_vector.size() != number_of_atlas_materials)
    {
      if (target_material!=-1 && number_of_atlas_materials==1)
      {
        AtlasMeshLabels_vector.resize(1);
        AtlasMeshLabels_vector[0]="specROI";  /// if an ROI was specified by the user use specROI label for the upper table
      }
      else
      {
        THROW_ALGORITHM_INPUT_ERROR("Number of material Labels in AtlasMesh and AtlasMeshLabels do not match");
      }
    }

    //TODO: use this example to improve Variable::List syntactical sugar
    Variable::List elc_vals_in_table;
    for (int i=0; i<AtlasMeshLabels_vector.size(); i++) /// loop over all materials, format numbers and put it in a Variable structure to be accessible in the GUI
    {
      Variable::List tmp;
      tmp += makeVariable("name", AtlasMeshLabels_vector[i]), //label name
        makeVariable("col0", formatStatistic((*output)(i,0))), //average
        makeVariable("col1", formatStatistic((*output)(i,1))), //stddev
        makeVariable("col2", formatStatistic((*output)(i,2))), //min
        makeVariable("col3", formatStatistic((*output)(i,3))), //max
        makeVariable("col4", formatCount((*output)(i,4))); //element count

      elc_vals_in_table.push_back(makeVariable("row" + boost::lexical_cast<std::string>(i), tmp));
    }

    VariableHandle statistics_table(new Variable(Name("Table"), elc_vals_in_table));

    return boost::make_tuple(output, statistics_table);
}

/// this function takes the (x,y,z) location of the user specified ROI and results in a std:vector<bool> that contains trues for ROI mesh elements and false for non-ROI mesh elements
std::vector<bool> GenerateROIStatisticsAlgorithm::statistics_based_on_xyz_coodinates(const FieldHandle mesh, const FieldHandle CoordinateSpace, double x, double y, double z, double radius, int target_material) const
{
  VField* vfield_coordspace = CoordinateSpace->vfield();
  VMesh* vmesh_coordspace = vfield_coordspace->vmesh();
  VField* vfield_atlas = mesh->vfield();
  VMesh* vmesh_atlas = vfield_atlas->vmesh();
  std::vector<bool> element_selection;
  long closest_atlas_element=-1;
  double distance=-1, mindis=std::numeric_limits<double>::max();
  Vector val;

  for (VMesh::Elem::index_type i=0; i < vmesh_coordspace->num_elems(); i++) /// loop over all tetrahedral elements (mesh)
  {
    vfield_coordspace->get_value(val,i);
    distance = std::sqrt((x-val[0])*(x-val[0])+(y-val[1])*(y-val[1])+(z-val[2])*(z-val[2]));
    if (distance < mindis)
    {
      closest_atlas_element=(long)i;
      mindis=distance;
    }
  }

  if (mindis>1)
  {
    std::ostringstream ostr1;
    ostr1 << "Distance from provided point (lower table in GUI: x,y,z) is more then 1 (=" << mindis << ") distance units away from provided coordinate space (coordinates defined as element data)." <<  std::endl;
    THROW_ALGORITHM_INPUT_ERROR(ostr1.str());

  }

  Point p;
  VMesh::Elem::index_type atlas_index = closest_atlas_element;
  vmesh_coordspace->get_center(p,atlas_index);

  double x1=p.x(), y1=p.y(), z1=p.z();

  long count_loop=0;

  for (VMesh::Elem::index_type i=0; i < vmesh_atlas->num_elems(); i++) /// loop over all tetrahedral elements (mesh)
  {
    if (target_material!=0)  /// was the target material (Atlas Material #) provided not the default, so "-1"
    {
      int current_material=-1;
      vfield_atlas->get_value(current_material, i);
      if (target_material==current_material) /// if the current material is in the defined spherical ROI and of the material we are looking for?
      {
        VMesh::Elem::index_type tmp = count_loop;
        vmesh_atlas->get_center(p,tmp);

        distance = sqrt((x1-p.x())*(x1-p.x())+(y1-p.y())*(y1-p.y())+(z1-p.z())*(z1-p.z()));
        if (distance > radius)
        {
          element_selection.push_back(false);
        } else
        {
          element_selection.push_back(true);
        }
      } else
      {
        element_selection.push_back(false);
      }
    } else
    {     /// in the else close look for materials in the ROI
      VMesh::Elem::index_type tmp = count_loop;
      vmesh_atlas->get_center(p,tmp);
      distance = sqrt((x1-p.x())*(x1-p.x())+(y1-p.y())*(y1-p.y())+(z1-p.z())*(z1-p.z()));
      if ( distance > radius)
      {
        element_selection.push_back(false);
      } else
      {
        element_selection.push_back(true);
      }
    }
    count_loop++;
  }

  return element_selection;
}

/// this function cuts the atlas label string into pieces (std::vector) based on the semicolon
std::vector<std::string> GenerateROIStatisticsAlgorithm::ConvertInputAtlasStringIntoVector(const std::string& atlasLabels) const
{
  std::vector<std::string> result;
  auto atlasLabelsTrimmed = atlasLabels;
  boost::trim_if(atlasLabelsTrimmed, boost::is_any_of(";")); /// use boost's trim function to get rid of all additional semicolons or
  boost::split(result,atlasLabelsTrimmed,boost::is_any_of(";")); /// use boost's trim function to cut the string

  return result;
}

AlgorithmOutput GenerateROIStatisticsAlgorithm::run_generic(const AlgorithmInput& input) const
{

  auto mesh_ = input.get<Field>(MeshDataOnElements);
  auto physicalUnit_ = input.get<Datatypes::String>(PhysicalUnit);
  auto atlasMesh_ = input.get<Field>(AtlasMesh);
  auto atlasMeshLabels_ = input.get<Datatypes::String>(AtlasMeshLabels);
  auto coordinateSpace_ = input.get<Field>(CoordinateSpace);
  auto coordinateLabel_ = input.get<Datatypes::String>(CoordinateSpaceLabel);
  auto roiSpec = input.get<DenseMatrix>(SpecifyROI);

  /// In the following check the validity of the inputs
  if (!mesh_)
    THROW_ALGORITHM_INPUT_ERROR("First input (mesh) is empty.");

  if (!atlasMesh_)
    THROW_ALGORITHM_INPUT_ERROR("Third input (atlas mesh) is empty.");

  FieldInformation fi(mesh_);

  if (!fi.is_constantdata())
    THROW_ALGORITHM_INPUT_ERROR("First input (mesh) requires the data to be on the elements.");

  // making sure the field contains data
  VField* vfield1 = mesh_->vfield();
  if (vfield1->is_nodata())
    THROW_ALGORITHM_INPUT_ERROR("First input field (mesh) contained no data.");

  // making sure the field is not in vector format
  if (!vfield1->is_scalar())
    THROW_ALGORITHM_INPUT_ERROR("First input field needs to have scalar data.");

  FieldInformation fi2(atlasMesh_);

  if (!fi2.is_constantdata())
    THROW_ALGORITHM_INPUT_ERROR("First input (mesh) requires the data to be on the elements.");

  // making sure the field contains data
  VField* vfield2 = atlasMesh_->vfield();
  if (vfield2->is_nodata())
    THROW_ALGORITHM_INPUT_ERROR("First input field (mesh) contained no data.");

  // making sure the field is not in vector format
  if (!vfield2->is_scalar())
    THROW_ALGORITHM_INPUT_ERROR("First input field needs to have scalar data.");

  if(vfield1->vmesh()->num_elems()<1 && vfield2->vmesh()->num_elems()<1)
    THROW_ALGORITHM_INPUT_ERROR("First (mesh) or second (AtlasMesh) input field does not contain elements.");

  if(vfield2->vmesh()->num_elems() !=  vfield1->vmesh()->num_elems())
    THROW_ALGORITHM_INPUT_ERROR(" Number of mesh elements of first input and third input does not match.");

  DenseMatrixHandle statistics;
  VariableHandle Statisticstable;

  /// since there are so many optional inputs to the module decide based on them and call the run function
  const std::string& atlasMeshLabelsStr = atlasMeshLabels_ == nullptr ? std::string("") : atlasMeshLabels_->value();
  const FieldHandle coorspace_ = coordinateSpace_ == nullptr ? FieldHandle() : coordinateSpace_;
  const DenseMatrixHandle roiSpec_ = roiSpec == nullptr ? DenseMatrixHandle() : roiSpec;
  boost::tie(statistics, Statisticstable) = run(mesh_, atlasMesh_, coorspace_, atlasMeshLabelsStr, roiSpec_);

  /// no statistics output? something went wrong then
  if (!statistics)
  {
    THROW_ALGORITHM_INPUT_ERROR(" Statistics output is null pointer! ");
  }

  AlgorithmOutput output;
  output[StatisticalResults] = statistics;
  output.setAdditionalAlgoOutput(Statisticstable);

  return output;
}


/***********************************************************************************************************************
*  OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
*
*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
*  following conditions are met:
*
*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
*  disclaimer.
*
*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
*  disclaimer in the documentation and/or other materials provided with the distribution.
*
*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
*  derived from this software without specific prior written permission from the respective party.
*
*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
*  written permission from Alliance for Sustainable Energy, LLC.
*
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
***********************************************************************************************************************/

#ifndef UTILITIES_DATA_VECTOR_HPP
#define UTILITIES_DATA_VECTOR_HPP

#include "../UtilitiesAPI.hpp"

#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/vector.hpp>

namespace openstudio {

/// Workaround to get Vector typedef, http://www.gotw.ca/gotw/079.htm
struct UTILITIES_API VectorStruct
{
  typedef boost::numeric::ublas::vector<double> VectorType;
  typedef boost::numeric::ublas::scalar_vector<double> ScalarVectorType;
};

/// Vector
typedef VectorStruct::VectorType Vector;

/// ScalarVector
typedef VectorStruct::ScalarVectorType ScalarVector;

/// Helper function to construct Vector from std::vector<double>.
UTILITIES_API Vector createVector(const std::vector<double>& values);

/// Helper function to construct Vector from std::vector<long>
UTILITIES_API Vector createVector(const std::vector<long>& values);

UTILITIES_API std::vector<double> toStandardVector(const Vector& values);

//////////////////////////////////////////////////////////////////////////
// Begin SWIG'able, copy and paste into Vector.i
//////////////////////////////////////////////////////////////////////////

/** @name Operators */
//@{

UTILITIES_API bool operator==(const Vector& lhs, const Vector& rhs);
UTILITIES_API bool operator!=(const Vector& lhs, const Vector& rhs);

//@}
/** @name Interpolation */
//@{

// The following link has hints for interpolation
// http://o2scl.sourceforge.net/o2scl/html/index.html#intp_section

/** Enum to specify the interpolation method. */
enum InterpMethod{LinearInterp, NearestInterp, HoldLastInterp, HoldNextInterp};

/** Enum to specify the extrapolation method. */
enum ExtrapMethod{NoneExtrap, NearestExtrap};

/** Data structure for holding interpolation information. */
struct UTILITIES_API InterpInfo{
  bool extrapolated; // was point out of range
  unsigned ia, ib; // indices of two nearest points
  double wa, wb; // weights of two nearest points
};

/** Linear interpolation of the function y = f(x) at point xi. Assumes that x is strictly
 *  increasing. */
UTILITIES_API InterpInfo interpInfo(const Vector& x, double xi);

/** Linear interpolation of the function y = f(x) at point xi. Assumes that x is strictly
 *  increasing */
UTILITIES_API double interp(const Vector& x, const Vector& y, double xi,
                            InterpMethod interpMethod = LinearInterp,
                            ExtrapMethod extrapMethod = NoneExtrap);

/** Linear interpolation of the function y = f(x) at points xi. Assumes that x is strictly
 *  increasing. */
UTILITIES_API Vector interp(const Vector& x, const Vector& y, const Vector& xi,
                            InterpMethod interpMethod = LinearInterp,
                            ExtrapMethod extrapMethod = NoneExtrap);

//@}
/** @name Common Methods and Vector Operations */
//@{

/** Generates a Vector of N points randomly drawn between and including a and b. */
UTILITIES_API Vector randVector(double a, double b, unsigned N);

/** Generates a Vector of N points linearly spaced between and including a and b. */
UTILITIES_API Vector linspace(double a, double b, unsigned N);

/** Generates a Vector linearly spaced points starting at a and ending before or at b with
 *  interval delta. */
UTILITIES_API Vector deltaSpace(double a, double b, double delta);

/** Generates a Vector of N points logarithmically spaced between and including base^a and
 *  base^b. */
UTILITIES_API Vector logspace(double a, double b, unsigned N, double base = 10.0);

/** Take the natural logarithm of elements of a Vector. */
UTILITIES_API Vector log(const Vector& x);

/** Take the logarithm of elements of a Vector with certain base. */
UTILITIES_API Vector log(const Vector& x, double base);

/** Compute the cumulative sum of a Vector. */
UTILITIES_API Vector cumsum(const Vector& x, double runningSum = 0.0);

/** Returns the dot product between lhs and rhs. */
UTILITIES_API double dot(const Vector& lhs, const Vector& rhs);

/** Returns the sum of vector's values. */
UTILITIES_API double sum(const Vector& vector);

/** Returns the largest element of vector. */
UTILITIES_API double maximum(const Vector& vector);

/** Returns the smallest element of vector. */
UTILITIES_API double minimum(const Vector& vector);

/** Returns the mean of vector's values */
UTILITIES_API double mean(const Vector& vector);

/** Returns the sample variance of vector's values. */
UTILITIES_API double variance(const Vector& vector);

/** Returns the standard deviation of vector's values. */
UTILITIES_API double stdDev(const Vector& vector);

/** Returns std::function pointer to sum(const Vector&). */
UTILITIES_API std::function<double (const Vector&)> sumVectorFunctor();

/** Returns std::function pointer to maximum(const Vector&). */
UTILITIES_API std::function<double (const Vector&)> maximumVectorFunctor();

/** Returns std::function pointer to minimum(const Vector&). */
UTILITIES_API std::function<double (const Vector&)> minimumVectorFunctor();

/** Returns std::function pointer to mean(const Vector&). */
UTILITIES_API std::function<double (const Vector&)> meanVectorFunctor();

/** Returns std::function pointer to variance(const Vector&). */
UTILITIES_API std::function<double (const Vector&)> varianceVectorFunctor();

/** Returns std::function pointer to stdDev(const Vector&). */
UTILITIES_API std::function<double (const Vector&)> stdDevVectorFunctor();

/** Evaluates functor(vector). For use in SWIG bindings. */
UTILITIES_API double evaluateDoubleFromVectorFunctor(
    const std::function<double (const Vector&)>& functor,
    const Vector& vector);

//@}

} // openstudio

#endif //UTILITIES_DATA_VECTOR_HPP