/**
 * Copyright (c) 2014-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#include "Decision.h"

#include <chrono>
#include <string>

#include <boost/functional/hash.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/property_map/property_map.hpp>
#include <fbzmq/service/logging/LogSample.h>
#include <fbzmq/service/stats/ThreadData.h>
#include <fbzmq/zmq/Zmq.h>
#include <folly/Format.h>
#include <folly/MapUtil.h>
#include <folly/Memory.h>
#include <folly/Optional.h>
#include <folly/String.h>
#include <gflags/gflags.h>

#include <openr/common/AddressUtil.h>
#include <openr/common/Util.h>

using namespace std;

using apache::thrift::FRAGILE;

using Weight = uint64_t;
using Graph = boost::adjacency_list<
    boost::listS,
    boost::vecS,
    boost::directedS,
    boost::no_property,
    boost::property<boost::edge_weight_t, Weight>>;
using VertexDescriptor = boost::graph_traits<Graph>::vertex_descriptor;
using EdgeDescriptor = boost::graph_traits<Graph>::edge_descriptor;
using EdgeProperty = boost::property<boost::edge_weight_t, Weight>;
using IndexMap = boost::property_map<Graph, boost::vertex_index_t>::type;
using WeightMap = boost::property_map<Graph, boost::edge_weight_t>;
using PredecessorMap = boost::iterator_property_map<
    VertexDescriptor*,
    IndexMap,
    VertexDescriptor,
    VertexDescriptor&>;
using DistanceMap =
    boost::iterator_property_map<Weight*, IndexMap, Weight, Weight&>;

namespace openr {

namespace {

using NexthopAdj = std::pair<thrift::Adjacency, Weight>;

// Default HWM is 1k. We set it to 0 to buffer all received messages.
const int kStoreSubReceiveHwm{0};

struct NodeData {
  std::string nodeName;

  bool isOverloaded{false};

  int32_t nodeLabel{0};

  unordered_map<
      std::pair<
          std::string /* remoteIfName */,
          std::string /* remoteNodeName */>,
      thrift::Adjacency>
      adjDb;

  std::unordered_set<thrift::IpPrefix> prefixes;

  bool
  isAdjacent(std::string const& otherIfName, std::string const& otherNodeName) {
    return adjDb.count({otherIfName, otherNodeName});
  }

  bool
  isNeighbor(std::string const& otherNodeName) {
    for (auto const& kv : adjDb) {
      auto const& neighbor = kv.first.second;
      if (neighbor == otherNodeName) {
        return true;
      }
    }
    return false;
  }

  // Update adjDb, at the same time check if drain status of router/adjacency
  // changes, if so return true to trigger SPF recalculation
  bool
  updateAdjacencyDb(
      thrift::AdjacencyDatabase adjacencyDb,
      std::vector<std::tuple<std::string, std::string, std::string>>& toUpdate,
      std::vector<std::tuple<std::string, std::string, std::string>>& toDel) {
    DCHECK(nodeName == adjacencyDb.thisNodeName)
        << "call updateAdjacencyDb on wrong node!";
    // Check if there's any node/adjcency overload, if so trigger SPF
    bool triggerSpf = (isOverloaded != adjacencyDb.isOverloaded) or
      (nodeLabel != adjacencyDb.nodeLabel);

    std::unordered_set<
        std::pair<std::string /* remote interface */, std::string /* node */>>
        newAdj;

    for (auto const& adj : adjacencyDb.adjacencies) {
      auto const& remoteIfName = getRemoteIfName(adj);
      newAdj.emplace(std::make_pair(remoteIfName, adj.otherNodeName));

      // if already in adjDb, check if drain status changed
      if (isAdjacent(remoteIfName, adj.otherNodeName)) {
        VLOG(3) << nodeName << " and neighbor " << adj.otherNodeName
                << " is verified adjacent on remote IfName " << remoteIfName;
        auto& it = adjDb.at(std::make_pair(remoteIfName, adj.otherNodeName));
        if (it != adj) {
          // Update cached adjDb
          VLOG(3) << "Adjacency exsists and is found updated";
          it = adj;
          toUpdate.push_back(
            std::make_tuple(adj.ifName, adj.otherIfName, adj.otherNodeName));
        }
      } else {
        // Update cached adjDb
        VLOG(3) << "Adjacency between " << nodeName
                << " and " << adj.otherNodeName
                << " on interface " << remoteIfName << " is freshly inserted";
        adjDb.emplace(std::make_pair(remoteIfName, adj.otherNodeName), adj);
        toUpdate.push_back(
          std::make_tuple(adj.ifName, adj.otherIfName, adj.otherNodeName));
      }
    }

    for (auto it = adjDb.begin(); it != adjDb.end();) {
      if (newAdj.count(it->first) == 0) {
        toDel.push_back(std::make_tuple(
          it->second.ifName, it->second.otherIfName, it->second.otherNodeName));
        // Update cahced adjDb
        it = adjDb.erase(it);
      } else {
        ++it;
      }
    }

    // update AdjacencyDatabase attributes
    isOverloaded = adjacencyDb.isOverloaded;
    nodeLabel = adjacencyDb.nodeLabel;
    return triggerSpf;
  }

  bool
  updatePrefixDb(std::unordered_set<thrift::IpPrefix> prefixDb) {
    bool prefixChange = false;
    // check if there's any prefix to be removed
    for (auto const& prefix : prefixes) {
      if (prefixDb.count(prefix) == 0) {
        prefixChange = true;
        break;
      }
    }
    // check if there's any new prefix to be added
    for (auto const& prefix : prefixDb) {
      if (prefixes.count(prefix) == 0) {
        prefixChange = true;
        break;
      }
    }

    // Update cached prefixes
    prefixes = prefixDb;
    return prefixChange;
  }
};

/**
 * Create a route with single path.
 */
folly::Optional<thrift::Route>
createRoute(
    const thrift::IpPrefix& prefix,
    const thrift::Adjacency& adjacency,
    Weight metric,
    bool enableV4) {
  auto addr = toIPAddress(prefix.prefixAddress);
  if (addr.isV4() and !enableV4) {
    LOG(ERROR) << "Received v4 prefix while v4 is not enabled.";
    return nullptr;
  }

  thrift::Path path(
      FRAGILE,
      addr.isV4() ? adjacency.nextHopV4 : adjacency.nextHopV6,
      adjacency.ifName,
      metric);
  return thrift::Route(FRAGILE, prefix, {std::move(path)});
}

/**
 * Create multi-path route, prefix with set of loop-free next-hops. If any
 * error occurs then the returned value will be empty and error will be logged.
 */
folly::Optional<thrift::Route>
createRouteMulti(
    const thrift::IpPrefix& prefix,
    const vector<pair<thrift::Adjacency, Weight>>& adjacencies,
    bool enableV4) {
  auto addr = toIPAddress(prefix.prefixAddress);
  if (addr.isV4() and !enableV4) {
    LOG(ERROR) << "Received v4 prefix while v4 is not enabled.";
    return nullptr;
  }

  if (adjacencies.empty()) {
    return nullptr;
  }

  std::set<thrift::Path> paths;
  for (auto const& kv : adjacencies) {
    auto const& adjacency = kv.first;
    paths.insert(thrift::Path(
        FRAGILE,
        addr.isV4() ? adjacency.nextHopV4 : adjacency.nextHopV6,
        adjacency.ifName,
        kv.second /* metric */));
  }

  return thrift::Route(
      FRAGILE, prefix, vector<thrift::Path>(paths.begin(), paths.end()));
}

} // anonymous namespace

/**
 * Private implementation of the SpfSolver
 */
class SpfSolver::SpfSolverImpl {
 public:
  explicit SpfSolverImpl(bool enableV4) : enableV4_(enableV4) {}

  ~SpfSolverImpl() = default;

  bool updateAdjacencyDatabase(thrift::AdjacencyDatabase const& adjacencyDb);
  bool deleteAdjacencyDatabase(const std::string& nodeName);
  std::unordered_map<std::string /* nodeName */, thrift::AdjacencyDatabase>
  getAdjacencyDatabases();
  bool updatePrefixDatabase(thrift::PrefixDatabase const& prefixDb);
  bool deletePrefixDatabase(const std::string& nodeName);
  std::unordered_map<std::string /* nodeName */, thrift::PrefixDatabase>
  getPrefixDatabases();

  thrift::RouteDatabase buildShortestPaths(const std::string& myNodeName);
  thrift::RouteDatabase buildMultiPaths(const std::string& myNodeName);
  thrift::RouteDatabase buildRouteDb(const std::string& myNodeName);

  /**
   * Graph is prepared as followed
   * 1. Add all node vertices (adjDb_.keys())
   * 2. Use adjDb to add edges between nodes. bidirectional check is
   *    enforced here.
   *
   * Bidirectional Check: An edge between node will be considered only when
   * both nodes reports each other. Otherwise it is ignored.
   *
   * Prepare data structures that we use for SPF run and it can be re-used
   * across multiple SPF computations.
   */
  void prepareGraph();

  std::unordered_map<std::string, int64_t> getCounters();

  fbzmq::ThreadData&
  getThreadData() noexcept {
    return tData_;
  }

 private:
  // no copy
  SpfSolverImpl(SpfSolverImpl const&) = delete;
  SpfSolverImpl& operator=(SpfSolverImpl const&) = delete;

  // run SPF and produce map from node name to next-hop and metric to reach it
  unordered_map<
      string /* other node name */,
      pair<string /* next-hop node name */, Weight /* metric */>>
  runSpf(const std::string& myNodeName);

  // bidirectionally verify newly added/deleted interfaces
  // When adjacency update is received, we enforce the bidirectional check and
  // only trigger SPF re-calculation when there's actual link change.
  // For example, for a graph like:
  // node1: intf1 -------- node2: intf2
  // When we firstly receive adjacency update from node1 declaring that node2
  // is added at local interface intf1, we don't trigger SPF immediately
  // because node1 --- node2 is not fully brought up yet. We only trigger SPF
  // when node2 also updates its adjacency with nodes1 added at node2's
  // local interface intf2.
  // Similarly, when the link is brought down, we trigger SPF recalculation
  // immediately when node1 is declaring node2 is no longer its neighbor,
  // and we don't trigger spf caclulation again when node2 reports node1 as
  // down on it's intf2 following previous event as the adjacency is no
  // longer bi-directional
  bool bidirectionalAdjacencyCheck(
      const std::string& nodeName,
      const std::vector<std::tuple<
          std::string /* local intf */,
          std::string /* remote intf */,
          std::string /* remote node */>>& toUpdate,
      const std::vector<std::tuple<
          std::string /* local intf */,
          std::string /* remote intf */,
          std::string /* remote node */>>& toDel);

  // this is the new data structure to support adjacencyDb updates, prefix
  // update and corresponding bidirectional check
  std::unordered_map<std::string /* nodeName */, NodeData> nodeData_;

  // the graph we operate on for SPF
  Graph graph_;

  // helpers for mapping router names to descriptros and vice versa
  unordered_map<string /* router name */, VertexDescriptor> nameToDescriptor_;
  unordered_map<VertexDescriptor, string /* router name */> descriptorToName_;

  // compare two adjacencies
  struct AdjComparator {
    bool
    operator()(
        const thrift::Adjacency& lhs, const thrift::Adjacency& rhs) const {
      return lhs.metric < rhs.metric;
    }
  };

  // compare two next-hop adjacencies
  struct NexthopAdjComparator {
    bool
    operator()(const NexthopAdj& lhs, const NexthopAdj& rhs) const {
      return lhs.first.metric < rhs.first.metric;
    }
  };

  // Adjacencies indexed by originating router and destination router
  // Note: use multiset, not set, since there are duplicates, i.e., adjacencies
  // with same metric
  unordered_map<
      pair<string /* thisRouterName */, string /* otherRouterName */>,
      multiset<
          thrift::Adjacency,
          AdjComparator> /* adjacencies ordered by non-descreasing metric */>
      adjacencyIndex_;

  // Save all direct next-hop distance from a given source node to a destination
  // node. We update it as we compute all LFA routes from perspective of source
  std::unordered_map<
      std::string /* node name */,
      std::unordered_map<
          std::string /* destination node */,
          std::map<
              std::string /* nexthop node */,
              Weight /* metric value from source node to destination node */>>>
      allDistFromNode_;

  // track some stats
  fbzmq::ThreadData tData_;

  // is v4 enabled. If yes then Decision will forward v4 prefixes with v4
  // nexthops to Fib module for programming. Else it will just drop them.
  const bool enableV4_{false};
};

