#include <boost/format.hpp>
#include <boost/optional.hpp>
#include <boost/program_options.hpp>
#include <boost/property_tree/ptree.hpp>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <queue>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "baldr/connectivity_map.h"
#include "baldr/graphreader.h"
#include "baldr/pathlocation.h"
#include "baldr/tilehierarchy.h"
#include "loki/search.h"
#include "loki/worker.h"
#include "midgard/distanceapproximator.h"
#include "midgard/encoded.h"
#include "midgard/logging.h"
#include "odin/directionsbuilder.h"
#include "odin/enhancedtrippath.h"
#include "odin/util.h"
#include "sif/costfactory.h"
#include "thor/attributes_controller.h"
#include "thor/bidirectional_astar.h"
#include "thor/multimodal.h"
#include "thor/route_matcher.h"
#include "thor/timedep.h"
#include "thor/triplegbuilder.h"
#include "worker.h"

#include "proto/api.pb.h"
#include "proto/directions.pb.h"
#include "proto/options.pb.h"
#include "proto/trip.pb.h"

#include "config.h"

using namespace valhalla::midgard;
using namespace valhalla::baldr;
using namespace valhalla::loki;
using namespace valhalla::odin;
using namespace valhalla::sif;
using namespace valhalla::thor;
using namespace valhalla::meili;

namespace bpo = boost::program_options;

namespace {

std::string get_env(const std::string& key) {
  char* val = std::getenv(key.c_str());
  return val == nullptr ? std::string("") : std::string(val);
}

// Default maximum distance between locations to choose a time dependent path algorithm
const float kDefaultMaxTimeDependentDistance = 500000.0f;

class PathStatistics {
  std::pair<float, float> origin;
  std::pair<float, float> destination;
  std::string success;
  uint32_t passes;
  uint32_t runtime;
  uint32_t trip_time;
  float trip_dist;
  float arc_dist;
  uint32_t manuevers;
  double elapsed_cost_seconds;
  double elapsed_cost_cost;

public:
  PathStatistics(std::pair<float, float> p1, std::pair<float, float> p2)
      : origin(p1), destination(p2), success("false"), passes(0), runtime(), trip_time(), trip_dist(),
        arc_dist(), manuevers(), elapsed_cost_seconds(0), elapsed_cost_cost(0) {
  }

  void setSuccess(std::string s) {
    success = std::move(s);
  }
  void incPasses(void) {
    ++passes;
  }
  void addRuntime(uint32_t msec) {
    runtime += msec;
  }
  void setTripTime(uint32_t t) {
    trip_time = t;
  }
  void setTripDist(float d) {
    trip_dist = d;
  }
  void setArcDist(float d) {
    arc_dist = d;
  }
  void setManuevers(uint32_t n) {
    manuevers = n;
  }
  void setElapsedCostSeconds(double secs) {
    elapsed_cost_seconds = secs;
  }
  void setElapsedCostCost(double cost) {
    elapsed_cost_cost = cost;
  }
  void log() {
    valhalla::midgard::logging::Log((boost::format("%f,%f,%f,%f,%s,%d,%d,%d,%f,%f,%d,%f,%f") %
                                     origin.first % origin.second % destination.first %
                                     destination.second % success % passes % runtime % trip_time %
                                     trip_dist % arc_dist % manuevers % elapsed_cost_seconds %
                                     elapsed_cost_cost)
                                        .str(),
                                    " [STATISTICS] ");
  }
};
} // namespace

/**
 * Test a single path from origin to destination.
 */
const valhalla::TripLeg* PathTest(GraphReader& reader,
                                  valhalla::Location& origin,
                                  valhalla::Location& dest,
                                  PathAlgorithm* pathalgorithm,
                                  const mode_costing_t& mode_costing,
                                  const TravelMode mode,
                                  PathStatistics& data,
                                  bool multi_run,
                                  uint32_t iterations,
                                  bool using_astar,
                                  bool using_bd,
                                  bool match_test,
                                  const std::string& routetype,
                                  valhalla::Api& request) {
  auto t1 = std::chrono::high_resolution_clock::now();
  auto paths =
      pathalgorithm->GetBestPath(origin, dest, reader, mode_costing, mode, request.options());
  cost_ptr_t cost = mode_costing[static_cast<uint32_t>(mode)];

  // If bidirectional A*, disable use of destination only edges on the first pass.
  // If there is a failure, we allow them on the second pass.
  if (using_bd) {
    cost->set_allow_destination_only(false);
  }

  cost->set_pass(0);
  data.incPasses();
  if (paths.empty() || (routetype == "pedestrian" && pathalgorithm->has_ferry())) {
    if (cost->AllowMultiPass()) {
      LOG_INFO("Try again with relaxed hierarchy limits");
      cost->set_pass(1);
      pathalgorithm->Clear();
      const float expansion_within_factor = (using_astar) ? 4.0f : 2.0f;
      cost->RelaxHierarchyLimits(using_astar, expansion_within_factor);
      cost->set_allow_destination_only(true);
      paths = pathalgorithm->GetBestPath(origin, dest, reader, mode_costing, mode, request.options());
      data.incPasses();
    }
  }
  if (paths.empty()) {
    // Return an empty trip path
    return nullptr;
  }
  const auto& pathedges = paths.front();
  LOG_INFO("Number of alternates requested=" + std::to_string(request.options().alternates()));
  LOG_INFO("Number of paths=" + std::to_string(paths.size()));
  LOG_INFO("Number of pathedges=" + std::to_string(pathedges.size()));

  auto t2 = std::chrono::high_resolution_clock::now();
  uint32_t msecs = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
  LOG_INFO("PathAlgorithm GetBestPath took " + std::to_string(msecs) + " ms");

  // Form trip path
  t1 = std::chrono::high_resolution_clock::now();
  AttributesController controller;
  auto& trip_path = *request.mutable_trip()->mutable_routes()->Add()->mutable_legs()->Add();
  TripLegBuilder::Build(request.options(), controller, reader, mode_costing, pathedges.begin(),
                        pathedges.end(), origin, dest, std::list<valhalla::Location>{}, trip_path,
                        {pathalgorithm->name()});
  t2 = std::chrono::high_resolution_clock::now();
  msecs = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
  LOG_INFO("TripLegBuilder took " + std::to_string(msecs) + " ms");

  // Time how long it takes to clear the path
  t1 = std::chrono::high_resolution_clock::now();
  pathalgorithm->Clear();
  t2 = std::chrono::high_resolution_clock::now();
  msecs = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
  LOG_INFO("PathAlgorithm Clear took " + std::to_string(msecs) + " ms");

  // Test RouteMatcher
  if (match_test) {
    LOG_INFO("Testing RouteMatcher");

    // Get shape
    std::vector<PointLL> shape = decode<std::vector<PointLL>>(trip_path.shape());

    // Use the shape to form a single edge correlation at the start and end of
    // the shape (using heading).
    std::vector<valhalla::baldr::Location> locations{shape.front(), shape.back()};
    locations.front().heading_ = std::round(PointLL::HeadingAlongPolyline(shape, 30.f));
    locations.back().heading_ = std::round(PointLL::HeadingAtEndOfPolyline(shape, 30.f));

    std::shared_ptr<DynamicCost> cost = mode_costing[static_cast<uint32_t>(mode)];
    const auto projections = Search(locations, reader, cost);
    std::vector<PathLocation> path_location;
    valhalla::Options options;

    for (const auto& ll : shape) {
      auto* sll = options.mutable_shape()->Add();
      sll->mutable_ll()->set_lat(ll.lat());
      sll->mutable_ll()->set_lng(ll.lng());
      // set type to via by default
      sll->set_type(valhalla::Location::kVia);
    }
    // first and last always get type break
    if (options.shape_size()) {
      options.mutable_shape(0)->set_type(valhalla::Location::kBreak);
      options.mutable_shape(options.shape_size() - 1)->set_type(valhalla::Location::kBreak);
    }

    for (const auto& loc : locations) {
      path_location.push_back(projections.at(loc));
      PathLocation::toPBF(path_location.back(), options.mutable_locations()->Add(), reader);
    }
    std::vector<std::vector<PathInfo>> paths;
    bool ret = RouteMatcher::FormPath(mode_costing, mode, reader, options, paths);
    if (ret) {
      LOG_INFO("RouteMatcher succeeded");
    } else {
      LOG_ERROR("RouteMatcher failed");
    }
  }

  // Run again to see benefits of caching
  if (multi_run) {
    uint32_t total_get_best_path_ms = 0;
    uint32_t total_trip_leg_builder_ms = 0;
    for (uint32_t i = 0; i < iterations; i++) {
      t1 = std::chrono::high_resolution_clock::now();
      paths = pathalgorithm->GetBestPath(origin, dest, reader, mode_costing, mode, request.options());
      t2 = std::chrono::high_resolution_clock::now();
      total_get_best_path_ms +=
          std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();

      // Form trip leg
      t1 = std::chrono::high_resolution_clock::now();
      AttributesController controller;
      valhalla::TripLeg trip_leg;
      const auto& pathedges = paths.front();
      TripLegBuilder::Build(request.options(), controller, reader, mode_costing, pathedges.begin(),
                            pathedges.end(), origin, dest, std::list<valhalla::Location>{}, trip_leg,
                            {pathalgorithm->name()});
      t2 = std::chrono::high_resolution_clock::now();
      total_trip_leg_builder_ms +=
          std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();

      pathalgorithm->Clear();
    }
    msecs = total_get_best_path_ms / iterations;
    LOG_INFO("PathAlgorithm GetBestPath average: " + std::to_string(msecs) + " ms");
    msecs = total_trip_leg_builder_ms / iterations;
    LOG_INFO("TripLegBuilder average: " + std::to_string(msecs) + " ms");
  }
  return &request.trip().routes(0).legs(0);
}

