/** Returns a (random) coordinate that is between two adjacent MapNodes */
  @Override
  public Coord getInitialLocation(boolean translate) {
    List<MapNode> nodes = map.getNodes();
    MapNode n, n2;
    Coord n2Location, nLocation, placement;
    double dx, dy;
    double rnd = rng.nextDouble();

    // choose a random node (from OK types if such are defined)
    do {
      n = nodes.get(rng.nextInt(nodes.size()));
    } while (okMapNodeTypes != null && !n.isType(okMapNodeTypes));

    // choose a random neighbor of the selected node
    n2 = n.getNeighbors().get(rng.nextInt(n.getNeighbors().size()));

    nLocation = n.getLocation();
    n2Location = n2.getLocation();

    placement = n.getLocation().clone();

    dx = rnd * (n2Location.getX() - nLocation.getX());
    dy = rnd * (n2Location.getY() - nLocation.getY());
    if (translate == false) {
      dx = 0;
      dy = 0;
    }
    placement.translate(dx, dy); // move coord from n towards n2

    this.lastMapNode = n;
    return placement;
  }
  /**
   * Checks that all map nodes can be reached from all other map nodes
   *
   * @param nodes The list of nodes to check
   * @throws SettingsError if all map nodes are not connected
   */
  private void checkMapConnectedness(List<MapNode> nodes) {
    Set<MapNode> visited = new HashSet<MapNode>();
    Queue<MapNode> unvisited = new LinkedList<MapNode>();
    MapNode firstNode;
    MapNode next = null;

    if (nodes.size() == 0) {
      throw new SimError("No map nodes in the given map");
    }

    firstNode = nodes.get(0);

    visited.add(firstNode);
    unvisited.addAll(firstNode.getNeighbors());

    while ((next = unvisited.poll()) != null) {
      visited.add(next);
      for (MapNode n : next.getNeighbors()) {
        if (!visited.contains(n) && !unvisited.contains(n)) {
          unvisited.add(n);
        }
      }
    }

    if (visited.size() != nodes.size()) { // some node couldn't be reached
      MapNode disconnected = null;
      for (MapNode n : nodes) { // find an example node
        if (!visited.contains(n)) {
          disconnected = n;
          break;
        }
      }
      throw new SettingsError(
          "SimMap is not fully connected. Only "
              + visited.size()
              + " out of "
              + nodes.size()
              + " map nodes "
              + "can be reached from "
              + firstNode
              + ". E.g. "
              + disconnected
              + " can't be reached");
    }
  }
  @Override
  public Path getPath() {
    Path p = new Path(generateSpeed());
    MapNode curNode = lastMapNode;
    MapNode prevNode = lastMapNode;
    MapNode nextNode = null;
    List<MapNode> neighbors;
    Coord nextCoord;

    assert lastMapNode != null : "Tried to get a path before placement";

    // start paths from current node
    p.addWaypoint(curNode.getLocation());

    int pathLength = rng.nextInt(maxPathLength - minPathLength) + minPathLength;

    for (int i = 0; i < pathLength; i++) {
      neighbors = curNode.getNeighbors();
      Vector<MapNode> n2 = new Vector<MapNode>(neighbors);
      if (!this.backAllowed) {
        n2.remove(prevNode); // to prevent going back
      }

      if (okMapNodeTypes != null) { // remove neighbor nodes that aren't ok
        for (int j = 0; j < n2.size(); ) {
          if (!n2.get(j).isType(okMapNodeTypes)) {
            n2.remove(j);
          } else {
            j++;
          }
        }
      }

      if (n2.size() == 0) { // only option is to go back
        nextNode = prevNode;
      } else { // choose a random node from remaining neighbors
        nextNode = n2.get(rng.nextInt(n2.size()));
      }

      prevNode = curNode;

      nextCoord = nextNode.getLocation();
      curNode = nextNode;

      p.addWaypoint(nextCoord);
    }

    lastMapNode = curNode;

    return p;
  }