bool
SpfSolver::SpfSolverImpl::updateAdjacencyDatabase(
    thrift::AdjacencyDatabase const& adjacencyDb) {
  auto const& nodeName = adjacencyDb.thisNodeName;
  VLOG(2) << "Updating adjacency database for node " << nodeName;
  tData_.addStatValue("decision.adj_db_update", 1, fbzmq::COUNT);

  for (auto const& adj : adjacencyDb.adjacencies) {
    VLOG(3) << "  nbr: " << adj.otherNodeName << ", remoteIfName: "
            << getRemoteIfName(adj)
            << ", ifName: " << adj.ifName << ", metric: " << adj.metric
            << ", overloaded: " << adj.isOverloaded << ", rtt: " << adj.rtt;
  }

  // Insert if not exists
  if (nodeData_.count(nodeName) == 0) {
    // This is a new node inserted in nodeData.adjDb, create entry in adjDb
    NodeData newNode;
    newNode.nodeName = adjacencyDb.thisNodeName;
    newNode.isOverloaded = adjacencyDb.isOverloaded;
    newNode.nodeLabel = adjacencyDb.nodeLabel;
    nodeData_.emplace(nodeName, newNode);
  }

  std::vector<std::tuple<
      std::string /* local intf */,
      std::string /* remote intf */,
      std::string /* remote node */>> toUpdate;
  std::vector<std::tuple<
      std::string /* local intf */,
      std::string /* remote intf */,
      std::string /* remote node */>> toDel;
  // Check if there's any node/adjcency overload, if so trigger SPF
  // updateAdjacencyDb only returns true if both ends of the adjacencies are
  // bidirectionally verified
  if (nodeData_.at(nodeName).updateAdjacencyDb(adjacencyDb, toUpdate, toDel)) {
    VLOG(3) << "Node label or overload attribute changed for " << nodeName;
    return true;
  }

  // If nothing changed, no need to trigger SPF
  if (toUpdate.empty() and toDel.empty()) {
    VLOG(3) << "Nothing to update in adjDb";
    return false;
  }

  return bidirectionalAdjacencyCheck(nodeName, toUpdate, toDel);
}

bool
SpfSolver::SpfSolverImpl::bidirectionalAdjacencyCheck(
    const std::string& nodeName,
    const std::vector<std::tuple<
        std::string /* local intf */,
        std::string /* remote intf */,
        std::string /* remote node */>>& toUpdate,
    const std::vector<std::tuple<
        std::string /* local intf */,
        std::string /* remote intf */,
        std::string /* remote node */>>& toDel) {
  // Check if newly added adjacencies is bidirectional. If so, trigger SPF
  for (auto const& adj : toUpdate) {
    auto const& ifName = std::get<0>(adj);
    auto const& otherIfName = std::get<1>(adj);
    auto const& otherNodeName = std::get<2>(adj);
    if (nodeData_.count(otherNodeName) == 0) {
      continue;
    }
    auto otherNodeIt = nodeData_.find(otherNodeName);
    // For newly added adjacencies, we only re-caculate SPF when current
    // node is also present in adjDb of added adjacencies
    // otherwise this adjacency update is not bidirectional verified
    // SPF won't get affected
    if (otherNodeIt->second.isAdjacent(ifName, nodeName)) {
      VLOG(2) << "Adding adjacency between " << nodeName << " and other node "
              << otherNodeName << " is bidirectionally verified on interface "
              << ifName;
      return true;
    }
    // if otherIfName is empty, we do loose bidirectional check:
    // if otherNodeName is neighbor of myNodeName, we assume this is a
    // bidirectional verified neighbor
    if (otherIfName.empty() && otherNodeIt->second.isNeighbor(nodeName)) {
      VLOG(2) << "Adding adjacency between " << nodeName << " and other node "
              << otherNodeName << " is bidirectionally verified as neighbors";
      return true;
    }
  }

  for (auto const& adj : toDel) {
    auto const& ifName = std::get<0>(adj);
    auto const& otherIfName = std::get<1>(adj);
    auto const& otherNodeName = std::get<2>(adj);
    if (nodeData_.count(otherNodeName) == 0) {
      continue;
    }
    auto otherNodeIt = nodeData_.find(otherNodeName);
    // For deleted adjacencies, we only re-caculated SPF when current node
    // is still present in adjDb of any of deleted adjacencies
    if (otherNodeIt->second.isAdjacent(ifName, nodeName)) {
      VLOG(2) << "Deleting adjacency between " << nodeName << " and other node "
              << otherNodeName << " is bidirectionally verified on interface "
              << ifName;
      return true;
    }
    // The following is to make sure to keep backward compatibility with
    // previous version of openr, in which remote interface is not specified in
    // newly discovered neighbor.
    // if remote interface is not specified, we do loose bidirectional check:
    // myNodeName is already discovered as the neighbor of remote node,
    // we assume this is a bidirectional verified neighbor
    if (otherIfName.empty() && otherNodeIt->second.isNeighbor(nodeName)) {
      VLOG(2) << "Deleting adjacency between " << nodeName << " and other node "
              << otherNodeName << " is bidirectionally verified as neighbors";
      return true;
    }
  }

  return false;
}

bool
SpfSolver::SpfSolverImpl::deleteAdjacencyDatabase(const std::string& nodeName) {
  auto nodeIt = nodeData_.find(nodeName);

  if (nodeIt == nodeData_.end()) {
    LOG(WARNING) << "Trying to delete adjacency db for nonexisting node "
                 << nodeName;
    return false;
  }
  nodeIt->second.adjDb.clear();
  // reset
  nodeIt->second.isOverloaded = false;
  nodeIt->second.nodeLabel = 0;
  if (nodeIt->second.adjDb.empty() and nodeIt->second.prefixes.empty()) {
    nodeData_.erase(nodeName);
  }
  return true;
}

std::unordered_map<std::string /* nodeName */, thrift::AdjacencyDatabase>
SpfSolver::SpfSolverImpl::getAdjacencyDatabases() {
  std::unordered_map<std::string, thrift::AdjacencyDatabase> adjacencyDbs;
  for (auto const& kv : nodeData_) {
    auto const& nodeName = kv.first;
    auto const& nodeInfo = kv.second;
    thrift::AdjacencyDatabase adjDb;
    adjDb.thisNodeName = nodeInfo.nodeName;
    adjDb.isOverloaded = nodeInfo.isOverloaded;
    adjDb.nodeLabel = nodeInfo.nodeLabel;
    for (auto const& adj : nodeInfo.adjDb) {
      adjDb.adjacencies.emplace_back(adj.second);
    }
    adjacencyDbs.emplace(nodeName, adjDb);
  }
  return adjacencyDbs;
}

bool
SpfSolver::SpfSolverImpl::updatePrefixDatabase(
    thrift::PrefixDatabase const& prefixDb) {
  auto const& nodeName = prefixDb.thisNodeName;
  VLOG(2) << "Updating prefix database for node " << nodeName;
  tData_.addStatValue("decision.prefix_db_update", 1, fbzmq::COUNT);

  std::unordered_set<thrift::IpPrefix> prefixes;
  for (auto const& prefixEntry : prefixDb.prefixEntries) {
    prefixes.insert(prefixEntry.prefix);
  }

  if (nodeData_.count(nodeName) == 0) {
    NodeData newNode;
    newNode.nodeName = nodeName;
    newNode.prefixes = prefixes;
    nodeData_.emplace(nodeName, newNode);
    // When prefix-db is added before adjacency-db, we won't have any route
    // to other nodes even after SPF computation
    return false;
  }

  return nodeData_.at(nodeName).updatePrefixDb(prefixes);
}

bool
SpfSolver::SpfSolverImpl::deletePrefixDatabase(const std::string& nodeName) {
  auto nodeIt = nodeData_.find(nodeName);
  if (nodeIt == nodeData_.end()) {
    LOG(INFO) << "Trying to delete prefix db for nonexisting node " << nodeName;
    return false;
  }
  if (nodeIt->second.prefixes.empty()) {
    LOG(INFO) << "Trying to delete empty prefix db for node " << nodeName;
    return false;
  }

  nodeIt->second.prefixes.clear();
  if (nodeIt->second.adjDb.empty() and nodeIt->second.prefixes.empty()) {
    nodeData_.erase(nodeName);
  }
  return true;
}

std::unordered_map<std::string /* nodeName */, thrift::PrefixDatabase>
SpfSolver::SpfSolverImpl::getPrefixDatabases() {
  // prefixes per advertising router
  std::unordered_map<std::string /* nodeName */, thrift::PrefixDatabase>
      prefixDbs;

  // construct prefix dbs from nodeData_.prefixes
  for (auto const& kv : nodeData_) {
    const auto& nodeName = kv.first;
    const auto& prefixes = kv.second.prefixes;
    auto& prefixDb = prefixDbs[nodeName];
    prefixDb.thisNodeName = nodeName;
    for (auto const& prefix : prefixes) {
      prefixDb.prefixEntries.emplace_back(
          apache::thrift::FRAGILE, prefix, thrift::PrefixType::LOOPBACK, "");
    }
  }

  return prefixDbs;
}

void
SpfSolver::SpfSolverImpl::prepareGraph() {
  VLOG(2) << "SpfSolver::SpfSolverImpl::prepareGraph() called.";

  graph_.clear();
  nameToDescriptor_.clear();
  descriptorToName_.clear();
  adjacencyIndex_.clear();

  // Create a set of all unidirectional adjacencies, which is from one node to
  // another one connected thru one remote interface
  std::unordered_set<std::tuple<
      std::string /* myNodeName*/,
      std::string /* otherNodeName */,
      std::string /* otherIfName*/>>
      presentAdjacencies;

  // Create a set of all unidirectional adjacencies, just for those with old
  // version of spark payload to maintain backward compatibility
  // TODO: remove logic related to this data structure in the next release
  std::unordered_set<
      std::pair<std::string /* myNodeName*/, std::string /* otherNodeName */>>
      compatiblePresentAdjacencies;

  for (auto const& kv : nodeData_) {
    auto const& thisNodeName = kv.first;
    auto const& adjacencies = kv.second.adjDb;

    for (auto const& adjacency : adjacencies) {
      // Skip adjacency from SPF graph if it is overloaded to avoid transit
      // traffic through it.
      if (adjacency.second.isOverloaded) {
        continue;
      }

      if (adjacency.second.otherIfName.empty() or
          adjacency.second.otherIfName.find("neigh-") == 0) {
        // maintain backward compatibility
        compatiblePresentAdjacencies.insert(
            std::make_pair(thisNodeName, adjacency.second.otherNodeName));
      } else {
        presentAdjacencies.insert(std::make_tuple(
            thisNodeName,
            adjacency.second.otherNodeName,
            adjacency.second.otherIfName));
      }
    }
  }

  // Build adjacencyIndex_ with bidirectional check enforced
  for (auto const& kv : nodeData_) {
    auto const& thisNodeName = kv.first;
    auto const& adjacencies = kv.second.adjDb;

    // walk all my ajdacencies
    for (auto const& myAdjacency : adjacencies) {
      auto const& otherNodeName = myAdjacency.second.otherNodeName;
      auto const& thisIfName = myAdjacency.second.ifName;

      // Skip if overloaded
      if (myAdjacency.second.isOverloaded) {
        continue;
      }

      // Check for reverse adjacency
      bool isBidir = presentAdjacencies.count(
          std::make_tuple(otherNodeName, thisNodeName, thisIfName));
      bool compatibleIsBidir = compatiblePresentAdjacencies.count(
          std::make_pair(otherNodeName, thisNodeName));
      if (!isBidir and !compatibleIsBidir) {
        VLOG(2) << "Failed to find matching adjacency for `" << thisNodeName
                << "' in `" << otherNodeName
                << "' thru interface " << thisIfName;
        continue;
      }

      adjacencyIndex_[{thisNodeName, otherNodeName}].insert(myAdjacency.second);
    } // for adjDb
  } // for nodeData_

  // 1. Add all node vertices
  for (auto const& kv : nodeData_) {
    auto const& nodeName = kv.first;
    auto const& descriptor = boost::add_vertex(graph_);
    nameToDescriptor_[nodeName] = descriptor;
    descriptorToName_[descriptor] = nodeName;
  }

  // 2. Add edges between node vertices
  for (const auto& kv : adjacencyIndex_) {
    const auto& thisNodeName = kv.first.first;
    const auto& otherNodeName = kv.first.second;
    const auto& adjacencies = kv.second;

    // this will not throw by virtue of building the name to descriptor map.
    // bidirectional check is enforced
    auto const& srcVertex = nameToDescriptor_.at(thisNodeName);
    auto const& dstVertex = nameToDescriptor_.at(otherNodeName);

    // Is thisNode overloaded. If node is overloaded then we bump up metric
    // values of it's outgoing edges by `kOverloadNodeMetric`. We use this
    // information later on to deduce if a shortest path is going over
    // overloaded node or not.
    // NOTE: link's metric can never be greater than kOverloadNodeMetric
    uint64_t metric = adjacencies.begin()->metric;
    if (nodeData_.at(thisNodeName).isOverloaded) {
      metric += Constants::kOverloadNodeMetric;
    }

    // for two nodes with multiple adjacencies in between, use the min metric
    // as their edge metric
    boost::add_edge(srcVertex, dstVertex, EdgeProperty(metric), graph_);
  } // for adjacencyIndex_

} // prepareGraph