namespace std {

// TODO: maybe move this into location.h if its actually useful elsewhere than here?
std::string to_string(const valhalla::baldr::Location& l) {
  std::string s;
  for (auto address : {&l.name_, &l.street_, &l.city_, &l.state_, &l.zip_, &l.country_}) {
    s.append(*address);
    s.push_back(',');
  }
  s.erase(s.end() - 1);
  return s;
}

// TODO: maybe move this into location.h if its actually useful elsewhere than here?
std::string to_json(const valhalla::baldr::Location& l) {
  std::string json = "{";
  json += "\"lat\":";
  json += std::to_string(l.latlng_.lat());

  json += ",\"lon\":";
  json += std::to_string(l.latlng_.lng());

  json += ",\"type\":\"";
  json += (l.stoptype_ == valhalla::baldr::Location::StopType::THROUGH) ? "through" : "break";
  json += "\"";

  if (l.heading_) {
    json += ",\"heading\":";
    json += *l.heading_;
  }

  if (!l.name_.empty()) {
    json += ",\"name\":\"";
    json += l.name_;
    json += "\"";
  }

  if (!l.street_.empty()) {
    json += ",\"street\":\"";
    json += l.street_;
    json += "\"";
  }

  if (!l.city_.empty()) {
    json += ",\"city\":\"";
    json += l.city_;
    json += "\"";
  }

  if (!l.state_.empty()) {
    json += ",\"state\":\"";
    json += l.state_;
    json += "\"";
  }

  if (!l.zip_.empty()) {
    json += ",\"postal_code\":\"";
    json += l.zip_;
    json += "\"";
  }

  if (!l.country_.empty()) {
    json += ",\"country\":\"";
    json += l.country_;
    json += "\"";
  }

  json += "}";

  return json;
}

} // namespace std

std::string GetFormattedTime(uint32_t seconds) {
  uint32_t hours = (uint32_t)seconds / 3600;
  uint32_t minutes = ((uint32_t)(seconds / 60)) % 60;
  std::string formattedTime = "";
  // Hours
  if (hours > 0) {
    formattedTime += std::to_string(hours);
    formattedTime += (hours == 1) ? " hour" : " hours";
    if (minutes > 0) {
      formattedTime += ", ";
    }
  }
  // Minutes
  if (minutes > 0) {
    formattedTime += std::to_string(minutes);
    formattedTime += (minutes == 1) ? " minute" : " minutes";
  }
  return formattedTime;
}