/**
 * Compute shortest-path routes from perspective of myNodeName; NOTE: you
 * need to call prepareGraph() to initialize the graph structure before
 * calling this method.
 */
unordered_map<
    string /* otherVertexName (node) */,
    pair<string /* nextHopVertexName (node) */, Weight>>
SpfSolver::SpfSolverImpl::runSpf(const std::string& myNodeName) {
  tData_.addStatValue("decision.spf_runs", 1, fbzmq::COUNT);

  // map of other nodes, next-hops and distances to reach to them
  unordered_map<
      string /* otherVertexName */,
      pair<string /* nextHopVertexName */, Weight>>
      result;

  // Find our node-vertex-descriptor before proceeding
  auto it = nameToDescriptor_.find(myNodeName);
  if (it == nameToDescriptor_.end()) {
    LOG(ERROR) << "Could not find descriptor for router `" << myNodeName << "`";
    return result;
  }
  auto const& myVertex = it->second;

  // Refer here for example of BGL Dijkstra use:
  // http://programmingexamples.net/wiki/Boost/BGL/DijkstraComputePath

  // holds predecessors for vertices in shortest-path tree
  // i.e. indexed by vertex and gives you the vertex that
  // precedes this one on the path toward the SPT root
  vector<VertexDescriptor> pred(boost::num_vertices(graph_));

  // distances from each vertex to the root of SPT
  vector<Weight> dist(boost::num_vertices(graph_));

  // build SPT from this vertex
  IndexMap indexMap = boost::get(boost::vertex_index, graph_);
  PredecessorMap predMap(&pred[0], indexMap);
  DistanceMap distMap(&dist[0], indexMap);
  boost::dijkstra_shortest_paths(
      graph_, myVertex, boost::distance_map(distMap).predecessor_map(predMap));

  // walk all the nodes in the topology and compute paths to their prefixes
  for (auto const& kv : nameToDescriptor_) {
    auto const& otherVertexName = kv.first;
    auto const& otherVertex = kv.second;

    // skip self
    if (otherVertexName == myNodeName) {
      continue;
    }

    // indirectly connected, trace the path
    auto prevVertex = otherVertex;
    auto currVertex = pred[otherVertex];

    if (currVertex == prevVertex) {
      // unreachable from the root of the SPT
      continue;
    }

    while (currVertex != myVertex) {
      prevVertex = currVertex;
      currVertex = pred[currVertex];
    }

    // at this point prevVertex -> to our nextHop
    auto nextHopVertexName = descriptorToName_[prevVertex];
    result[otherVertexName] = {nextHopVertexName, dist[otherVertex]};
  } // for nameToDescriptor_

  // Print SPF table for debugging
  VLOG(4) << "SPF table for " << myNodeName;
  for (auto const& kv : result) {
    auto const& otherVertexName = kv.first;
    auto const& nextHopVertexName = kv.second.first;
    auto const& weight = kv.second.second;
    VLOG(4) << folly::sformat(
        "  {} via {}, cost {}", otherVertexName, nextHopVertexName, weight);
  }

  return result;
}

thrift::RouteDatabase
SpfSolver::SpfSolverImpl::buildShortestPaths(const std::string& myNodeName) {
  VLOG(4) << "SpfSolver::buildShortestPaths for " << myNodeName;
  tData_.addStatValue("decision.paths_build_requests", 1, fbzmq::COUNT);

  thrift::RouteDatabase routeDb;
  routeDb.thisNodeName = myNodeName;
  if (nodeData_.count(myNodeName) == 0) {
    LOG(ERROR) << "Couldn't find node-database for myself: `" << myNodeName
               << "`, skipping spf run";
    return routeDb;
  }

  const auto startTime = std::chrono::steady_clock::now();

  prepareGraph();
  auto spfMap = runSpf(myNodeName);
  const bool myselfOverloaded = nodeData_.at(myNodeName).isOverloaded;

  // Load balance traffic to a given prefix advertised from two or more nodes
  // Combine nexthops towards all nodes bounded to the same prefix and pick
  // the smallest from them for loop-free routing
  unordered_map<
      thrift::IpPrefix, /* prefix */
      multiset<
          std::pair<thrift::Adjacency, Weight>,
          NexthopAdjComparator>
          /* adjacencies ordered by non-descreasing metric */>
      prefixToNextHops;

  for (auto const& kv : nodeData_) {
    auto const& thisNodeName = kv.first;
    if (thisNodeName == myNodeName) {
      continue;
    }
    // Find minimum nexthop to reach that node
    auto nodeIt = spfMap.find(thisNodeName);
    if (nodeIt == spfMap.end()) {
      VLOG(2) << "Skipping unreachable node " << thisNodeName;
      continue;
    }

    auto const& nextHopNodeName = nodeIt->second.first;
    auto metric = nodeIt->second.second;

    // Subtract overload metric value if myself is overloaded
    if (myselfOverloaded) {
      CHECK_LT(Constants::kOverloadNodeMetric, metric);
      metric -= Constants::kOverloadNodeMetric;
    }

    // Skip route if it is going through a node which is overloaded
    if (metric > Constants::kOverloadNodeMetric) {
      VLOG(2) << "Skipping shortest route to node " << thisNodeName
              << " because it is via an overloaded node.";
      continue;
    }

    // Add route for each prefix bounded to this node via the nextHopNodeName
    auto const& nhAdjs = adjacencyIndex_.at({myNodeName, nextHopNodeName});
    for (auto const& prefix : kv.second.prefixes) {
      prefixToNextHops[prefix].insert(std::make_pair(*nhAdjs.begin(), metric));
    }
  } // for nodeData_

  for (auto const& kv : prefixToNextHops) {
    auto const& prefix = kv.first;
    // pick up nexthop with minimum metric
    auto const& nhAdj = *kv.second.begin();
    auto route =
        createRoute(prefix, nhAdj.first, nhAdj.second /* metric */, enableV4_);
    if (route) {
      routeDb.routes.emplace_back(std::move(*route));
    }
  }

  auto deltaTime = std::chrono::duration_cast<std::chrono::milliseconds>(
      std::chrono::steady_clock::now() - startTime);
  LOG(INFO) << "Decision::buildShortestPaths took " << deltaTime.count()
            << "ms.";
  tData_.addStatValue(
      "decision.spf.shortestpath_ms", deltaTime.count(), fbzmq::AVG);

  return routeDb;
} // buildShortestPaths

thrift::RouteDatabase
SpfSolver::SpfSolverImpl::buildMultiPaths(const std::string& myNodeName) {
  VLOG(4) << "SpfSolver::buildMultiPaths for " << myNodeName;
  tData_.addStatValue("decision.paths_build_requests", 1, fbzmq::COUNT);

  thrift::RouteDatabase routeDb;
  routeDb.thisNodeName = myNodeName;
  if (nodeData_.count(myNodeName) == 0) {
    LOG(ERROR) << "Couldn't find node-database for myself: " << myNodeName
               << ", skipping multi-spf run";
    return routeDb;
  }

  auto const& startTime = std::chrono::steady_clock::now();

  // reset next-hops info
  std::unordered_map<
    std::string /* destination-node */,
    std::map<
        std::string /* nexthop node */,
        Weight /* metric value from nexthop-node to destination-node */>>
    nodeDist;

  prepareGraph();
  auto prepareTime = std::chrono::duration_cast<std::chrono::milliseconds>(
      std::chrono::steady_clock::now() - startTime);
  LOG(INFO) << "Decision::prepareGraph took " << prepareTime.count() << "ms.";

  auto mySpfPaths = runSpf(myNodeName);

  // avoid duplicate iterations over a neighbor which can happen due to multiple
  // adjacencies to it
  std::unordered_set<std::string /* adjacent node name */> visitedAdjNodes;

  // Go over all neighbors and find shortest distance of prefixes from them
  for (auto const& adj : nodeData_.at(myNodeName).adjDb) {
    auto const& adjNodeName = adj.second.otherNodeName;

    // Skip if already visited before
    if (not visitedAdjNodes.insert(adjNodeName).second) {
      VLOG(4) << "Adjacent neighbor " << adjNodeName << " has been considered.";
      continue;
    }

    // Skip if we don't have reverse edge from adjNodeName->myNodeName
    if (!adjacencyIndex_.count({adjNodeName, myNodeName})) {
      VLOG(2) << "Neighbor " << adjNodeName << " doesn't have reverse edge "
              << "to myself " << myNodeName << ". Skipping spf run for it.";
      continue;
    }

    // Update cost from this node to itself
    nodeDist[adjNodeName][adjNodeName] = 0;

    // Run SPF for the node
    VLOG(4) << "Running SPF for neighbor: " << adjNodeName;
    auto nbrSpfPaths = runSpf(adjNodeName);

    // Walk through all nodes and find shortest distance from myNodeName
    // to the prefix via adjNodeName
    for (auto const& kv : nodeData_) {
      auto const& thisNodeName = kv.first;
      if (thisNodeName == myNodeName) {
        continue;
      }

      // Skip if node is unreachable from neighbor
      auto nodeIt = nbrSpfPaths.find(thisNodeName);
      if (nodeIt == nbrSpfPaths.end()) {
        continue; // Node is unreachable from neighbor
      }

      // Get distances from adjNodeName
      auto adjNodeDist = nodeIt->second.second;
      auto adjMyNodeDist = nbrSpfPaths.at(myNodeName).second;

      // Get optimal distance of thisNodeName from us. Node must be
      // reachable from us because it is reachable from our neighbor
      auto myNodeDist = mySpfPaths.at(thisNodeName).second;

      // A neighbor (N) can provide loop free alternate to destination (D) from
      // source (S) if and only if
      //   Distance_opt(N, D) < Distance_opt(N, S) + Distance_opt(S, D)
      // For more info read: https://tools.ietf.org/html/rfc5286
      // NOTE: We are using negated condition here to skip the neighbor
      if (adjNodeDist >= adjMyNodeDist + myNodeDist) {
        VLOG(4) << "Skipping non LFA nexthop " << adjNodeName << " to "
                << "node " << thisNodeName;
        continue;
      }

      VLOG(4) << "Adding LFA nexthop " << adjNodeName << " for node "
              << thisNodeName;
      nodeDist[thisNodeName][adjNodeName] = adjNodeDist;
    } // nodeData_
  } // nodeData_[myNodeName].adjDb

  auto deltaTime = std::chrono::duration_cast<std::chrono::milliseconds>(
      std::chrono::steady_clock::now() - startTime);
  LOG(INFO) << "Decision::buildMultiPaths took " << deltaTime.count() << "ms.";
  tData_.addStatValue(
      "decision.spf.multipath_ms", deltaTime.count(), fbzmq::AVG);

  // Update allDistFromNode_ for myNodeName
  allDistFromNode_[myNodeName] = nodeDist;

  return buildRouteDb(myNodeName);

} // buildMultiPaths

thrift::RouteDatabase
SpfSolver::SpfSolverImpl::buildRouteDb(const std::string& myNodeName) {
  VLOG(4) << "SpfSolver::buildRouteDb for " << myNodeName;

  tData_.addStatValue("decision.route_build_requests", 1, fbzmq::COUNT);

  thrift::RouteDatabase routeDb;
  routeDb.thisNodeName = myNodeName;
  if (allDistFromNode_.count(myNodeName) == 0) {
    LOG(ERROR) << "Could not find adjacency Db for myself: " << myNodeName
               << ", skipping multi-spf run";
    return routeDb;
  }

  const auto& nodeDist = allDistFromNode_.at(myNodeName);

  const auto startTime = std::chrono::steady_clock::now();
  // Build RouteDb for all prefixes including LFAs
  unordered_map<
      thrift::IpPrefix, /* prefix */
      vector<std::pair<thrift::Adjacency, Weight>>>
      prefixToNextHops;
  for (auto const& kv : nodeData_) {
    auto const& nodeName = kv.first;

    // skip our own prefixes
    if (nodeName == myNodeName) {
      continue;
    }

    auto nodeIt = nodeDist.find(nodeName);
    if (nodeIt == nodeDist.end()) {
      VLOG(4) << "Skipping route to unreachable node " << nodeName;
      continue;
    }

    std::vector<std::pair<thrift::Adjacency, Weight>> adjacencies;
    for (auto const& nhMetric : nodeIt->second) {
      auto const& nhNodeName = nhMetric.first;
      auto metric = nhMetric.second;

      // Skip route if it is going through a node which is overloaded
      // NOTE: the metric here is a distance to prefix-node via our neighbor
      if (metric > Constants::kOverloadNodeMetric) {
        VLOG(2) << "Skipping shortest route to node " << nodeName
                << " because it is via an overloaded node.";
        continue;
      }

      // Add all adjacencies to nextHopNodeName as potential nexthops. FIB will
      // only program ones with min metric and others will act as LFAs
      for (const auto& adj : adjacencyIndex_.at({myNodeName, nhNodeName})) {
        adjacencies.push_back({adj, metric + adj.metric});
      }
    }

    for (auto const& prefix : kv.second.prefixes) {
      // skip routeDb update when a prefix is advertised from more than one node
      if (nodeData_.at(myNodeName).prefixes.count(prefix)) {
        VLOG(2) << "Prefix " << toString(prefix) << "is bouned to myself "
                << myNodeName << ", skipping routeDb update....";
        continue;
      }
      prefixToNextHops[prefix].insert(
          prefixToNextHops[prefix].end(),
          adjacencies.begin(),
          adjacencies.end());
    }
  } // for nodeData_ (build RouteDb)

  for (auto const& kv : prefixToNextHops) {
    auto const& prefix = kv.first;
    auto const& adjs = kv.second;
    auto route = createRouteMulti(prefix, adjs, enableV4_);
    if (route) {
      routeDb.routes.emplace_back(std::move(*route));
    }
  }

  auto deltaTime = std::chrono::duration_cast<std::chrono::milliseconds>(
      std::chrono::steady_clock::now() - startTime);
  LOG(INFO) << "Decision::buildRouteDb took " << deltaTime.count() << "ms.";
  tData_.addStatValue(
      "decision.spf.buildroute_ms", deltaTime.count(), fbzmq::AVG);

  return routeDb;
} // buildRouteDb

std::unordered_map<std::string, int64_t>
SpfSolver::SpfSolverImpl::getCounters() {
  return tData_.getCounters();
}

//
// Public SpfSolver
//

SpfSolver::SpfSolver(bool enableV4)
    : impl_(new SpfSolver::SpfSolverImpl(enableV4)) {}

SpfSolver::~SpfSolver() {}

// update adjacencies for the given router; everything is replaced
bool
SpfSolver::updateAdjacencyDatabase(
    thrift::AdjacencyDatabase const& adjacencyDb) {
  return impl_->updateAdjacencyDatabase(adjacencyDb);
}

bool
SpfSolver::deleteAdjacencyDatabase(const std::string& nodeName) {
  return impl_->deleteAdjacencyDatabase(nodeName);
}

std::unordered_map<std::string /* nodeName */, thrift::AdjacencyDatabase>
SpfSolver::getAdjacencyDatabases() {
  return impl_->getAdjacencyDatabases();
}

// update prefixes for a given router
bool
SpfSolver::updatePrefixDatabase(thrift::PrefixDatabase const& prefixDb) {
  return impl_->updatePrefixDatabase(prefixDb);
}

bool
SpfSolver::deletePrefixDatabase(const std::string& nodeName) {
  return impl_->deletePrefixDatabase(nodeName);
}

std::unordered_map<std::string /* nodeName */, thrift::PrefixDatabase>
SpfSolver::getPrefixDatabases() {
  return impl_->getPrefixDatabases();
}

thrift::RouteDatabase
SpfSolver::buildShortestPaths(const std::string& myNodeName) {
  return impl_->buildShortestPaths(myNodeName);
}

thrift::RouteDatabase
SpfSolver::buildMultiPaths(const std::string& myNodeName) {
  return impl_->buildMultiPaths(myNodeName);
}

thrift::RouteDatabase
SpfSolver::buildRouteDb(const std::string& myNodeName) {
  return impl_->buildRouteDb(myNodeName);
}

std::unordered_map<std::string, int64_t>
SpfSolver::getCounters() {
  return impl_->getCounters();
}

//
// Decision class implementation
//

Decision::Decision(
    std::string myNodeName,
    bool enableV4,
    const AdjacencyDbMarker& adjacencyDbMarker,
    const PrefixDbMarker& prefixDbMarker,
    std::chrono::milliseconds debounceMinDur,
    std::chrono::milliseconds debounceMaxDur,
    const KvStoreLocalCmdUrl& storeCmdUrl,
    const KvStoreLocalPubUrl& storePubUrl,
    const DecisionCmdUrl& decisionCmdUrl,
    const DecisionPubUrl& decisionPubUrl,
    const MonitorSubmitUrl& monitorSubmitUrl,
    fbzmq::Context& zmqContext)
    : processUpdatesBackoff_(debounceMinDur, debounceMaxDur),
      myNodeName_(myNodeName),
      adjacencyDbMarker_(adjacencyDbMarker),
      prefixDbMarker_(prefixDbMarker),
      storeCmdUrl_(storeCmdUrl),
      storePubUrl_(storePubUrl),
      decisionCmdUrl_(decisionCmdUrl),
      decisionPubUrl_(decisionPubUrl),
      storeSub_(
          zmqContext, folly::none, folly::none, fbzmq::NonblockingFlag{true}),
      decisionRep_(
          zmqContext, folly::none, folly::none, fbzmq::NonblockingFlag{true}),
      decisionPub_(
          zmqContext, folly::none, folly::none, fbzmq::NonblockingFlag{true}) {
  processUpdatesTimer_ = fbzmq::ZmqTimeout::make(
      this, [this]() noexcept { processPendingUpdates(); });
  spfSolver_ = std::make_unique<SpfSolver>(enableV4);

  zmqMonitorClient_ =
      std::make_unique<fbzmq::ZmqMonitorClient>(zmqContext, monitorSubmitUrl);

  // Initialize ZMQ sockets
  scheduleTimeout(
      std::chrono::seconds(0), [this, &zmqContext]() { prepare(zmqContext); });
}

void
Decision::prepare(fbzmq::Context& zmqContext) noexcept {
  VLOG(2) << "Decision: Binding cmdUrl '" << decisionCmdUrl_ << "'";
  const auto repBind = decisionRep_.bind(fbzmq::SocketUrl{decisionCmdUrl_});
  if (repBind.hasError()) {
    LOG(FATAL) << "Error binding to URL '" << decisionCmdUrl_ << "' "
               << repBind.error();
  }

  VLOG(2) << "Decision: Binding pubUrl '" << decisionPubUrl_ << "'";
  const auto pubBind = decisionPub_.bind(fbzmq::SocketUrl{decisionPubUrl_});
  if (pubBind.hasError()) {
    LOG(FATAL) << "Error binding to URL '" << decisionPubUrl_ << "' "
               << pubBind.error();
  }

  VLOG(2) << "Decision: Connecting to store '" << storePubUrl_ << "'";
  const auto optRet =
      storeSub_.setSockOpt(ZMQ_RCVHWM, &kStoreSubReceiveHwm, sizeof(int));
  if (optRet.hasError()) {
    LOG(FATAL) << "Error setting ZMQ_RCVHWM to " << kStoreSubReceiveHwm << " "
               << optRet.error();
  }
  const auto subConnect = storeSub_.connect(fbzmq::SocketUrl{storePubUrl_});
  if (subConnect.hasError()) {
    LOG(FATAL) << "Error binding to URL '" << storePubUrl_ << "' "
               << subConnect.error();
  }
  const auto subRet = storeSub_.setSockOpt(ZMQ_SUBSCRIBE, "", 0);
  if (subRet.hasError()) {
    LOG(FATAL) << "Error setting ZMQ_SUBSCRIBE to "
               << ""
               << " " << subRet.error();
  }

  VLOG(2) << "Decision thread attaching socket/event callbacks...";

  // Schedule periodic timer for submission to monitor
  const bool isPeriodic = true;
  monitorTimer_ =
      fbzmq::ZmqTimeout::make(this, [this]() noexcept { submitCounters(); });
  monitorTimer_->scheduleTimeout(Constants::kMonitorSubmitInterval, isPeriodic);

  // Attach callback for processing publications on storeSub_ socket
  addSocket(
      fbzmq::RawZmqSocketPtr{*storeSub_}, ZMQ_POLLIN, [this](int) noexcept {
        VLOG(3) << "Decision: publication received...";

        auto maybeThriftPub = storeSub_.recvThriftObj<thrift::Publication>(
            serializer_);
        if (maybeThriftPub.hasError()) {
          LOG(ERROR) << "Error processing KvStore publication: "
                     << maybeThriftPub.error();
          return;
        }

        // Apply publication and update stored update status
        auto const& res = processPublication(maybeThriftPub.value());
        processUpdatesStatus_.adjChanged |= res.adjChanged;
        processUpdatesStatus_.prefixesChanged |= res.prefixesChanged;
        // compute routes with exponential backoff timer if needed
        if (res.adjChanged || res.prefixesChanged) {
          if (!processUpdatesBackoff_.atMaxBackoff()) {
            processUpdatesBackoff_.reportError();
            processUpdatesTimer_->scheduleTimeout(
                processUpdatesBackoff_.getTimeRemainingUntilRetry());
          } else {
            CHECK(processUpdatesTimer_->isScheduled());
          }
        }
      });

  // Attach callback for processing requests on REP socket
  addSocket(
      fbzmq::RawZmqSocketPtr{*decisionRep_}, ZMQ_POLLIN, [this](int) noexcept {
        VLOG(3) << "Decision: request received...";
        processRequest();
      });

  initialSync(zmqContext);
}

void
Decision::processRequest() {
  auto maybeThriftReq = decisionRep_.recvThriftObj<thrift::DecisionRequest>(
      serializer_);
  if (maybeThriftReq.hasError()) {
    LOG(ERROR) << "Decision: Error processing request on REP socket: "
               << maybeThriftReq.error();
    return;
  }

  auto thriftReq = maybeThriftReq.value();
  thrift::DecisionReply reply;
  switch (thriftReq.cmd) {
  case thrift::DecisionCommand::ROUTE_DB_GET: {
    auto nodeName = thriftReq.nodeName;
    if (nodeName.empty()) {
      VLOG(1) << "Decision: Routes requested with no specific node name. "
              << "Returning " << myNodeName_ << " routes.";
      nodeName = myNodeName_;
    }

    reply.routeDb = spfSolver_->buildMultiPaths(nodeName);
    break;
  }

  case thrift::DecisionCommand::ADJ_DB_GET: {
    reply.adjDbs = spfSolver_->getAdjacencyDatabases();
    break;
  }

  case thrift::DecisionCommand::PREFIX_DB_GET: {
    reply.prefixDbs = spfSolver_->getPrefixDatabases();
    break;
  }

  default: {
    LOG(ERROR) << "Unexpected command received: "
               << folly::get_default(
                      thrift::_DecisionCommand_VALUES_TO_NAMES,
                      thriftReq.cmd,
                      "UNKNOWN");
    return;
  }
  }

  auto sendRc = decisionRep_.sendThriftObj(reply, serializer_);
  if (sendRc.hasError()) {
    LOG(ERROR) << "Error sending response: " << sendRc.error();
  }
}

std::unordered_map<std::string, int64_t>
Decision::getCounters() {
  return spfSolver_->getCounters();
}