valhalla::DirectionsLeg DirectionsTest(valhalla::Api& api,
                                       valhalla::Location& orig,
                                       valhalla::Location& dest,
                                       PathStatistics& data,
                                       bool verbose_lanes) {
  // TEMPORARY? Change to PathLocation...
  const PathLocation& origin = PathLocation::fromPBF(orig);
  const PathLocation& destination = PathLocation::fromPBF(dest);

  DirectionsBuilder::Build(api);
  const auto& trip_directions = api.directions().routes(0).legs(0);
  EnhancedTripLeg etl(*api.mutable_trip()->mutable_routes(0)->mutable_legs(0));
  std::string units = (api.options().units() == valhalla::Options::kilometers ? "km" : "mi");
  int m = 1;
  valhalla::midgard::logging::Log("From: " + std::to_string(origin), " [NARRATIVE] ");
  valhalla::midgard::logging::Log("To: " + std::to_string(destination), " [NARRATIVE] ");
  valhalla::midgard::logging::Log("==============================================", " [NARRATIVE] ");
  for (int i = 0; i < trip_directions.maneuver_size(); ++i) {
    const auto& maneuver = trip_directions.maneuver(i);

    // Depart instruction
    if (maneuver.has_depart_instruction()) {
      valhalla::midgard::logging::Log((boost::format("   %s") % maneuver.depart_instruction()).str(),
                                      " [NARRATIVE] ");
    }

    // Verbal depart instruction
    if (maneuver.has_verbal_depart_instruction()) {
      valhalla::midgard::logging::Log((boost::format("   VERBAL_DEPART: %s") %
                                       maneuver.verbal_depart_instruction())
                                          .str(),
                                      " [NARRATIVE] ");
    }

    // Instruction
    valhalla::midgard::logging::Log((boost::format("%d: %s | %.1f %s") % m %
                                     maneuver.text_instruction() % maneuver.length() % units)
                                        .str(),
                                    " [NARRATIVE] ");

    // Turn lanes
    // Only for driving and no start/end maneuvers
    if ((maneuver.travel_mode() == valhalla::DirectionsLeg_TravelMode_kDrive) &&
        !((maneuver.type() == valhalla::DirectionsLeg_Maneuver_Type_kStart) ||
          (maneuver.type() == valhalla::DirectionsLeg_Maneuver_Type_kStartRight) ||
          (maneuver.type() == valhalla::DirectionsLeg_Maneuver_Type_kStartLeft) ||
          (maneuver.type() == valhalla::DirectionsLeg_Maneuver_Type_kDestination) ||
          (maneuver.type() == valhalla::DirectionsLeg_Maneuver_Type_kDestinationRight) ||
          (maneuver.type() == valhalla::DirectionsLeg_Maneuver_Type_kDestinationLeft))) {
      auto prev_edge = etl.GetPrevEdge(maneuver.begin_path_index());
      if (prev_edge && (prev_edge->turn_lanes_size() > 0)) {
        std::string turn_lane_status = "ACTIVE_TURN_LANES";
        if (prev_edge->HasNonDirectionalTurnLane()) {
          turn_lane_status = "NON_DIRECTIONAL_TURN_LANES";
        } else if (!prev_edge->HasActiveTurnLane()) {
          turn_lane_status = "NO_ACTIVE_TURN_LANES";
        }
        valhalla::midgard::logging::Log((boost::format("   %d: TURN_LANES: %s %s") % m %
                                         prev_edge->TurnLanesToString() % turn_lane_status)
                                            .str(),
                                        " [NARRATIVE] ");
      }
    }

    // Verbal transition alert instruction
    if (maneuver.has_verbal_transition_alert_instruction()) {
      valhalla::midgard::logging::Log((boost::format("   VERBAL_ALERT: %s") %
                                       maneuver.verbal_transition_alert_instruction())
                                          .str(),
                                      " [NARRATIVE] ");
    }

    // Verbal pre transition instruction
    if (maneuver.has_verbal_pre_transition_instruction()) {
      valhalla::midgard::logging::Log((boost::format("   VERBAL_PRE: %s") %
                                       maneuver.verbal_pre_transition_instruction())
                                          .str(),
                                      " [NARRATIVE] ");
    }

    // Verbal post transition instruction
    if (maneuver.has_verbal_post_transition_instruction()) {
      valhalla::midgard::logging::Log((boost::format("   VERBAL_POST: %s") %
                                       maneuver.verbal_post_transition_instruction())
                                          .str(),
                                      " [NARRATIVE] ");
    }

    // Arrive instruction
    if (maneuver.has_arrive_instruction()) {
      valhalla::midgard::logging::Log((boost::format("   %s") % maneuver.arrive_instruction()).str(),
                                      " [NARRATIVE] ");
    }

    // Verbal arrive instruction
    if (maneuver.has_verbal_arrive_instruction()) {
      valhalla::midgard::logging::Log((boost::format("   VERBAL_ARRIVE: %s") %
                                       maneuver.verbal_arrive_instruction())
                                          .str(),
                                      " [NARRATIVE] ");
    }

    // All turn lanes along maneuver
    if (verbose_lanes) {
      for (auto n = maneuver.begin_path_index() + 1; n < maneuver.end_path_index(); ++n) {
        auto prev_edge = etl.GetPrevEdge(n);
        auto q = n - maneuver.begin_path_index();
        if (prev_edge) {
          std::string turn_lane_status = "ACTIVE_TURN_LANES";
          if (prev_edge->HasNonDirectionalTurnLane()) {
            turn_lane_status = "NON_DIRECTIONAL_TURN_LANES";
          } else if (prev_edge->turn_lanes_size() == 0) {
            turn_lane_status = "NO_TURN_LANES";
          } else if (!prev_edge->HasActiveTurnLane()) {
            turn_lane_status = "NO_ACTIVE_TURN_LANES";
          }
          valhalla::midgard::logging::Log((boost::format("   %d-%d: TURN_LANES: %s %s") % m % q %
                                           prev_edge->TurnLanesToString() % turn_lane_status)
                                              .str(),
                                          " [NARRATIVE] ");
        }
      }
    }

    if (i < trip_directions.maneuver_size() - 1) {
      valhalla::midgard::logging::Log("----------------------------------------------",
                                      " [NARRATIVE] ");
    }

    // Increment maneuver number
    ++m;
  }
  valhalla::midgard::logging::Log("==============================================", " [NARRATIVE] ");
  valhalla::midgard::logging::Log("Total time: " + GetFormattedTime(trip_directions.summary().time()),
                                  " [NARRATIVE] ");
  valhalla::midgard::logging::Log((boost::format("Total length: %.1f %s") %
                                   trip_directions.summary().length() % units)
                                      .str(),
                                  " [NARRATIVE] ");
  if (origin.date_time_) {
    valhalla::midgard::logging::Log("Departed at: " + *origin.date_time_, " [NARRATIVE] ");
  }
  if (destination.date_time_) {
    valhalla::midgard::logging::Log("Arrived at: " + *destination.date_time_, " [NARRATIVE] ");
  }
  data.setTripTime(trip_directions.summary().time());
  data.setTripDist(trip_directions.summary().length());
  data.setManuevers(trip_directions.maneuver_size());
  data.setElapsedCostSeconds(etl.node().rbegin()->cost().elapsed_cost().seconds());
  data.setElapsedCostCost(etl.node().rbegin()->cost().elapsed_cost().cost());

  return trip_directions;
}