ProcessPublicationResult
Decision::processPublication(thrift::Publication const& thriftPub) {
  ProcessPublicationResult res;

  // LSDB addition/update
  // deserialize contents of every LSDB key

  // Nothing to process if no adj/prefix db changes
  if (thriftPub.keyVals.empty() and thriftPub.expiredKeys.empty()) {
    return res;
  }

  for (const auto& kv : thriftPub.keyVals) {
    const auto& key = kv.first;
    const auto& rawVal = kv.second;
    std::string prefix, nodeName;
    folly::split(Constants::kPrefixNameSeparator.toString(), key, prefix, nodeName);

    if (not rawVal.value.hasValue()) {
      // skip TTL update
      DCHECK(rawVal.ttlVersion > 0);
      continue;
    }

    try {
      if (key.find(adjacencyDbMarker_) == 0) {
        // update adjacencyDb
        auto adjacencyDb =
            fbzmq::util::readThriftObjStr<thrift::AdjacencyDatabase>(
                rawVal.value.value(), serializer_);
        CHECK_EQ(nodeName, adjacencyDb.thisNodeName);
        if (spfSolver_->updateAdjacencyDatabase(adjacencyDb)) {
          res.adjChanged = true;
          pendingAdjUpdates_.addUpdate(myNodeName_, adjacencyDb.perfEvents);
        }
        continue;
      }

      if (key.find(prefixDbMarker_) == 0) {
        // update prefixDb
        auto prefixDb = fbzmq::util::readThriftObjStr<thrift::PrefixDatabase>(
            rawVal.value.value(), serializer_);
        CHECK_EQ(nodeName, prefixDb.thisNodeName);
        if (spfSolver_->updatePrefixDatabase(prefixDb)) {
          res.prefixesChanged = true;
          pendingPrefixUpdates_.addUpdate(myNodeName_, prefixDb.perfEvents);
        }
        continue;
      }
    } catch (std::exception const& e) {
      LOG(ERROR) << "Failed to deserialize info for key " << key
                 << ". Exception: " << folly::exceptionStr(e);
    }
  }

  // LSDB deletion
  for (const auto& key : thriftPub.expiredKeys) {
    std::string prefix, nodeName;
    folly::split(Constants::kPrefixNameSeparator.toString(), key, prefix, nodeName);

    if (key.find(adjacencyDbMarker_) == 0) {
      if (spfSolver_->deleteAdjacencyDatabase(nodeName)) {
        res.adjChanged = true;
        pendingAdjUpdates_.addUpdate(myNodeName_, folly::none);
      }
      continue;
    }

    if (key.find(prefixDbMarker_) == 0) {
      if (spfSolver_->deletePrefixDatabase(nodeName)) {
        res.prefixesChanged = true;
        pendingPrefixUpdates_.addUpdate(myNodeName_, folly::none);
      }
      continue;
    }
  }

  return res;
}

// perform full dump of all LSDBs and run initial routing computations
void
Decision::initialSync(fbzmq::Context& zmqContext) {
  thrift::Request thriftReq;

  fbzmq::Socket<ZMQ_REQ, fbzmq::ZMQ_CLIENT> storeReq(zmqContext);

  // we'll be using this to get the full dump from the KvStore
  const auto reqConnect = storeReq.connect(fbzmq::SocketUrl{storeCmdUrl_});
  if (reqConnect.hasError()) {
    LOG(FATAL) << "Error connecting to URL '" << storeCmdUrl_ << "' "
               << reqConnect.error();
  }

  thriftReq.cmd = thrift::Command::KEY_DUMP;
  storeReq.sendThriftObj(thriftReq, serializer_);

  VLOG(2) << "Decision process requesting initial state...";

  // receive the full dump of the database
  auto maybeThriftPub = storeReq.recvThriftObj<thrift::Publication>(
      serializer_, Constants::kReadTimeout);
  if (maybeThriftPub.hasError()) {
    LOG(ERROR) << "Error processing KvStore publication: "
               << maybeThriftPub.error();
    return;
  }

  // Process publication and immediately apply updates
  auto const& ret = processPublication(maybeThriftPub.value());
  if (ret.adjChanged) {
    // Graph changes
    processPendingAdjUpdates();
  } else if (ret.prefixesChanged) {
    // Only Prefix changes, no graph changes
    processPendingPrefixUpdates();
  }
}

// periodically submit counters to Counters thread
void
Decision::submitCounters() {
  VLOG(3) << "Submitting counters...";

  // Prepare for submitting counters
  auto counter = spfSolver_->getCounters();
  counter["decision.aliveness"] = 1;
  fbzmq::CounterMap submittingCounters = prepareSubmitCounters(counter);

  zmqMonitorClient_->setCounters(submittingCounters);
}

void
Decision::logRouteEvent(const std::string& event, const int numOfRoutes) {
  fbzmq::LogSample sample{};

  sample.addString("event", event);
  sample.addString("entity", "Decision");
  sample.addString("node_name", myNodeName_);
  sample.addInt("num_of_routes", numOfRoutes);

  zmqMonitorClient_->addEventLog(fbzmq::thrift::EventLog(
      apache::thrift::FRAGILE,
      Constants::kEventLogCategory.toString(),
      {sample.toJson()}));
}

void
Decision::logDebounceEvent(
    const int numUpdates, const std::chrono::milliseconds debounceTime) {
  fbzmq::LogSample sample{};

  sample.addString("event", "DECISION_DEBOUNCE");
  sample.addString("entity", "Decision");
  sample.addString("node_name", myNodeName_);
  sample.addInt("updates", numUpdates);
  sample.addInt("duration_ms", debounceTime.count());

  zmqMonitorClient_->addEventLog(fbzmq::thrift::EventLog(
      apache::thrift::FRAGILE,
      Constants::kEventLogCategory.toString(),
      {sample.toJson()}));
}

void
Decision::processPendingUpdates() {
  if (processUpdatesStatus_.adjChanged) {
    processPendingAdjUpdates();
  } else if (processUpdatesStatus_.prefixesChanged) {
    processPendingPrefixUpdates();
  }

  // reset update status
  processUpdatesStatus_.adjChanged = false;
  processUpdatesStatus_.prefixesChanged = false;

  // update decision debounce flag
  processUpdatesBackoff_.reportSuccess();
}

void
Decision::processPendingAdjUpdates() {
  VLOG(1) << "Decision: processing " << pendingAdjUpdates_.getCount()
          << " accumulated adjacency updates.";

  if (!pendingAdjUpdates_.getCount()) {
    LOG(ERROR) << "Decision route computation triggered without any pending "
               << "adjacency updates.";
    return;
  }

  // Retrieve perf events, add debounce perf event, log information to
  // ZmqMonitor, ad and clear pending updates
  auto maybePerfEvents = pendingAdjUpdates_.getPerfEvents();
  if (maybePerfEvents) {
    addPerfEvent(*maybePerfEvents, myNodeName_, "DECISION_DEBOUNCE");
    auto const& events = maybePerfEvents->events;
    auto const& eventsCnt = events.size();
    CHECK_LE(2, eventsCnt);
    auto duration = events[eventsCnt - 1].unixTs - events[eventsCnt - 2].unixTs;
    logDebounceEvent(
        pendingAdjUpdates_.getCount(), std::chrono::milliseconds(duration));
  }
  pendingAdjUpdates_.clear();

  // run SPF once for all updates received
  LOG(INFO) << "Decision: computing new paths.";
  auto routeDb = spfSolver_->buildMultiPaths(myNodeName_);
  logRouteEvent("ROUTE_CALC", routeDb.routes.size());
  if (maybePerfEvents) {
    addPerfEvent(*maybePerfEvents, myNodeName_, "DECISION_SPF");
  }
  routeDb.perfEvents = maybePerfEvents;

  // publish the new route state
  auto sendRc = decisionPub_.sendThriftObj(routeDb, serializer_);
  if (sendRc.hasError()) {
    LOG(ERROR) << "Error publishing new routing table: " << sendRc.error();
  }
}

void
Decision::processPendingPrefixUpdates() {
  auto maybePerfEvents = pendingPrefixUpdates_.getPerfEvents();
  pendingPrefixUpdates_.clear();

  // update routeDb once for all updates received
  LOG(INFO) << "Decision: updating new routeDb.";
  auto routeDb = spfSolver_->buildRouteDb(myNodeName_);
  logRouteEvent("ROUTE_CALC", routeDb.routes.size());
  if (maybePerfEvents) {
    addPerfEvent(*maybePerfEvents, myNodeName_, "ROUTE_UPDATE");
  }
  routeDb.perfEvents = maybePerfEvents;

  // publish the new route state
  auto sendRc = decisionPub_.sendThriftObj(routeDb, serializer_);
  if (sendRc.hasError()) {
    LOG(ERROR) << "Error publishing new routing table: " << sendRc.error();
  }
}

} // namespace openr


#include <StdInc.h>

#include <sstream>
#include <IteratorView.h>

#include <boost/algorithm/string.hpp>

#include <CoreConsole.h>

#include <NetAddress.h>

#define FOLLY_NO_CONFIG

#ifdef _WIN32
#undef ssize_t
#pragma comment(lib, "iphlpapi.lib")
#else
#include <sys/types.h>
#endif

#include <folly/IPAddress.h>
#include <folly/String.h>

class NetworkList
{
public:
	NetworkList()
	{

	}

	explicit NetworkList(std::string_view str)
	{
		// make_split_iterator currently does not understand string_view
		auto strRef = std::string{
			str
		};

		for (auto item :
			fx::GetIteratorView(
				std::make_pair(
					boost::algorithm::make_split_iterator(
						strRef,
						boost::algorithm::token_finder(
							boost::algorithm::is_space(),
							boost::algorithm::token_compress_on
						)
					),
					boost::algorithm::split_iterator<std::string::iterator>()
				)
			)
		)
		{
			auto network = folly::IPAddress::tryCreateNetwork(folly::range(&*item.begin(), &*item.end()));

			if (network)
			{
				networks.push_back(*network);
			}
		}
	}

	bool ContainsIP(const net::PeerAddress& ip) const
	{
		auto sockaddr = ip.GetSocketAddress();
		folly::ByteRange ipRange;

		if (sockaddr->sa_family == AF_INET)
		{
			sockaddr_in* inAddr = (sockaddr_in*)sockaddr;
			ipRange = folly::ByteRange{ (uint8_t*)&inAddr->sin_addr, (uint8_t*)&inAddr->sin_addr + sizeof(inAddr->sin_addr) };
		}
		else if (sockaddr->sa_family == AF_INET6)
		{
			sockaddr_in6* inAddr = (sockaddr_in6*)sockaddr;
			ipRange = folly::ByteRange{ (uint8_t*)&inAddr->sin6_addr, (uint8_t*)&inAddr->sin6_addr + sizeof(inAddr->sin6_addr) };
		}

		if (!ipRange.empty())
		{
			auto ipAddress = folly::IPAddress::tryFromBinary(ipRange);

			if (ipAddress)
			{
				for (const auto& network : networks)
				{
					if (ipAddress->inSubnet(network.first, network.second))
					{
						return true;
					}
				}
			}
		}

		return false;
	}

	bool ContainsIP(std::string_view ip) const
	{
		auto ipAddress = folly::IPAddress::tryFromString(ip);

		if (ipAddress)
		{
			for (const auto& network : networks)
			{
				if (ipAddress->inSubnet(network.first, network.second))
				{
					return true;
				}
			}
		}

		return false;
	}

	std::string ToString() const
	{
		std::stringstream ss;

		for (auto& network : networks)
		{
			ss << folly::IPAddress::networkToString(network) << " ";
		}

		// strip the final space
		auto s = ss.str();

		if (!s.empty())
		{
			s = s.substr(0, s.length() - 1);
		}

		return s;
	}

	inline bool operator==(const NetworkList& right) const
	{
		return networks == right.networks;
	}

	inline bool operator!=(const NetworkList& right) const
	{
		return !(*this == right);
	}

private:
	std::vector<folly::CIDRNetwork> networks;
};

template<>
struct ConsoleArgumentType<NetworkList>
{
	static std::string Unparse(const NetworkList& input)
	{
		return input.ToString();
	}

	static bool Parse(const std::string& input, NetworkList* out)
	{
		*out = NetworkList{
			input
		};

		return true;
	}
};

template<>
struct ConsoleArgumentName<NetworkList>
{
	inline static const char* Get()
	{
		return "NetworkList";
	}
};

ConVar<NetworkList>* g_networkListVar;

namespace fx
{
bool DLL_EXPORT IsProxyAddress(std::string_view ep)
{
	return g_networkListVar->GetValue().ContainsIP(ep);
}

bool DLL_EXPORT IsProxyAddress(const net::PeerAddress& ep)
{
	return g_networkListVar->GetValue().ContainsIP(ep);
}
}

static InitFunction initFunction([]()
{
	static ConVar<NetworkList> allowedIpCidr("sv_proxyIPRanges", ConVar_None, NetworkList{ "10.0.0.0/8 127.0.0.0/8 192.168.0.0/16 172.16.0.0/12" });

	g_networkListVar = &allowedIpCidr;
});


//
// Copyright (c) 2020 INRIA
//

#ifndef __pinocchio_math_mutliprecision_hpp__
#define __pinocchio_math_mutliprecision_hpp__

#include "pinocchio/math/fwd.hpp"

#include <boost/multiprecision/number.hpp>
#include <Eigen/Dense>

namespace Eigen
{
  namespace internal
  {
    template <class Backend, boost::multiprecision::expression_template_option ExpressionTemplates, typename Scalar>
    struct cast_impl<boost::multiprecision::number<Backend, ExpressionTemplates>,Scalar>
    {
#if EIGEN_VERSION_AT_LEAST(3,2,90)
      EIGEN_DEVICE_FUNC
#endif
      static inline Scalar run(const boost::multiprecision::number<Backend, ExpressionTemplates> & x)
      {
        return x.template convert_to<Scalar>();
      }
    };
  }
} //namespace Eigen

#ifndef BOOST_MP_EIGEN_HPP

//  Code adapted from <boost/multiprecision/eigen.hpp>
//  Copyright 2018 John Maddock. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
namespace Eigen
{
  template <class Backend, boost::multiprecision::expression_template_option ExpressionTemplates>
  struct NumTraits<boost::multiprecision::number<Backend, ExpressionTemplates> >
  {
    typedef boost::multiprecision::number<Backend, ExpressionTemplates>                          self_type;
#if BOOST_VERSION / 100 % 1000 >= 68
    typedef typename boost::multiprecision::scalar_result_from_possible_complex<self_type>::type Real;
#else
    typedef self_type                                                                            Real;
#endif
    typedef self_type                                                                            NonInteger; // Not correct but we can't do much better??
    typedef double                                                                               Literal;
    typedef self_type                                                                            Nested;
    enum
    {
#if BOOST_VERSION / 100 % 1000 >= 68
      IsComplex             = boost::multiprecision::number_category<self_type>::value == boost::multiprecision::number_kind_complex,
#else
      IsComplex             = 0,
#endif
      IsInteger             = boost::multiprecision::number_category<self_type>::value == boost::multiprecision::number_kind_integer,
      ReadCost              = 1,
      AddCost               = 4,
      MulCost               = 8,
      IsSigned              = std::numeric_limits<self_type>::is_specialized ? std::numeric_limits<self_type>::is_signed : true,
      RequireInitialization = 1
    };
    static Real epsilon()
    {
      return std::numeric_limits<Real>::epsilon();
    }
    static Real dummy_precision()
    {
      return 1000 * epsilon();
    }
    static Real highest()
    {
      return (std::numeric_limits<Real>::max)();
    }
    static Real lowest()
    {
      return (std::numeric_limits<Real>::min)();
    }
    static int digits10_imp(const boost::mpl::true_&)
    {
      return std::numeric_limits<Real>::digits10;
    }
    template <bool B>
    static int digits10_imp(const boost::mpl::bool_<B>&)
    {
      return Real::default_precision();
    }
    static int digits10()
    {
      return digits10_imp(boost::mpl::bool_ < std::numeric_limits<Real>::digits10 && (std::numeric_limits<Real>::digits10 != INT_MAX) ? true : false > ());
    }
  };

  template <class tag, class Arg1, class Arg2, class Arg3, class Arg4>
  struct NumTraits<boost::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4> > : public NumTraits<typename boost::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>::result_type>
  {
  };

  namespace internal
  {

    template <class Backend, boost::multiprecision::expression_template_option ExpressionTemplates, class tag, class Arg1, class Arg2, class Arg3, class Arg4>
    struct scalar_product_traits<boost::multiprecision::number<Backend, ExpressionTemplates>, boost::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4> >
    {
//      BOOST_STATIC_ASSERT(boost::is_convertible<typename boost::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>::result_type, boost::multiprecision::number<Backend, ExpressionTemplates> >::value, "Interoperability with this arithmetic type is not supported.");
      typedef boost::multiprecision::number<Backend, ExpressionTemplates> ReturnType;
    };

    template <class tag, class Arg1, class Arg2, class Arg3, class Arg4, class Backend, boost::multiprecision::expression_template_option ExpressionTemplates>
    struct scalar_product_traits<boost::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>, boost::multiprecision::number<Backend, ExpressionTemplates> >
    {
//      BOOST_STATIC_ASSERT(boost::is_convertible<typename boost::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>::result_type, boost::multiprecision::number<Backend, ExpressionTemplates> >::value, "Interoperability with this arithmetic type is not supported.");
      typedef boost::multiprecision::number<Backend, ExpressionTemplates> ReturnType;
    };

    template <typename Scalar>
    struct conj_retval;

    template <typename Scalar, bool IsComplex>
    struct conj_impl;

    template <class tag, class Arg1, class Arg2, class Arg3, class Arg4>
    struct conj_retval<boost::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4> >
    {
      typedef typename boost::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>::result_type type;
    };

    template <class tag, class Arg1, class Arg2, class Arg3, class Arg4>
    struct conj_impl<boost::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>, true>
    {
#if EIGEN_VERSION_AT_LEAST(3,2,90)
      EIGEN_DEVICE_FUNC
#endif
      static inline typename boost::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>::result_type run(const typename boost::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>& x)
      {
        return conj(x);
      }
    };

  } // namespace internal

} // namespace Eigen

#endif // ifndef BOOST_MP_EIGEN_HPP

#endif // ifndef __pinocchio_math_mutliprecision_hpp__


/*
 Copyright (C) 2018 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include <boost/make_shared.hpp>
#include <boost/test/unit_test.hpp>
#include <oret/toplevelfixture.hpp>

#include <ql/math/interpolations/linearinterpolation.hpp>
#include <ql/math/interpolations/loginterpolation.hpp>
#include <ql/time/daycounters/actualactual.hpp>

#include <ored/configuration/curveconfigurations.hpp>
#include <ored/marketdata/commoditycurve.hpp>
#include <ored/marketdata/curvespec.hpp>
#include <ored/marketdata/loader.hpp>

using namespace std;
using namespace boost::unit_test_framework;
using namespace QuantLib;
using namespace QuantExt;
using namespace ore::data;

namespace {

// testTolerance for Real comparison
Real testTolerance = 1e-10;

class MockLoader : public Loader {
public:
    MockLoader();
    const vector<boost::shared_ptr<MarketDatum>>& loadQuotes(const Date&) const { return data_; }
    const boost::shared_ptr<MarketDatum>& get(const string& name, const Date&) const { return dummyDatum_; }
    const vector<Fixing>& loadFixings() const { return dummyFixings_; }
    const vector<Fixing>& loadDividends() const { return dummyDividends_; }

private:
    vector<boost::shared_ptr<MarketDatum>> data_;
    boost::shared_ptr<MarketDatum> dummyDatum_;
    vector<Fixing> dummyFixings_;
    vector<Fixing> dummyDividends_;
};

MockLoader::MockLoader() {
    Date asof(5, Feb, 2016);
    data_ = {
        boost::make_shared<CommoditySpotQuote>(1155.593, asof, "COMMODITY/PRICE/GOLD/USD",
                                               MarketDatum::QuoteType::PRICE, "GOLD", "USD"),
        boost::make_shared<CommodityForwardQuote>(1157.8, asof, "COMMODITY_FWD/PRICE/GOLD/USD/2016-02-29",
                                                  MarketDatum::QuoteType::PRICE, "GOLD", "USD", Date(29, Feb, 2016)),
        boost::make_shared<CommodityForwardQuote>(1160.9, asof, "COMMODITY_FWD/PRICE/GOLD/USD/2017-02-28",
                                                  MarketDatum::QuoteType::PRICE, "GOLD", "USD", Date(28, Feb, 2017)),
        boost::make_shared<CommodityForwardQuote>(1189.5, asof, "COMMODITY_FWD/PRICE/GOLD/USD/2020-06-30",
                                                  MarketDatum::QuoteType::PRICE, "GOLD", "USD", Date(30, Jun, 2020))};
}

} // namespace

BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)

BOOST_AUTO_TEST_SUITE(CommodityCurveTests)

BOOST_AUTO_TEST_CASE(testCommodityCurveConstruction) {

    BOOST_TEST_MESSAGE("Testing commodity curve building");

    // As of date
    Date asof(5, Feb, 2016);

    // Commodity curve configuration
    vector<string> quotes = {"COMMODITY_FWD/PRICE/GOLD/USD/2016-02-29", "COMMODITY_FWD/PRICE/GOLD/USD/2017-02-28",
                             "COMMODITY_FWD/PRICE/GOLD/USD/2020-06-30"};
    boost::shared_ptr<CommodityCurveConfig> curveConfig = boost::make_shared<CommodityCurveConfig>(
        "GOLD_USD", "", "USD", "COMMODITY/PRICE/GOLD/USD", quotes, "A365", "Linear", true);

    // Curve configurations
    CurveConfigurations curveConfigs;
    curveConfigs.commodityCurveConfig("GOLD_USD") = curveConfig;

    // Commodity curve spec
    CommodityCurveSpec curveSpec("USD", "GOLD_USD");

    // Market data loader
    MockLoader loader;

    // Check commodity curve construction works
    boost::shared_ptr<CommodityCurve> curve;
    BOOST_CHECK_NO_THROW(curve =
                             boost::make_shared<CommodityCurve>(asof, curveSpec, loader, curveConfigs, Conventions()));

    // Check a few prices
    boost::shared_ptr<PriceTermStructure> priceCurve = curve->commodityPriceCurve();

    // Check a few prices
    BOOST_CHECK_CLOSE(priceCurve->price(asof), 1155.593, testTolerance);
    BOOST_CHECK_CLOSE(priceCurve->price(Date(29, Feb, 2016)), 1157.8, testTolerance);
    BOOST_CHECK_CLOSE(priceCurve->price(Date(28, Feb, 2017)), 1160.9, testTolerance);
    BOOST_CHECK_CLOSE(priceCurve->price(Date(30, Jun, 2020)), 1189.5, testTolerance);

    // Will use this to check the interpolation on the price curve via a dynamic cast
    boost::shared_ptr<PriceTermStructure> checkCurveInterpolation;

    // Check that variables were picked up from the config
    BOOST_CHECK_EQUAL(priceCurve->dayCounter(), Actual365Fixed());
    BOOST_CHECK_EQUAL(priceCurve->allowsExtrapolation(), true);
    checkCurveInterpolation = boost::dynamic_pointer_cast<InterpolatedPriceCurve<Linear>>(priceCurve);
    BOOST_CHECK(checkCurveInterpolation);

    // Change the config and check that the variables again
    curveConfig = boost::make_shared<CommodityCurveConfig>("GOLD_USD", "", "USD", "COMMODITY/PRICE/GOLD/USD", quotes,
                                                           "ACT/ACT", "LogLinear", false);
    curveConfigs.commodityCurveConfig("GOLD_USD") = curveConfig;

    BOOST_CHECK_NO_THROW(curve =
                             boost::make_shared<CommodityCurve>(asof, curveSpec, loader, curveConfigs, Conventions()));
    priceCurve = curve->commodityPriceCurve();
    BOOST_CHECK_EQUAL(priceCurve->dayCounter(), ActualActual());
    BOOST_CHECK_EQUAL(priceCurve->allowsExtrapolation(), false);
    checkCurveInterpolation = boost::dynamic_pointer_cast<InterpolatedPriceCurve<LogLinear>>(priceCurve);
    BOOST_CHECK(checkCurveInterpolation);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()


#include <boost/python.hpp>

#include "../../../def_submodule.hpp"

#include "IceRay/geometry/simple/cone.hpp"

#include "./simple.hpp"

typedef GS_DDMRM::S_IceRay::S_geometry::GC_cone            GTs_cone;

void expose_IceRay_geometry_cone()
 {
  //MAKE_SUBMODULE( IceRay );
  MAKE_SUBMODULE( core );
  MAKE_SUBMODULE( geometry );

  boost::python::class_< GTs_cone, boost::python::bases<GTs__base> >( "GeometrySimpleCone" )
    .def( boost::python::init<>() )
  ;

 }


/**
 * <ORGANIZATION> = Jennifer Buehler
 * <COPYRIGHT HOLDER> = Jennifer Buehler
 *
 * Copyright (c) 2016 Jennifer Buehler
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * 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.
 *     * Neither the name of the <ORGANIZATION> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 <COPYRIGHT HOLDER> 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.
 * ------------------------------------------------------------------------------
 **/
#include <Eigen/Core>
#include <Eigen/Geometry>

#include <ros/ros.h>
#include <urdf_traverser/Functions.h>
#include <urdf_traverser/Helpers.h>
#include <urdf_traverser/UrdfTraverser.h>
#include <urdf_transform/AlignRotationAxis.h>

using urdf_traverser::UrdfTraverser;
using urdf_traverser::RecursionParams;

/**
 * \brief Recursion parameters with one 3D vector
 * \author Jennifer Buehler
 */