// Main method for testing a single path
int main(int argc, char* argv[]) {
  bpo::options_description poptions(
      "valhalla_run_route " VALHALLA_VERSION "\n"
      "\n"
      " Usage: valhalla_run_route [options]\n"
      "\n"
      "valhalla_run_route is a simple command line test tool for shortest path routing. "
      "\n"
      "Use -j option for specifying the locations and costing method and options. "
      "\n"
      "\n");

  std::string json, json_file, config;
  bool multi_run = false;
  bool match_test = false;
  bool verbose_lanes = false;
  uint32_t iterations;

  poptions.add_options()("help,h", "Print this help message.")("version,v",
                                                               "Print the version of this software.")(
      "json,j", boost::program_options::value<std::string>(&json),
      "JSON Example: "
      "'{\"locations\":[{\"lat\":40.748174,\"lon\":-73.984984,\"type\":\"break\",\"heading\":200,"
      "\"name\":\"Empire State Building\",\"street\":\"350 5th Avenue\",\"city\":\"New "
      "York\",\"state\":\"NY\",\"postal_code\":\"10118-0110\",\"country\":\"US\"},{\"lat\":40."
      "749231,\"lon\":-73.968703,\"type\":\"break\",\"name\":\"United Nations "
      "Headquarters\",\"street\":\"405 East 42nd Street\",\"city\":\"New "
      "York\",\"state\":\"NY\",\"postal_code\":\"10017-3507\",\"country\":\"US\"}],\"costing\":"
      "\"auto\",\"directions_options\":{\"units\":\"miles\"}}'")(
      "json-file", boost::program_options::value<std::string>(&json_file),
      "file containing the json query")("match-test", "Test RouteMatcher with resulting shape.")(
      "multi-run", bpo::value<uint32_t>(&iterations),
      "Generate the route N additional times before exiting.")(
      "verbose-lanes", bpo::bool_switch(&verbose_lanes),
      "Include verbose lanes output in DirectionsTest.")
      // positional arguments
      ("config", bpo::value<std::string>(&config), "Valhalla configuration file");

  bpo::positional_options_description pos_options;
  pos_options.add("config", 1);

  bpo::variables_map vm;
  try {
    bpo::store(bpo::command_line_parser(argc, argv).options(poptions).positional(pos_options).run(),
               vm);
    bpo::notify(vm);
  } catch (std::exception& e) {
    std::cerr << "Unable to parse command line options because: " << e.what() << "\n"
              << "This is a bug, please report it at " PACKAGE_BUGREPORT << "\n";
    return EXIT_FAILURE;
  }

  if (vm.count("help")) {
    std::cout << poptions << "\n";
    return EXIT_SUCCESS;
  }

  if (vm.count("version")) {
    std::cout << "valhalla_run_route " << VALHALLA_VERSION << "\n";
    return EXIT_SUCCESS;
  }

  if (vm.count("match-test")) {
    match_test = true;
  }

  if (vm.count("multi-run")) {
    multi_run = true;
  }

  if (vm.count("json-file")) {
    if (vm.count("json")) {
      LOG_WARN("json and json-file option are set, using json-file content");
    }
    std::ifstream ifs(json_file);
    json.assign((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
  }

  // Grab the directions options, if they exist
  valhalla::Api request;
  valhalla::ParseApi(json, valhalla::Options::route, request);
  const auto& options = request.options();

  // Get type of route - this provides the costing method to use.
  const std::string& routetype = valhalla::Costing_Enum_Name(options.costing());
  LOG_INFO("routetype: " + routetype);

  // Locations
  auto locations = valhalla::baldr::PathLocation::fromPBF(options.locations());
  if (locations.size() < 2) {
    throw;
  }

  // parse the config
  boost::property_tree::ptree pt;
  rapidjson::read_json(config.c_str(), pt);

  // configure logging
  boost::optional<boost::property_tree::ptree&> logging_subtree =
      pt.get_child_optional("thor.logging");
  if (logging_subtree) {
    auto logging_config =
        valhalla::midgard::ToMap<const boost::property_tree::ptree&,
                                 std::unordered_map<std::string, std::string>>(logging_subtree.get());
    valhalla::midgard::logging::Configure(logging_config);
  }
  // Something to hold the statistics
  uint32_t n = locations.size() - 1;
  PathStatistics data({locations[0].latlng_.lat(), locations[0].latlng_.lng()},
                      {locations[n].latlng_.lat(), locations[n].latlng_.lng()});
  // Crow flies distance between locations (km)
  float d1 = 0.0f;
  for (uint32_t i = 0; i < n; i++) {
    d1 += locations[i].latlng_.Distance(locations[i + 1].latlng_) * kKmPerMeter;
  }
  // Get something we can use to fetch tiles
  valhalla::baldr::GraphReader reader(pt.get_child("mjolnir"));

  // Get the maximum distance for time dependent routes
  float max_timedep_distance =
      pt.get<float>("service_limits.max_timedep_distance", kDefaultMaxTimeDependentDistance);

  auto t0 = std::chrono::high_resolution_clock::now();

  // Construct costing
  CostFactory factory;
  // Get the costing method - pass the JSON configuration
  TravelMode mode;
  auto mode_costing = factory.CreateModeCosting(options, mode);

  // Find path locations (loki) for sources and targets
  auto tw0 = std::chrono::high_resolution_clock::now();
  loki_worker_t lw(pt);
  auto tw1 = std::chrono::high_resolution_clock::now();
  auto msw = std::chrono::duration_cast<std::chrono::milliseconds>(tw1 - tw0).count();
  LOG_INFO("Location Worker construction took " + std::to_string(msw) + " ms");

  auto tl0 = std::chrono::high_resolution_clock::now();
  lw.route(request);
  auto tl1 = std::chrono::high_resolution_clock::now();
  auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(tl1 - tl0).count();
  LOG_INFO("Location Processing took " + std::to_string(ms) + " ms");

  // Get the route
  TimeDepForward astar;
  BidirectionalAStar bd;
  MultiModalPathAlgorithm mm;
  TimeDepForward timedep_forward;
  TimeDepReverse timedep_reverse;
  for (uint32_t i = 0; i < n; i++) {
    // Set origin and destination for this segment
    valhalla::Location origin = options.locations(i);
    valhalla::Location dest = options.locations(i + 1);

    PointLL ll1(origin.ll().lng(), origin.ll().lat());
    PointLL ll2(dest.ll().lng(), dest.ll().lat());

    // Choose path algorithm
    PathAlgorithm* pathalgorithm;
    if (routetype == "multimodal") {
      pathalgorithm = &mm;
    } else {
      // Use time dependent algorithms if date time is present
      // TODO - this isn't really correct for multipoint routes but should allow
      // simple testing.
      if (options.has_date_time() && ll1.Distance(ll2) < max_timedep_distance &&
          (options.date_time_type() == valhalla::Options_DateTimeType_depart_at ||
           options.date_time_type() == valhalla::Options_DateTimeType_current)) {
        pathalgorithm = &timedep_forward;
      } else if (options.date_time_type() == valhalla::Options_DateTimeType_arrive_by &&
                 ll1.Distance(ll2) < max_timedep_distance) {
        pathalgorithm = &timedep_reverse;
      } else {
        // Use bidirectional except for trivial cases (same edge or connected edges)
        pathalgorithm = &bd;
        for (auto& edge1 : origin.path_edges()) {
          for (auto& edge2 : dest.path_edges()) {
            if (edge1.graph_id() == edge2.graph_id() ||
                reader.AreEdgesConnected(GraphId(edge1.graph_id()), GraphId(edge2.graph_id()))) {
              pathalgorithm = &astar;
            }
          }
        }
      }
    }
    bool using_astar = (pathalgorithm == &astar || pathalgorithm == &timedep_forward ||
                        pathalgorithm == &timedep_reverse);
    bool using_bd = pathalgorithm == &bd;

    // Get the best path
    const valhalla::TripLeg* trip_path = nullptr;
    try {
      trip_path = PathTest(reader, origin, dest, pathalgorithm, mode_costing, mode, data, multi_run,
                           iterations, using_astar, using_bd, match_test, routetype, request);
    } catch (std::runtime_error& rte) { LOG_ERROR("trip_path not found"); }

    // If successful get directions
    if (trip_path && trip_path->node_size() != 0) {

      // Write the path.pbf if requested
      if (get_env("SAVE_PATH_PBF") == "true") {
        std::string path_bytes = request.SerializeAsString();
        std::string pbf_filename = "path.pbf";
        LOG_INFO("Writing TripPath to " + pbf_filename + " with size " +
                 std::to_string(path_bytes.size()));
        std::ofstream output_pbf(pbf_filename, std::ios::out | std::ios::trunc | std::ios::binary);
        if (output_pbf.is_open() && path_bytes.size() > 0) {
          output_pbf.write(&path_bytes[0], path_bytes.size());
          output_pbf.close();
        } else {
          std::cerr << "Failed to write " << pbf_filename << std::endl;
          return EXIT_FAILURE;
        }
      }

      // Try the the directions
      auto t1 = std::chrono::high_resolution_clock::now();
      const auto& trip_directions = DirectionsTest(request, origin, dest, data, verbose_lanes);
      auto t2 = std::chrono::high_resolution_clock::now();
      auto msecs = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();

      auto trip_time = trip_directions.summary().time();
      auto trip_length = trip_directions.summary().length() * 1609.344f;
      LOG_INFO("trip_processing_time (ms)::" + std::to_string(msecs));
      LOG_INFO("trip_time (secs)::" + std::to_string(trip_time));
      LOG_INFO("trip_length (meters)::" + std::to_string(trip_length));
      data.setSuccess("success");
    } else {
      // Route was unsuccessful
      data.setSuccess("fail_no_route");
    }
  }

  // Set the arc distance. Convert to miles if needed
  if (options.units() == valhalla::Options::miles) {
    d1 *= kMilePerKm;
  }
  data.setArcDist(d1);

  // Time all stages for the stats file: location processing,
  // path computation, trip path building, and directions
  auto t2 = std::chrono::high_resolution_clock::now();
  auto msecs = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t0).count();
  LOG_INFO("Total time= " + std::to_string(msecs) + " ms");
  data.addRuntime(msecs);
  data.log();

  // Shutdown protocol buffer library
  google::protobuf::ShutdownProtobufLibrary();

  return EXIT_SUCCESS;
}


#include <lanelet2_core/primitives/Area.h>
#include <lanelet2_core/utility/Units.h>
#include <lanelet2_traffic_rules/TrafficRules.h>
#include <lanelet2_traffic_rules/TrafficRulesFactory.h>
#include <boost/python.hpp>

using namespace boost::python;
using namespace lanelet;
using namespace lanelet::traffic_rules;

SpeedLimitInformation makeSpeedLimit(double speedLimitKph, bool isMandatory) {
  return SpeedLimitInformation{Velocity(speedLimitKph * units::KmH()), isMandatory};
}

double getVelocity(const SpeedLimitInformation& self) { return units::KmHQuantity(self.speedLimit).value(); }

void setVelocity(SpeedLimitInformation& self, double velocityKmh) {
  self.speedLimit = Velocity(velocityKmh * units::KmH());
}

template <typename T>
bool canPassWrapper(const TrafficRules& self, const T& llt) {
  return self.canPass(llt);
}
template <typename T1, typename T2>
bool canPassFromToWrapper(const TrafficRules& self, const T1& from, const T2& to) {
  return self.canPass(from, to);
}

template <typename T>
SpeedLimitInformation speedLimitWrapper(const TrafficRules& self, const T& llt) {
  return self.speedLimit(llt);
}
bool isOneWayWrapper(const TrafficRules& self, const ConstLanelet& llt) { return self.isOneWay(llt); }
bool hasDynamicRulesWrapper(const TrafficRules& self, const ConstLanelet& llt) { return self.hasDynamicRules(llt); }

TrafficRulesPtr createTrafficRulesWrapper(const std::string& location, const std::string& participant) {
  return TrafficRulesFactory::create(location, participant);
}

template <const char Val[]>
std::string asString() {
  return Val;
}

BOOST_PYTHON_MODULE(PYTHON_API_MODULE_NAME) {  // NOLINT
  auto core = import("lanelet2.core");

  class_<SpeedLimitInformation>("SpeedLimitInformation", "Current speed limit as returned by a traffic rule object")
      .def("__init__", makeSpeedLimit,
           "Initialize from speed limit [m/s] and bool if speedlimit is "
           "mandatory")
      .add_property("speedLimit", getVelocity, setVelocity, "velocity in km/h")
      .add_property("isMandatory", &SpeedLimitInformation::isMandatory,
                    "True if speedlimit is not just a recommendation")
      .def(self_ns::str(self_ns::self));

  class_<TrafficRules, boost::noncopyable, std::shared_ptr<TrafficRules>>("TrafficRules", no_init)
      .def("canPass", canPassWrapper<ConstLanelet>, "Returns whether it is allowed to pass/drive on this lanelet")
      .def("canPass", canPassWrapper<ConstArea>, "Returns whether it is allowed to pass/drive on this area")
      .def("canPass", canPassFromToWrapper<ConstLanelet, ConstLanelet>,
           "Returns whether it is allowed to drive from first to second lanelet")
      .def("canPass", canPassFromToWrapper<ConstLanelet, ConstArea>)
      .def("canPass", canPassFromToWrapper<ConstArea, ConstArea>)
      .def("canPass", canPassFromToWrapper<ConstArea, ConstLanelet>)
      .def("canChangeLane", &TrafficRules::canChangeLane,
           "determines if a lane change can be made between two lanelets")
      .def("speedLimit", speedLimitWrapper<ConstLanelet>, "get speed limit of this lanelet")
      .def("speedLimit", speedLimitWrapper<ConstArea>, "get speed limit of this lanelet")
      .def("isOneWay", isOneWayWrapper, "returns whether a lanelet can be driven in one direction only")
      .def("hasDynamicRules", hasDynamicRulesWrapper,
           "returns whether dynamic traffic rules apply to this lanelet that "
           "can not be understood by this traffic rules object")
      .def("location", &TrafficRules::location, return_value_policy<copy_const_reference>(),
           "Location these rules are valid for")
      .def("participant", &TrafficRules::participant, return_value_policy<copy_const_reference>(),
           "Participants the rules are valid for")
      .def(self_ns::str(self_ns::self));

  class_<Locations>("Locations").add_static_property("Germany", asString<Locations::Germany>);

  class_<Participants>("Participants")
      .add_static_property("Vehicle", asString<Participants::Vehicle>)
      .add_static_property("VehicleCar", asString<Participants::VehicleCar>)
      .add_static_property("VehicleCarElectric", asString<Participants::VehicleCarElectric>)
      .add_static_property("VehicleCarCombustion", asString<Participants::VehicleCarCombustion>)
      .add_static_property("VehicleBus", asString<Participants::VehicleBus>)
      .add_static_property("VehicleTruck", asString<Participants::VehicleTruck>)
      .add_static_property("VehicleMotorcycle", asString<Participants::VehicleMotorcycle>)
      .add_static_property("VehicleTaxi", asString<Participants::VehicleTaxi>)
      .add_static_property("VehicleEmergency", asString<Participants::VehicleEmergency>)
      .add_static_property("Bicycle", asString<Participants::Bicycle>)
      .add_static_property("Pedestrian", asString<Participants::Pedestrian>)
      .add_static_property("Train", asString<Participants::Train>);

  def("create", createTrafficRulesWrapper,
      "Create a traffic rules object from location and participant string (see "
      "Locations and Participants class");
}


#include <algorithm>
#include <atomic>
#include <cstdlib>
#include <iostream>
#include <string>
#include <stdexcept>
#include <thread>
#include <unordered_set>
#include <vector>

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

#include <tbb/parallel_for.h>

#include "slide/KurageBoard.hpp"
#include "slide/KurageSolver.hpp"

#include "util/define.hpp"
#include "util/Random.hpp"
#include "util/StopWatch.hpp"

struct Config
{
    std::size_t H;
    std::size_t W;
    int selectionLimit;
    bool verbose;
};

Config parseCommand(int argc, const char* const argv[])
{
    namespace po = boost::program_options;

    po::options_description opt("Allowed options");
    Config config;

    opt.add_options()
        ("help",                                                                             "print this help message")
        ("height,h",              po::value<std::size_t>(&config.H)->default_value(4u),      "height of the board")
        ("width,w",               po::value<std::size_t>(&config.W)->default_value(4u),      "width of the board")
        ("max_selection_limit,l", po::value<int>(&config.selectionLimit)->default_value(16), "maximum limit time of the selection")
        ("verbose,v",                                                                        "A lot printing")
    ;

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

    // show help
    if(vm.count("help")){
        std::cerr << opt << std::endl;
        std::exit(EXIT_SUCCESS);
    }

    config.verbose = vm.count("verbose");
    return config;
}

slide::Problem setProblem(int selectionLimit)
{
    const int swappingCost   = util::Random::nextInt(MIN_SWAPPING_COST,   MAX_SWAPPING_COST);
    const int selectionCost  = util::Random::nextInt(MIN_SELECTION_COST,  MAX_SELECTION_COST);

    slide::Problem problem(8, 8, swappingCost, selectionCost, selectionLimit);
    problem.board = slide::Board<slide::Flexible>::randomState(8, 8);

    return problem;
}

double measure()
{
    std::atomic_int sum(0);
    std::atomic_int cnt(0);

    tbb::parallel_for(0, 100, [&](int){
        bool solved = false;
        slide::KurageSolver solver(setProblem(2));
        solver.retry = false;

        solver.onCreatedAnswer = [&](const slide::Answer& answer){
            if(!solved){
                sum += answer.size();
                ++cnt;
                std::cerr << answer.size() << ' ';
                solved = true;
            }
        };

        solver.solve();
        if(!solved){
            std::cerr << "! ";
        }
    });

    const double score = double(sum.load()) / cnt.load();
    std::cerr << "\navg = " << score << std::endl;
    return score;
}

int main(int argc, const char* const argv[])
{
    using KurageBoard = slide::KurageBoard<8, 8>;

    const Config config = parseCommand(argc, argv);
    slide::KurageSolver::verbose = false;

    const float coef[5] = {15.0f, 1.0f, 30.0f, 12.0f, 2.0f};
    std::copy_n(coef, 5, KurageBoard::coefficients);

    for(int c=0;; ++c){
        double preScore = 1e100;
        double increment = true;

        for(int r=0; r<20; ++r){

            const int param_id = 3 - c % 4;
            const float pre = KurageBoard::coefficients[param_id];
            KurageBoard::coefficients[param_id] *= (increment ? 1.5f : 0.7f) * (20-r) / 20.0;

            for(float c : KurageBoard::coefficients){
                std::cout << c << ", ";
            }
            std::cout << std::endl;

            const double score = measure();
            if(score > preScore){
                KurageBoard::coefficients[param_id] = pre;
                increment = !increment;
            }
            else{
                preScore = score;
            }
            std::cerr << std::endl;
        }
    }

    return EXIT_SUCCESS;
}


#include "neuon/neuon_c.h"

#include "neuon_cxx.h"
#include "configuration.h"

#include "composer.h"
#include "speech_detection.h"
#include "face_detection.h"
#include "model.h"
#include "tensorflow_api.h"
#include "tensorflow_static.h"
#include "license.h"
#include "demo.h"
#include "log.h"
#include "birthday.h"

#include <boost/filesystem.hpp>

#include "dlib/image_processing.h"

#include <json/json.h>

struct neuon_context_t{
    std::unique_ptr<neuon::neuon_t> engine;
};

uint64_t neuon_estimate_target_audio_samplerate(neuon_microseconds_t video_frame_duration) {
    return neuon::target_audio_samplerate(std::chrono::microseconds(video_frame_duration));
}

neuon_context_t *neuon_create_engine(const char *model_filename, const char *norma_filename, const char *landmark_filepath, const char *license_filename, const neuon_options_t *options, neuon_logging_t* app_log, neuon_user_data_t *user_data) {
    if(!app_log){
        return nullptr;
    }

    auto log = neuon::log_t(app_log);

    log.debug() << "Try to open license file if available...";

    std::string license_payload;
    if (license_filename) {
        std::ifstream license_file(license_filename);
        if(license_file){
            std::getline(license_file, license_payload);
        }
    }

    log.debug() << "Try to read license information...";
    common::license_t license(license_payload);
    if (license.demo()) {
        common::demo_t demo(neuon::birthday);
        if(demo.has_expired){
            log.error() << "Demo time period limit exceeded! Please contact the seller.";
            return nullptr;
        }
    }

    log.debug() << "Try to open neural network model...";
    std::ifstream model_file(model_filename, std::ios::binary);
    if(!model_file){
        log.error() << "Neural network model file is not available. Check the path and permissions for " << model_filename;
        return nullptr;
    }

    log.debug() << "Try to open face landmark database...";
    std::ifstream landmark_file(landmark_filepath, std::ios::binary);
    if (!landmark_file) {
        log.error() << "Face landmark database file is not available. Check the path and permissions for " << landmark_filepath;
        return nullptr;
    }

    log.debug() << "Try to open normalization parameters...";
    std::ifstream norma_file(norma_filename, std::ios::binary);
    if (!norma_file) {
        log.error() << "Normalization parameters file is not available. Check the path and permissions for " << norma_filename;
        return nullptr;
    }

    log.debug() << "Read normalization parameters...";
    Json::Value root;
    norma_file >> root;

    neuon::normalization_t normalization{};
    normalization.audio.min = root["audio"]["min"].asDouble();
    normalization.audio.max = root["audio"]["max"].asDouble();
    normalization.audio.mean = root["audio"]["mean"].asDouble();
    normalization.audio.std = root["audio"]["std"].asDouble();

    normalization.video.min = root["video"]["min"].asDouble();
    normalization.video.max = root["video"]["max"].asDouble();
    normalization.video.mean = root["video"]["mean"].asDouble();
    normalization.video.std = root["video"]["std"].asDouble();

    log.debug() << "Face detection engine initialization...";
    std::unique_ptr<neuon::face_detection_t> face_detection(new neuon::face_detection_t(neuon::video_width_per_entry, neuon::video_height_per_entry, landmark_file));

    log.debug() << "Speech detection engine initialization...";
    const std::chrono::microseconds audio_slice(static_cast<uint64_t>(std::round(neuon::fft_samples_per_entry * 1000000.0 / options->target_audio_samplerate)));
    std::unique_ptr<neuon::speech_detection_t> speech_detection(new neuon::speech_detection_t(options->target_audio_samplerate, audio_slice, neuon::audio_slice_overlap, neuon::audio_features_per_entry));

    if(speech_detection->sample_count != neuon::fft_samples_per_entry){
        log.error() << "Engine could not initializate speech detection engine properly due to rounding error. Report an issue and provide the media file causing the issue. ";
        return nullptr;
    }

    log.debug() << "Read neural network model...";
    std::vector<uint8_t> model_payload(boost::filesystem::file_size(model_filename));
    model_file.read(reinterpret_cast<char *>(model_payload.data()), model_payload.size());

    log.debug() << "Load neural network model...";
    std::shared_ptr<neuon::model_t> model = std::make_shared<neuon::model_t>(std::make_shared<tensorflow::static_backend_t>(), model_payload, normalization);

    log.debug() << "Initialize A/V Sync Engine...";
    std::unique_ptr<neuon::composer_t> composer(
        new neuon::composer_t(
            neuon::video_frames_per_entry,
            neuon::audio_samples_per_entry,
            [model, user_data, log](
                const dlib::matrix<uint8_t> &video_set,
                const std::chrono::microseconds &first_video_pts,
                const std::chrono::microseconds &last_video_pts,
                const dlib::matrix<double> &audio_set,
                const std::chrono::microseconds &first_audio_pts,
                const std::chrono::microseconds last_audio_pts) {

                    log.debug() << "The next data sample is evaluated. Video PTS: " << first_video_pts.count() << " - "<< last_video_pts.count() << "Audio PTS : " << first_audio_pts.count() << " - "<< last_audio_pts.count();
                    auto prediction = model->predict(video_set, {1, neuon::video_frames_per_entry, neuon::video_height_per_entry, neuon::video_width_per_entry, 1}, audio_set, {1, neuon::audio_derivation_per_entry, neuon::audio_samples_per_entry, neuon::audio_features_per_entry, 1});
                    neuon_outcome_t outcome{neuon_microseconds_t(first_video_pts.count()), prediction[0]};
                    user_data->on_result(user_data, &outcome);
                }
            )
        );

    log.debug() << "Engine content initialization.";
    return new neuon_context_t{std::unique_ptr<neuon::neuon_t>{new neuon::neuon_t{std::move(face_detection), std::move(speech_detection), std::move(composer)}}};
}

void neuon_free_engine(neuon_context_t * context) {
    delete context;
}

void neuon_put_video(const neuon_context_t *context, const uint8_t *p, size_t bytes, size_t width, size_t height, size_t stride, neuon_microseconds_t pts) {
    neuon::video_sample_t sample{p, bytes, width, height, stride, std::chrono::microseconds(pts)};
    context->engine->put(sample);
}

void neuon_put_audio(const neuon_context_t *context, const uint8_t *p, size_t bytes, size_t samples, neuon_microseconds_t pts) {
    neuon::audio_sample_t sample{p, bytes, samples, std::chrono::microseconds(pts)};
    context->engine->put(sample);
}


#include "plot_file.h"
#include "plot_file_math.h"

#include <regex>

#include <boost/tokenizer.hpp>
#include <boost/format.hpp>
#include <boost/filesystem.hpp>

PlotFile::PlotFile( const boost::filesystem::path & filePath )
{
    Initialize( filePath );
}

PlotFile::PlotFile( const PlotFileParams & params, const boost::filesystem::path & directory )
{
    EXCEPTION_ASSERT( boost::filesystem::exists( directory ) &&
        boost::filesystem::is_directory( directory ) );
    EXCEPTION_ASSERT( params.staggerSizeInNonces_ > 0 );
    params_ = params;
    boost::filesystem::path filePath = directory /
        BuildFileNameWithoutSuffix();

    Initialize( filePath );
}

void PlotFile::StartCreation( Operation op )
{
    EXCEPTION_ASSERT( op == Operation::Optimization ); // Remove this when more operations are available
    EXCEPTION_ASSERT( status_ == PossibleStatuses::NotPresent );
    std::string filePath = BuildFilePathWithSuffix( optimizationSuffix_ );
    status_ = PossibleStatuses::OptimizingInProgress;

    // Open file and close it (basically create it)
    stream_.open( filePath, std::ios_base::out | std::ios_base::binary );
    stream_.close();

    // Reserve space
    boost::filesystem::resize_file( boost::filesystem::path( filePath ),
        PlotFileMath::CalcPlotFileSize( params_ ) );

    // Open it again
    stream_.open( filePath, std::ios_base::out | std::ios_base::binary );
}

void PlotFile::FinishCreation()
{
    stream_.close();

    boost::filesystem::path oldFilePath = BuildFilePathWithSuffix( optimizationSuffix_ );
    boost::filesystem::path newFilePath = BuildFilePathWithSuffix();
    boost::filesystem::rename( oldFilePath, newFilePath );

    bool longValidation = false;
    if ( IsValid( newFilePath, longValidation ) )
    {
        status_ = PossibleStatuses::Valid;
    }
    else
    {
        throw std::logic_error( "Plot file building failed" );
    }
}

uint64_t PlotFile::Read(uint64_t staggerNum, uint64_t scoopNum, char * data)
{
    EXCEPTION_ASSERT(nullptr != data);
    EXCEPTION_ASSERT(PossibleStatuses::Valid == status_);
    uint64_t expectedSize = PlotFileMath::CalcScoopRegionSizeInBytes(params_);
    stream_.seekg( PlotFileMath::CalcScoopStartOffsetInBytes( params_, staggerNum, scoopNum ) );
    stream_.read(data, expectedSize);
    uint64_t bytesRead = stream_.gcount();
    EXCEPTION_ASSERT(expectedSize == bytesRead);
    return bytesRead;
}

void PlotFile::Write(uint64_t staggerNum, uint64_t scoopNum, const char * data)
{
    EXCEPTION_ASSERT(nullptr != data);
    uint64_t expectedSize = PlotFileMath::CalcScoopRegionSizeInBytes(params_);
    stream_.seekg( PlotFileMath::CalcScoopStartOffsetInBytes( params_, staggerNum, scoopNum ) );
    stream_.write(data, expectedSize);
}

/*
Common part of object initialization
*/
inline void PlotFile::Initialize( const boost::filesystem::path & filePath )
{
    if( boost::filesystem::exists( filePath ) )
    {
        if( boost::filesystem::is_regular_file( filePath ) )
        {
            bool longValidation = false;
            status_ = IsValid( filePath, longValidation ) ? PossibleStatuses::Valid : PossibleStatuses::Corrupted;
        }
        else
        {
            throw std::logic_error( "PlotFile filePath constructor: filePath " +
                filePath.string() + " refers "
                "to something that exists but is not a file." );
        }
    }
    else
    {
        status_ = PossibleStatuses::NotPresent;
    }
    PlotFileParams params = ExtractParamsFromFilePath( filePath );
    EXCEPTION_ASSERT( params.nonceNumRange_.SizeInNonce() % params.staggerSizeInNonces_ == 0 );
    EXCEPTION_ASSERT( params.nonceNumRange_.SizeInNonce() >= params.staggerSizeInNonces_ );
    params_ = params;
    filePathWithoutSuffix_ = filePath;
    stream_.exceptions( std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit );
    if (status_ == PossibleStatuses::Valid)
    {
        stream_.open( BuildFilePathWithSuffix(), std::ios_base::in | std::ios_base::binary );
    }
}

inline std::string PlotFile::BuildFileNameWithoutSuffix()
{
    return BuildPlotFileNameForParams( params_ );
}

std::string PlotFile::BuildFilePathWithSuffix( const std::string& suffix /* = "" */ ) const
{
    // TODO use BuildCanonicalFilePath()?
    return filePathWithoutSuffix_.string() + suffix;
}

inline PlotFileParams PlotFile::ExtractParamsFromFilePath( const boost::filesystem::path & filePath )
{
    EXCEPTION_ASSERT( IsNameValid( filePath ) );
    boost::filesystem::path fileName = filePath.filename();
    typedef boost::tokenizer<boost::char_separator<char> >
        tokenizer;
    tokenizer tokens( fileName.string(), boost::char_separator<char>( "_" ) );
    std::vector<std::string> tokensVector;
    std::copy( tokens.begin(), tokens.end(), std::back_inserter( tokensVector ) );
    EXCEPTION_ASSERT( tokensVector.size() == 4 );

    uint64_t accountNumericId = std::stoull( tokensVector.at( 0 ) );
    uint64_t startNonceNum = std::stoull( tokensVector.at( 1 ) );
    uint64_t sizeInNonce = std::stoull( tokensVector.at( 2 ) );
    uint64_t staggerSizeInNonces = std::stoull( tokensVector.at( 3 ) );
    return PlotFileParams( accountNumericId, NonceNumRange( startNonceNum, sizeInNonce ), staggerSizeInNonces );
}

inline bool PlotFile::IsValid( const boost::filesystem::path & filePath, bool longValidation )
{
    EXCEPTION_ASSERT( !longValidation ); // Long validation is not implemented yet
    PlotFileParams params = ExtractParamsFromFilePath( filePath );
    bool valid = ( params.nonceNumRange_.SizeInNonce() % params.staggerSizeInNonces_ ) == 0;
    valid = valid || ( params.nonceNumRange_.SizeInNonce() >= params.staggerSizeInNonces_ );
    return valid;
}

bool PlotFile::IsNameValid( const boost::filesystem::path& filePath )
{
    /*
     * Name is considered valid if:
     * it has all four numbers separated by a single underscore,
     * file name may have suffix but it has the following limitation:
     *    if suffix is present, it consists of a dot plus at least one letter
     *    (other symbols are not allowed)
     *    cannot have just a dot
     */
    std::string fileName = filePath.filename().string();
    std::regex regex( R"(\d+_\d+_\d+_\d+(\.[a-zA-Z]+)?)" );
    return std::regex_match( fileName, regex );
}

bool PlotFile::operator==( const PlotFile& rhs )
{
    return this->BuildCanonicalFilePath() == rhs.BuildCanonicalFilePath();
}

std::string PlotFile::BuildCanonicalFilePath() const
{
    return boost::filesystem::canonical( filePathWithoutSuffix_ ).string();
}

bool PlotFile::operator!=( const PlotFile& rhs )
{
    return !( *this == rhs );
}

bool PlotFile::operator<( const PlotFile& rhs )
{
    return BuildCanonicalFilePath() < rhs.BuildCanonicalFilePath();
}

// TODO test it somehow?
bool PlotFile::DoesItBelongToDirectory( const boost::filesystem::path& directory ) const
{
    return !boost::filesystem::relative( FileNameWithPath(), boost::filesystem::canonical( directory ) ).empty();
}

bool PlotFile::IsFullyOptimized() const
{
    return params_.nonceNumRange_.SizeInNonce() == params_.staggerSizeInNonces_;
}

std::string PlotFile::BuildPlotFileNameForParams( const PlotFileParams& params )
{
    return ( boost::format( "%d_%d_%d_%d" ) %
        params.accountNumericId_ %
        params.nonceNumRange_.StartNonceNum() %
        params.nonceNumRange_.SizeInNonce() %
        params.staggerSizeInNonces_ ).str();
}


/*! \file demo_1d_vector.cpp
    \brief Simple plot of vector of 1D data.
    \details An example to demonstrate simple 1D plot using two vectors,
     see also demo_1d_containers for examples using other STL containers.
    \author Jacob Voytko & Paul A. Bristow
    \date Feb 2009
*/

// Copyright Jacob Voytko 2007
// Copyright Paul A Bristow 2008, 2009

// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

// This file is written to be included from a Quickbook .qbk document.
// It can be compiled by the C++ compiler, and run. Any output can
// also be added here as comment or included or pasted in elsewhere.
// Caution: this file contains Quickbook markup as well as code
// and comments: don't change any of the special comment markups!

//[demo_1d_vector_1

/*`First we need a few includes to use Boost.Plot:
*/
#include <boost/svg_plot/svg_1d_plot.hpp>
  using namespace boost::svg;
#include <vector>
  using std::vector;
//] [/demo_1d_vector_1]

int main()
{
//[demo_1d_vector_2

/*`STL vector is used as the container for our two data series,
and values are inserted using push_back.  Since this is a 1-D plot
the order of data values is not important.
*/
  vector<double> dan_times;
  dan_times.push_back(3.1);
  dan_times.push_back(4.2);

  vector<double> elaine_times;
  elaine_times.push_back(2.1);
  elaine_times.push_back(7.8);

/*`The constructor initializes a new 1D plot, called `my_plot`, and also sets all the very many defaults for axes, width, colors, etc.
*/
  svg_1d_plot my_plot;

/*`A few (member) functions that set are fairly self-explanatory:

* title provides a title at the top for the whole plot,
* `legend_on(true)` will mean that titles of data series and markers will display in the legend box.
* `x_range(-1, 11)` sets the axis limits from -1 to +11 (instead of the default -10 to +10).
* `background_border_color(blue)` sets just one of the very many options.
*/

  my_plot.background_border_color(blue)
    .legend_on(true)
    .title("Race Times")
    .x_range(-1, 11);

  my_plot.legend_lines(true);

/*`The syntax `my_plot.title("Hello").legend_on(true)...` may appear unfamiliar,
but is a convenient way of specifying many parameters in any order. It is equivalent to:
``
  my_plot.title("Race Times");
  my_plot.legend_on(true);
  my_plot.x_range(-1, 11);
  my_plot.background_border_color(blue);
``
Chaining thus allows you to avoid repeatedly typing "`myplot.`"
and easily group related settings like plot window, axes ... together.
A fixed order would clearly become impracticable with
hundreds of possible arguments needed to set all the myriad plot options.

Within all of the plot classes, 'chaining' works the same way,
by returning a reference to the calling object thus `return  *this;`

Then we need to add our data series,
and add optional (but very helpful) data series titles
if we want them to show on the legend.
*/

  my_plot.plot(dan_times, "Dan").shape(circlet).size(10).stroke_color(red).fill_color(green);
  my_plot.plot(elaine_times, "Elaine").shape(vertical_line).stroke_color(blue);

/*`Finally, we can write the SVG to a file of our choice.
*/

  my_plot.write("./demo_1d_vector.svg");
//] [/demo_1d_vector_2]

  return 0;
} // int main()

/*

Output:
//[demo_1d_vector_output

Compiling...
demo_1d_vector.cpp
Linking...
Embedding manifest...
Autorun "j:\Cpp\SVG\debug\demo_1d_vector.exe"
Build Time 0:04
//] [/demo_1d_vector_output]
*/


// Boost.Geometry (aka GGL, Generic Geometry Library)
// This file is manually converted from PROJ4

// Copyright (c) 2008-2012 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017, 2018, 2019.
// Modifications copyright (c) 2017-2019, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Geometry Library by Barend Gehrels (Geodan, Amsterdam)

// Original copyright notice:

// 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.

#ifndef BOOST_GEOMETRY_PROJECTIONS_IMPL_PJ_FWD_HPP
#define BOOST_GEOMETRY_PROJECTIONS_IMPL_PJ_FWD_HPP

#include <boost/geometry/core/radian_access.hpp>
#include <boost/geometry/util/math.hpp>

#include <boost/geometry/srs/projections/impl/adjlon.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>

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

/* general forward projection */

namespace boost { namespace geometry { namespace projections {

namespace detail {

/* forward projection entry */
template <typename Prj, typename LL, typename XY, typename P>
inline void pj_fwd(Prj const& prj, P const& par, LL const& ll, XY& xy)
{
    typedef typename P::type calc_t;
    static const calc_t EPS = 1.0e-12;

    using namespace detail;

    calc_t lp_lon = geometry::get_as_radian<0>(ll);
    calc_t lp_lat = geometry::get_as_radian<1>(ll);
    calc_t const t = geometry::math::abs(lp_lat) - geometry::math::half_pi<calc_t>();

    /* check for forward and latitude or longitude overange */
    if (t > EPS || geometry::math::abs(lp_lon) > 10.)
    {
        BOOST_THROW_EXCEPTION( projection_exception(error_lat_or_lon_exceed_limit) );
    }

    if (geometry::math::abs(t) <= EPS)
    {
        lp_lat = lp_lat < 0. ? -geometry::math::half_pi<calc_t>() : geometry::math::half_pi<calc_t>();
    }
    else if (par.geoc)
    {
        lp_lat = atan(par.rone_es * tan(lp_lat));
    }

    lp_lon -= par.lam0;    /* compute del lp.lam */
    if (! par.over)
    {
        lp_lon = adjlon(lp_lon); /* post_forward del longitude */
    }

    calc_t x = 0;
    calc_t y = 0;

    prj.fwd(par, lp_lon, lp_lat, x, y);

    geometry::set<0>(xy, par.fr_meter * (par.a * x + par.x0));
    geometry::set<1>(xy, par.fr_meter * (par.a * y + par.y0));
}

} // namespace detail
}}} // namespace boost::geometry::projections

#endif // BOOST_GEOMETRY_PROJECTIONS_IMPL_PJ_FWD_HPP


// Boost.Geometry

// Copyright (c) 2020, Oracle and/or its affiliates.

// Contributed and/or modified by Vissarion Fysikopoulos, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_STRATEGIES_CONVEX_HULL_SPHERICAL_HPP
#define BOOST_GEOMETRY_STRATEGIES_CONVEX_HULL_SPHERICAL_HPP

#include <boost/geometry/strategies/convex_hull/services.hpp>
#include <boost/geometry/strategies/detail.hpp>
#include <boost/geometry/strategies/spherical/ssf.hpp>

namespace boost { namespace geometry
{

namespace strategies { namespace convex_hull
{

template <typename CalculationType = void>
class spherical : public strategies::detail::spherical_base<void>
{
public:
    static auto side()
    {
        return strategy::side::spherical_side_formula<CalculationType>();
    }
};

namespace services
{

template <typename Geometry>
struct default_strategy<Geometry, spherical_equatorial_tag>
{
    using type = strategies::convex_hull::spherical<>;
};

} // namespace services

}} // namespace strategies::convex_hull

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_CONVEX_HULL_SPHERICAL_HPP