class Vector3RecursionParams: public RecursionParams
{
public:
    typedef baselib_binding::shared_ptr<Vector3RecursionParams>::type Ptr;
    Vector3RecursionParams(): RecursionParams() {}
    Vector3RecursionParams(const Eigen::Vector3d& _vec):
        RecursionParams(),
        vec(_vec) {}
    Vector3RecursionParams(const Vector3RecursionParams& o):
        RecursionParams(o),
        vec(o.vec) {}
    virtual ~Vector3RecursionParams() {}

    // Result set
    Eigen::Vector3d vec;
};

/**
 * Recursion method to be used with traverseTreeTopDown() and recursion parameters
 * of type *Vector3RecursionParams*.
 *
 * Re-arranges the joint-transform of the recursion link's *parent joint*, along with
 * the link's visual/collision/intertial rotations, such that all joints rotate around the axis
 * given in the recursion parameters vector.
 */
int allRotationsToAxisCB(urdf_traverser::RecursionParamsPtr& p)
{
    urdf_traverser::LinkPtr link = p->getLink();
    if (!link)
    {
        ROS_ERROR("allRotationsToAxis: NULL link passed");
        return -1;
    }

    Vector3RecursionParams::Ptr param = baselib_binding_ns::dynamic_pointer_cast<Vector3RecursionParams>(p);
    if (!param)
    {
        ROS_ERROR("Wrong recursion parameter type");
        return -1;
    }

    urdf_traverser::JointPtr joint = link->parent_joint;
    if (!joint)
    {
        ROS_INFO_STREAM("allRotationsToAxis: Joint for link " << link->name << " is NULL, so this must be the root joint");
        return 1;
    }

    Eigen::Vector3d axis = param->vec;

    Eigen::Quaterniond alignAxis;
    if (urdf_traverser::jointTransformForAxis(joint, axis, alignAxis))
    {
        Eigen::Vector3d rotAxis(joint->axis.x, joint->axis.y, joint->axis.z);
        // ROS_INFO_STREAM("Transforming axis "<<rotAxis<<" for joint "<<joint->name<<" with transform "<<urdf_traverser::EigenTransform(alignAxis));

        urdf_traverser::applyTransform(joint, urdf_traverser::EigenTransform(alignAxis), false);

        // the link has to receive the inverse transform, so it stays at the original position
        Eigen::Quaterniond alignAxisInv = alignAxis.inverse();
        urdf_traverser::applyTransform(link, urdf_traverser::EigenTransform(alignAxisInv), true);

        // we also have to fix the child joint's (1st order child joints) transform
        // to correct for this transformation.
        for (std::vector<urdf_traverser::JointPtr>::iterator pj = link->child_joints.begin();
                pj != link->child_joints.end(); pj++)
        {
            urdf_traverser::applyTransform(*pj, urdf_traverser::EigenTransform(alignAxisInv), true);
        }

        // finally, set the rotation axis to the target
        joint->axis.x = axis.x();
        joint->axis.y = axis.y();
        joint->axis.z = axis.z();
    }

    // all good, indicate that recursion can continue
    return 1;
}

bool urdf_transform::allRotationsToAxis(UrdfTraverser& traverser, const std::string& fromLink, const Eigen::Vector3d& axis)
{
    // ROS_INFO_STREAM("### Transforming all rotations starting from "<<fromLinkName<<" to axis "<<axis);
    std::string startLink = fromLink;
    if (startLink.empty())
    {
        startLink = traverser.getRootLinkName();
    }
    urdf_traverser::LinkPtr startLink_ = traverser.getLink(startLink);
    if (!startLink_)
    {
        ROS_ERROR("Link %s does not exist", startLink.c_str());
        return false;
    }

    Vector3RecursionParams * vp = new Vector3RecursionParams(axis);
    urdf_traverser::RecursionParamsPtr p(vp);

    // traverse top-down, but don't include the link itself, as the method allRotationsToAxis()
    // operates on the links parent joints.
    int travRet = traverser.traverseTreeTopDown(startLink,
                  boost::bind(&allRotationsToAxisCB, _1), p, false);
    if (travRet <= 0)
    {
        ROS_ERROR("Recursion to align all rotation axes failed");
        return false;
    }
    return true;
}


/*

Copyright (c) 2005-2020, University of Oxford.
All rights reserved.

University of Oxford means the Chancellor, Masters and Scholars of the
University of Oxford, having an administrative office at Wellington
Square, Oxford OX1 2JD, UK.

This file is part of Chaste.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 * 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.
 * Neither the name of the University of Oxford nor the names of its
   contributors may be used to endorse or promote products derived from this
   software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 OR CONTRIBUTORS 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 BACKWARDEULERIVPODESOLVER_HPP_
#define BACKWARDEULERIVPODESOLVER_HPP_

#include "AbstractOneStepIvpOdeSolver.hpp"
#include "AbstractOdeSystemWithAnalyticJacobian.hpp"

#include "ChasteSerialization.hpp"
#include <boost/serialization/base_object.hpp>

#include "AbstractOneStepIvpOdeSolver.hpp"

/**
 * A concrete one step ODE solver class that employs the backward Euler
 * method. This numerical method is implicit and hence unconditionally stable.
 */
class BackwardEulerIvpOdeSolver  : public AbstractOneStepIvpOdeSolver
{
private:
    /** Needed for serialization. */
    friend class boost::serialization::access;
    /**
     * Archive the abstract IVP Solver, never used directly - boost uses this.
     *
     * @param archive the archive
     * @param version the current version of this class
     */
    template<class Archive>
    void serialize(Archive & archive, const unsigned int version)
    {
        // This calls serialize on the base class.
        archive & boost::serialization::base_object<AbstractOneStepIvpOdeSolver>(*this);
        //archive & mSizeOfOdeSystem; - this done in save and load construct now.
        archive & mNumericalJacobianEpsilon;
        archive & mForceUseOfNumericalJacobian;
    }

    /** The number of state variables in the ODE system. */
    unsigned mSizeOfOdeSystem;

    /** The epsilon to use in calculating the numerical Jacobian of the ODE system. */
    double mNumericalJacobianEpsilon;

    /**
     * Whether to force the solver to use the numerical Jacobian even if
     * the ODE system object provides an analytical Jacobian.
     */
    bool mForceUseOfNumericalJacobian;

    /*
     * NOTE: we use (unsafe) double pointers here rather than
     * std::vectors because using std::vectors would lead to a
     * slow down by a factor of about 4.
     */

    /** Working memory : residual vector */
    double* mResidual;

    /** Working memory : Jacobian matrix */
    double** mJacobian;

    /** Working memory : update vector */
    double* mUpdate;

    /**
     * Compute the current residual.
     *
     * @param pAbstractOdeSystem  the ODE system to solve
     * @param timeStep  dt
     * @param time  the current time
     * @param rCurrentYValues  the current (initial) state
     * @param rCurrentGuess  current guess for the state at the next timestep
     */
    void ComputeResidual(AbstractOdeSystem* pAbstractOdeSystem,
                         double timeStep,
                         double time,
                         std::vector<double>& rCurrentYValues,
                         std::vector<double>& rCurrentGuess);

    /**
     * Compute the Jacobian of the ODE system.
     *
     * @param pAbstractOdeSystem  the ODE system to solve
     * @param timeStep  dt
     * @param time  the current time
     * @param rCurrentYValues  the current (initial) state
     * @param rCurrentGuess  current guess for the state at the next timestep
     */
    void ComputeJacobian(AbstractOdeSystem* pAbstractOdeSystem,
                         double timeStep,
                         double time,
                         std::vector<double>& rCurrentYValues,
                         std::vector<double>& rCurrentGuess);

    /**
     * Solve a linear system of equations to update the
     * current guess for the solution to the ODE system at
     * the next timestep.
     * Used by the method CalculateNextYValue.
     */
    void SolveLinearSystem();

    /**
     * Compute the infinity/maximum norm of a vector.
     * Used by the method CalculateNextYValue.
     *
     * @param pVector  a pointer to a vector
     * @return the vector's norm.
     */
    double ComputeNorm(double* pVector);

    /**
     * Compute the Jacobian of the ODE system numerically.
     *
     * @param pAbstractOdeSystem  the ODE system to solve
     * @param timeStep  dt
     * @param time  the current time
     * @param rCurrentYValues  the current (initial) state
     * @param rCurrentGuess  current guess for the state at the next timestep
     */
    void ComputeNumericalJacobian(AbstractOdeSystem* pAbstractOdeSystem,
                                  double timeStep,
                                  double time,
                                  std::vector<double>& rCurrentYValues,
                                  std::vector<double>& rCurrentGuess);

protected:

    /**
     * Calculate the solution to the ODE system at the next timestep.
     *
     * A usage example:
     *     BackwardEulerIvpOdeSolver mySolver;
     *     OdeSolution solution = mySolver.Solve(pMyOdeSystem, yInit, StartTime, EndTime, TimeStep, SamplingTime);
     *
     * @param pAbstractOdeSystem  the ODE system to solve
     * @param timeStep  dt
     * @param time  the current time
     * @param rCurrentYValues  the current (initial) state
     * @param rNextYValues  the state at the next timestep
     */
    void CalculateNextYValue(AbstractOdeSystem* pAbstractOdeSystem,
                             double timeStep,
                             double time,
                             std::vector<double>& rCurrentYValues,
                             std::vector<double>& rNextYValues);

public:

    /**
     * Constructor.
     *
     * @param sizeOfOdeSystem  the number of state variables in the ODE system
     */
    BackwardEulerIvpOdeSolver(unsigned sizeOfOdeSystem);

    /**
     * Destructor.
     */
    ~BackwardEulerIvpOdeSolver();

    /**
     * Set the epsilon to use in calculating the
     * numerical Jacobian of the ODE system.
     *
     * @param epsilon
     */
    void SetEpsilonForNumericalJacobian(double epsilon);

    /**
     * Force the solver to use the numerical Jacobian even if
     * the ODE system object provides an analytical Jacobian.
     */
    void ForceUseOfNumericalJacobian();

    /**
     * Public method used in archiving.
     *
     * @return the size of the system
     */
     unsigned GetSystemSize() const {return mSizeOfOdeSystem;};
};

#include "SerializationExportWrapper.hpp"
CHASTE_CLASS_EXPORT(BackwardEulerIvpOdeSolver)

namespace boost
{
namespace serialization
{
/**
 * Allow us to not need a default constructor, by specifying how Boost should
 * instantiate a BackwardEulerIvpOdeSolver instance.
 */
template<class Archive>
inline void save_construct_data(
    Archive & ar, const BackwardEulerIvpOdeSolver * t, const unsigned int file_version)
{
    const unsigned system_size = t->GetSystemSize();
    ar & system_size;
}

/**
 * Allow us to not need a default constructor, by specifying how Boost should
 * instantiate a BackwardEulerIvpOdeSolver instance (using existing constructor)
 *
 * NB this constructor allocates memory for the other member variables too.
 */
template<class Archive>
inline void load_construct_data(
    Archive & ar, BackwardEulerIvpOdeSolver * t, const unsigned int file_version)
{
     unsigned ode_system_size;
     ar >> ode_system_size;
     ::new(t)BackwardEulerIvpOdeSolver(ode_system_size);
}
}
} // namespace ...

#endif /*BACKWARDEULERIVPODESOLVER_HPP_*/


//
//
// The MIT License (MIT)
//
// Copyright (c) 2015  Peter Fisk, Michael J. Wouters
//
// 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 <cmath>
#include <cstring>
#include <time.h>
#include <gsl/gsl_multifit.h>

#include <boost/regex.hpp>

#include "Utility.h"

std::string Utility::trim(std::string const& str)
{
	std::size_t first = str.find_first_not_of(' ');

	// If there is no non-whitespace character, both first and last will be std::string::npos (-1)
	// There is no point in checking both, since if either doesn't work, the
	// other won't work, either.
	if(first == std::string::npos)
		return "";

	std::size_t last  = str.find_last_not_of(' ');

	return str.substr(first, last-first+1);
}

void Utility::MJDtoDate(int mjd,int *year,int *mon, int *mday, int *yday)
{
	time_t tt = (mjd - 40587)*86400;
	struct tm *utc = gmtime(&tt);
	*year = 1900 + utc->tm_year;
	*mon  = utc->tm_mon+1;
	*mday = utc->tm_mday;
	*yday = utc->tm_yday+1;
}

bool Utility::TODStrtoTOD(std::string const& todstr,int *hh,int *mm,int *ss)
{
	// Two formats OK
	// HHMMSS or HH:MM:SS
	boost::regex re1("^(\\d{2})(\\d{2})(\\d{2})$");
	boost::smatch matches;
	if (boost::regex_search(todstr,matches,re1)){
		*hh = atoi(matches[1].str().c_str()); // regex has already checked input is valid
		*mm = atoi(matches[2].str().c_str());
		*ss = atoi(matches[3].str().c_str());
		return (*hh <= 23 && *mm <= 59 && *ss <= 59);
	}
	boost::regex re2("^(\\d{2}):(\\d{2}):(\\d{2})$");
	if (boost::regex_search(todstr,matches,re2)){
		*hh = atoi(matches[1].str().c_str()); // regex has already checked input is valid
		*mm = atoi(matches[2].str().c_str());
		*ss = atoi(matches[3].str().c_str());
		return (*hh <= 23 && *mm <= 59 && *ss <= 59);
	}
	return false;
}

bool Utility::linearFit(double x[], double y[],int n,double xinterp,double *yinterp,double *c,double *m,double *rmsResidual)
{
	double sx = 0, sy = 0, sr2 = 0, xbar, ybar, sxxbary=0,sxxbarsq=0;

	for (int i=0;i<n;i++){
		sx += x[i];
		sy += y[i];
	}

	xbar = sx/n;
	ybar = sy/n;

	for (int i=0;i<n;i++){
		sxxbary += (x[i] - xbar) * y[i];
		sxxbarsq += (x[i] - xbar)*(x[i] - xbar);
	}

	// Slope and intercept
	*m = sxxbary/sxxbarsq;
	*c = ybar - *m * xbar;

	// Sum of residuals^2

	for (int i=0;i<n;i++)
		sr2 += pow(y[i] - *m * x[i] - *c,2);

	// RMS residuals
	*rmsResidual = sqrt(sr2/n);

	*yinterp = *m * xinterp + *c;

	return true;
}

bool Utility::quadFit(double x[], double y[],int n,double xinterp,double *yinterp){
	//double c,m,rmsResidual;
	//linearFit(x,y,n,xinterp,yinterp,&c,&m,&rmsResidual); // FIXME

	double chisq;
  gsl_matrix *X, *cov;
  gsl_vector *Y, *c;

	X = gsl_matrix_alloc (n, 3);
	Y = gsl_vector_alloc (n);
	c = gsl_vector_alloc (3);
	cov = gsl_matrix_alloc (3, 3);

	for (int i = 0; i < n; i++){
		gsl_matrix_set (X, i, 0, 1.0);
		gsl_matrix_set (X, i, 1, x[i]);
		gsl_matrix_set (X, i, 2, x[i]*x[i]);
		gsl_vector_set (Y, i, y[i]);
	}

	gsl_multifit_linear_workspace * work = gsl_multifit_linear_alloc (n, 3);
	gsl_multifit_linear (X, Y, c, cov,&chisq, work);
	gsl_multifit_linear_free (work);

	#define C(i) (gsl_vector_get(c,(i)))

	*yinterp = C(0) + C(1)*xinterp + C(2)*xinterp*xinterp;

	gsl_matrix_free (X);
  gsl_vector_free (Y);
  gsl_vector_free (c);
  gsl_matrix_free (cov);

	return true;
}

void Utility::ECEFtoLatLonH(double X, double Y, double Z,
	double *lat, double *lon, double *ht)
{
	// Parameters for WGS84 ellipsoid
	double a = 6378137.00; // semi-major axis
	double inverse_flattening = 298.257223563;
	double latitude,longitude;
	double p=sqrt(X*X + Y*Y);
	double r=sqrt(p*p + Z*Z);
	double f=1/inverse_flattening;
	double esq=2*f-f*f;
	double u=atan2(Z/p , 1.0/(1-f+esq*a/r));

	longitude = atan2(Y,X);
	latitude = atan2(Z*(1-f) + esq * a * pow(sin(u),3) ,
		(1-f)*(p  - esq * a * pow(cos(u),3)));
	*ht = p*cos(latitude) + Z*sin(latitude) -
			a*sqrt(1-esq* pow(sin(latitude),2));

	// Convert to degrees
	*lat=latitude*180.0/M_PI;
	*lon=longitude*180.0/M_PI;

	return;
}


// Copyright (c) 2015 - 2016 Anyar, Inc.
//
// 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.

#include "ascent/io/ChaiEngine.h"

#include <chaiscript/chaiscript_stdlib.hpp>

#include "ascent/core/LinkBase.h"
#include "ascent/Link.h"
#include "ascent/core/ModuleCore.h"

#include <Eigen/Dense>

using namespace asc;
using namespace chaiscript;

template <typename E>
void assign(E& lhs, const E& rhs) { lhs = rhs; }

template <typename E>
E cross(const E& lhs, const E& rhs) { return lhs.cross(rhs); }

template <typename E>
double dot(const E& lhs, const E& rhs) { return lhs.dot(rhs); }

template <typename LHS, typename E>
E timesL(const LHS& lhs, const E& rhs) { return lhs*rhs; }

template <typename E, typename RHS>
E timesR(const E& lhs, const RHS& rhs) { return lhs*rhs; }

std::shared_ptr<asc::Module> linkBaseModule(asc::LinkBase& link_base)
{
   return asc::ModuleCore::getModule(link_base.linkedModule().second).shared_from_this();
}

ChaiEngine::ChaiEngine() : ChaiScript(Std_Lib::library())
{
   add(base_class<asc::LinkBase, asc::Link<asc::Module>>());
   add(fun(static_cast<std::shared_ptr<asc::Module>(asc::Link<asc::Module>::*)>(&asc::Link<asc::Module>::module)), "module");
   add(fun(&linkBaseModule), "module");

   add(fun(static_cast<const size_t(asc::Module::*)>(&asc::Module::module_id)), "module_id");

   add(fun([](const size_t id) -> asc::Module& { return asc::ModuleCore::getModule(id); }), "getModule");

   add(fun(static_cast<bool(asc::Link<asc::Module>::*)(const std::string&)>(&asc::Link<asc::Module>::name)), "name");

   add(fun(&asc::hidden::assignModule), "=");
   add(fun(&asc::hidden::assignLinkBase), "=");

   // Register Eigen matrix/vector types
   add(constructor<Eigen::Vector2d(const double x0, const double x1)>(), "Vector2d");
   add(constructor<Eigen::Vector2d(const Eigen::Vector2d&)>(), "Vector2d");
   add(fun(&assign<Eigen::Vector2d>), "=");
   add(fun(&timesL<double, Eigen::Vector2d>), "*");
   add(fun(&timesR<Eigen::Vector2d, double>), "*");
   add(fun(&dot<Eigen::Vector2d>), "dot");

   add(constructor<Eigen::Vector3d(const double x0, const double x1, const double x2)>(), "Vector3d");
   add(constructor<Eigen::Vector3d(const Eigen::Vector3d&)>(), "Vector3d");
   add(fun(&assign<Eigen::Vector3d>), "=");
   add(fun(&timesL<double, Eigen::Vector3d>), "*");
   add(fun(&timesR<Eigen::Vector3d, double>), "*");
   add(fun(&dot<Eigen::Vector3d>), "dot");
   add(fun(&cross<Eigen::Vector3d>), "cross");

   add(constructor<Eigen::Vector4d(const double x0, const double x1, const double x2, const double x3)>(), "Vector4d");
   add(constructor<Eigen::Vector4d(const Eigen::Vector4d&)>(), "Vector4d");
   add(fun(&assign<Eigen::Vector4d>), "=");
   add(fun(&timesL<double, Eigen::Vector4d>), "*");
   add(fun(&timesR<Eigen::Vector4d, double>), "*");
   add(fun(&dot<Eigen::Vector4d>), "dot");

   add(fun(&assign<Eigen::VectorXd>), "=");
   add(fun(&timesL<double, Eigen::VectorXd>), "*");
   add(fun(&timesR<Eigen::VectorXd, double>), "*");
   add(fun(&dot<Eigen::VectorXd>), "dot");

   add(fun(&assign<Eigen::MatrixXd>), "=");
   add(fun(&timesL<double, Eigen::MatrixXd>), "*");
   add(fun(&timesL<Eigen::VectorXd, Eigen::MatrixXd>), "*");
   add(fun(&timesR<Eigen::MatrixXd, double>), "*");
   add(fun(&timesR<Eigen::MatrixXd, Eigen::VectorXd>), "*");
}

bool ChaiEngine::registered(const std::string& module, const std::string& var)
{
   if (!chai_rg.count(module))
      return false;

   std::vector<std::string>& names = chai_rg[module];

   for (auto& name : names)
   {
      if (name == var)
         return true;
   }

   return false;
}


//
// Copyright Jason Rice 2016
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//

#include <mpdef/map.hpp>
#include <mpdef/tree_node.hpp>

#include <boost/hana.hpp>

namespace hana = boost::hana;

int main()
{
  {
    constexpr auto m = mpdef::make_map(
      mpdef::make_tree_node(hana::int_c<1>, hana::int_c<-1>),
      mpdef::make_tree_node(hana::int_c<2>, hana::int_c<-2>),
      mpdef::make_tree_node(hana::int_c<3>, hana::int_c<-3>)
    );

    BOOST_HANA_CONSTANT_ASSERT(hana::at_key(m, hana::int_c<1>) == hana::int_c<-1>);
    BOOST_HANA_CONSTANT_ASSERT(hana::at_key(m, hana::int_c<2>) == hana::int_c<-2>);
    BOOST_HANA_CONSTANT_ASSERT(hana::at_key(m, hana::int_c<3>) == hana::int_c<-3>);

    BOOST_HANA_CONSTANT_ASSERT(m[hana::int_c<1>] == hana::int_c<-1>);
    BOOST_HANA_CONSTANT_ASSERT(m[hana::int_c<2>] == hana::int_c<-2>);
    BOOST_HANA_CONSTANT_ASSERT(m[hana::int_c<3>] == hana::int_c<-3>);

    BOOST_HANA_CONSTANT_ASSERT(hana::find(m, hana::int_c<1>) == hana::just(hana::int_c<-1>));
    BOOST_HANA_CONSTANT_ASSERT(hana::find(m, hana::int_c<2>) == hana::just(hana::int_c<-2>));
    BOOST_HANA_CONSTANT_ASSERT(hana::find(m, hana::int_c<3>) == hana::just(hana::int_c<-3>));
    BOOST_HANA_CONSTANT_ASSERT(hana::find(m, hana::int_c<4>) == hana::nothing);

    BOOST_HANA_CONSTANT_ASSERT(hana::find_if(m, hana::equal.to(hana::int_c<1>))
      == hana::just(hana::int_c<-1>));
    BOOST_HANA_CONSTANT_ASSERT(hana::find_if(m, hana::equal.to(hana::int_c<2>))
      == hana::just(hana::int_c<-2>));
    BOOST_HANA_CONSTANT_ASSERT(hana::find_if(m, hana::equal.to(hana::int_c<3>))
      == hana::just(hana::int_c<-3>));
    BOOST_HANA_CONSTANT_ASSERT(hana::find_if(m, hana::equal.to(hana::int_c<4>))
      == hana::nothing);

    BOOST_HANA_CONSTANT_ASSERT(hana::any_of(m, hana::equal.to(hana::int_c<1>)));
    BOOST_HANA_CONSTANT_ASSERT(hana::any_of(m, hana::equal.to(hana::int_c<2>)));
    BOOST_HANA_CONSTANT_ASSERT(hana::any_of(m, hana::equal.to(hana::int_c<3>)));
    BOOST_HANA_CONSTANT_ASSERT(hana::not_(hana::any_of(m, hana::equal.to(hana::int_c<4>))));

    BOOST_HANA_CONSTANT_ASSERT(hana::contains(m, hana::int_c<1>));
    BOOST_HANA_CONSTANT_ASSERT(hana::contains(m, hana::int_c<2>));
    BOOST_HANA_CONSTANT_ASSERT(hana::contains(m, hana::int_c<3>));
    BOOST_HANA_CONSTANT_ASSERT(hana::not_(hana::contains(m, hana::int_c<4>)));
  }
  {
    constexpr auto a = mpdef::make_map(
      mpdef::make_tree_node(hana::int_c<1>, hana::int_c<-1>),
      mpdef::make_tree_node(hana::int_c<2>, hana::int_c<-2>)
    );
    constexpr auto b = mpdef::make_map(
      mpdef::make_tree_node(hana::int_c<1>, hana::int_c<-1>),
      mpdef::make_tree_node(hana::int_c<2>, hana::int_c<-2>)
    );
    constexpr auto c = mpdef::make_map(
      mpdef::make_tree_node(hana::int_c<2>, hana::int_c<-2>),
      mpdef::make_tree_node(hana::int_c<1>, hana::int_c<-1>)
    );

    BOOST_HANA_CONSTANT_ASSERT(a == b);
    BOOST_HANA_CONSTANT_ASSERT(a == c);
  }
}