/**
  * Makes a new empty leg from a starting edge
  *
  * @param itinerary
  */
 private Leg makeLeg(Itinerary itinerary, State s) {
   Leg leg = new Leg();
   itinerary.addLeg(leg);
   leg.startTime = makeCalendar(s.getBackState());
   EdgeNarrative en = s.getBackEdgeNarrative();
   leg.distance = 0.0;
   leg.from = makePlace(s.getBackState(), false);
   leg.mode = en.getMode().toString();
   return leg;
 }
 /**
  * Makes a new empty leg from a starting edge
  *
  * @param itinerary
  */
 private Leg makeLeg(Itinerary itinerary, State s) {
   Leg leg = new Leg();
   itinerary.addLeg(leg);
   leg.startTime = makeCalendar(s.getBackState());
   leg.distance = 0.0;
   String name;
   Edge backEdge = s.getBackEdge();
   if (backEdge instanceof StreetEdge) {
     name = backEdge.getName();
   } else {
     name = s.getVertex().getName();
   }
   leg.from = makePlace(s.getBackState(), name, false);
   leg.mode = s.getBackMode().toString();
   if (s.isBikeRenting()) {
     leg.rentedBike = true;
   }
   return leg;
 }
  /**
   * Generate an itinerary from a @{link GraphPath}. The algorithm here is to walk over each state
   * in the graph path, accumulating geometry, time, and length data from the incoming edge. When
   * the incoming edge and outgoing edge have different modes (or when a vehicle changes names due
   * to interlining) a new leg is generated. Street legs undergo an additional processing step to
   * generate turn-by-turn directions.
   *
   * @param path
   * @param showIntermediateStops whether intermediate stops are included in the generated itinerary
   * @return itinerary
   */
  private Itinerary generateItinerary(GraphPath path, boolean showIntermediateStops) {
    Graph graph = path.getRoutingContext().graph;
    TransitIndexService transitIndex = graph.getService(TransitIndexService.class);

    Itinerary itinerary = makeEmptyItinerary(path);
    EdgeNarrative postponedAlerts = null;
    Leg leg = null;
    CoordinateArrayListSequence coordinates = new CoordinateArrayListSequence();
    double previousElevation = Double.MAX_VALUE;
    int startWalk = -1;
    int i = -1;
    PlanGenState pgstate = PlanGenState.START;
    String nextName = null;
    for (State state : path.states) {
      i += 1;
      Edge backEdge = state.getBackEdge();
      EdgeNarrative backEdgeNarrative = state.getBackEdgeNarrative();
      if (backEdge == null) {
        continue;
      }

      TraverseMode mode = backEdgeNarrative.getMode();
      if (mode != null) {
        long dt = state.getAbsTimeDeltaSec();
        if (mode == TraverseMode.BOARDING
            || mode == TraverseMode.ALIGHTING
            || mode == TraverseMode.STL) {
          itinerary.waitingTime += dt;
        } else if (mode.isOnStreetNonTransit()) {
          itinerary.walkDistance += backEdgeNarrative.getDistance();
          itinerary.walkTime += dt;
        } else if (mode.isTransit()) {
          itinerary.transitTime += dt;
        }
      }

      if (backEdge instanceof FreeEdge) {
        if (backEdge instanceof PreBoardEdge) {
          // Add boarding alerts to the next leg
          postponedAlerts = backEdgeNarrative;
        } else if (backEdge instanceof PreAlightEdge) {
          // Add alighting alerts to the previous leg
          addNotesToLeg(itinerary.legs.get(itinerary.legs.size() - 1), backEdgeNarrative);
        }
        continue;
      }

      if (backEdge instanceof EdgeWithElevation) {
        PackedCoordinateSequence profile = ((EdgeWithElevation) backEdge).getElevationProfile();
        previousElevation = applyElevation(profile, itinerary, previousElevation);
      }

      switch (pgstate) {
        case START:
          if (mode == TraverseMode.WALK) {
            pgstate = PlanGenState.WALK;
            leg = makeLeg(itinerary, state);
            leg.from.orig = nextName;
            startWalk = i;
          } else if (mode == TraverseMode.BICYCLE) {
            pgstate = PlanGenState.BICYCLE;
            leg = makeLeg(itinerary, state);
            leg.from.orig = nextName;
            startWalk = i;
          } else if (mode == TraverseMode.CAR) {
            pgstate = PlanGenState.CAR;
            leg = makeLeg(itinerary, state);
            leg.from.orig = nextName;
            startWalk = i;
          } else if (mode == TraverseMode.BOARDING) {
            // this itinerary starts with transit
            pgstate = PlanGenState.PRETRANSIT;
            leg = makeLeg(itinerary, state);
            leg.from.orig = nextName;
            startWalk = -1;
          } else if (mode == TraverseMode.STL) {
            // this comes after an alight; do nothing
          } else if (mode == TraverseMode.TRANSFER) {
            // handle the whole thing in one step
            leg = makeLeg(itinerary, state);
            coordinates = new CoordinateArrayListSequence();
            coordinates.add(state.getBackState().getVertex().getCoordinate());
            coordinates.add(state.getVertex().getCoordinate());
            finalizeLeg(leg, state, path.states, i, i, coordinates);
            coordinates.clear();
          } else {
            LOG.error("Unexpected state (in START): " + mode);
          }
          break;
        case WALK:
          if (leg == null) {
            leg = makeLeg(itinerary, state);
          }
          if (mode == TraverseMode.WALK) {
            // do nothing
          } else if (mode == TraverseMode.BICYCLE) {
            finalizeLeg(leg, state, path.states, startWalk, i, coordinates);
            startWalk = i;
            leg = makeLeg(itinerary, state);
            if (backEdge instanceof RentABikeOnEdge) {
              leg.rentedBike = true;
            }
            pgstate = PlanGenState.BICYCLE;
          } else if (mode == TraverseMode.STL) {
            finalizeLeg(leg, state, path.states, startWalk, i, coordinates);
            leg = null;
            pgstate = PlanGenState.PRETRANSIT;
          } else if (mode == TraverseMode.BOARDING) {
            // this only happens in case of a timed transfer.
            pgstate = PlanGenState.PRETRANSIT;
            finalizeLeg(leg, state, path.states, startWalk, i, coordinates);
            leg = makeLeg(itinerary, state);
            itinerary.transfers++;
          } else if (backEdgeNarrative instanceof LegSwitchingEdge) {
            nextName = state.getBackState().getBackState().getBackState().getVertex().getName();
            finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates);
            leg = null;
            pgstate = PlanGenState.START;
          } else {
            LOG.error("Unexpected state (in WALK): " + mode);
          }
          break;
        case BICYCLE:
          if (leg == null) {
            leg = makeLeg(itinerary, state);
          }
          if (mode == TraverseMode.BICYCLE) {
            // do nothing
          } else if (mode == TraverseMode.WALK) {
            finalizeLeg(leg, state, path.states, startWalk, i, coordinates);
            leg = makeLeg(itinerary, state);
            startWalk = i;
            pgstate = PlanGenState.WALK;
          } else if (mode == TraverseMode.STL) {
            finalizeLeg(leg, state, path.states, startWalk, i, coordinates);
            leg = null;
            pgstate = PlanGenState.PRETRANSIT;
          } else if (backEdgeNarrative instanceof LegSwitchingEdge) {
            finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates);
            leg = null;
            pgstate = PlanGenState.START;
          } else {
            LOG.error("Unexpected state (in BICYCLE): " + mode);
          }
          break;
        case CAR:
          if (leg == null) {
            leg = makeLeg(itinerary, state);
          }
          if (mode == TraverseMode.CAR) {
            // do nothing
          } else if (mode == TraverseMode.STL) {
            finalizeLeg(leg, state, path.states, startWalk, i, coordinates);
            leg = null;
            pgstate = PlanGenState.PRETRANSIT;
          } else if (backEdgeNarrative instanceof LegSwitchingEdge) {
            finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates);
            leg = null;
            pgstate = PlanGenState.START;
          } else {
            LOG.error("Unexpected state (in CAR): " + mode);
          }
          break;
        case PRETRANSIT:
          if (mode == TraverseMode.BOARDING) {
            if (leg != null) {
              LOG.error("leg unexpectedly not null (boarding loop)");
            } else {
              leg = makeLeg(itinerary, state);
              leg.stop = new ArrayList<Place>();
              itinerary.transfers++;
              leg.boardRule = (String) state.getExtension("boardAlightRule");
            }
          } else if (backEdge instanceof HopEdge) {
            pgstate = PlanGenState.TRANSIT;
            fixupTransitLeg(leg, state, transitIndex);
            leg.stop = new ArrayList<Place>();
          } else {
            LOG.error("Unexpected state (in PRETRANSIT): " + mode);
          }
          break;
        case TRANSIT:
          String route = backEdgeNarrative.getName();
          if (mode == TraverseMode.ALIGHTING) {
            if (showIntermediateStops && leg.stop != null && leg.stop.size() > 0) {
              if (leg.stop.isEmpty()) {
                leg.stop = null;
              }
            }
            leg.alightRule = (String) state.getExtension("boardAlightRule");
            finalizeLeg(leg, state, null, -1, -1, coordinates);
            leg = null;
            pgstate = PlanGenState.START;
          } else if (mode.toString().equals(leg.mode)) {
            // no mode change, handle intermediate stops
            if (showIntermediateStops) {
              /*
               * any further transit edge, add "from" vertex to intermediate stops
               */
              if (!(backEdge instanceof DwellEdge)) {
                Place stop = makePlace(state.getBackState(), true);
                leg.stop.add(stop);
              } else if (leg.stop.size() > 0) {
                leg.stop.get(leg.stop.size() - 1).departure = makeCalendar(state);
              }
            }
            if (!route.equals(leg.route)) {
              // interline dwell
              finalizeLeg(leg, state, null, -1, -1, coordinates);
              leg = makeLeg(itinerary, state);
              leg.stop = new ArrayList<Place>();
              fixupTransitLeg(leg, state, transitIndex);
              leg.startTime = makeCalendar(state);
              leg.interlineWithPreviousLeg = true;
            }
          } else {
            LOG.error("Unexpected state (in TRANSIT): " + mode);
          }
          break;
      }
      if (leg != null) {
        leg.distance += backEdgeNarrative.getDistance();
        Geometry edgeGeometry = backEdgeNarrative.getGeometry();
        if (edgeGeometry != null) {
          Coordinate[] edgeCoordinates = edgeGeometry.getCoordinates();
          if (coordinates.size() > 0
              && coordinates.getCoordinate(coordinates.size() - 1).equals(edgeCoordinates[0])) {
            coordinates.extend(edgeCoordinates, 1);
          } else {
            coordinates.extend(edgeCoordinates);
          }
        }

        if (postponedAlerts != null) {
          addNotesToLeg(leg, postponedAlerts);
          postponedAlerts = null;
        }

        addNotesToLeg(leg, backEdgeNarrative);
      }
    } /* end loop over graphPath edge list */

    if (leg != null) {
      finalizeLeg(leg, path.states.getLast(), path.states, startWalk, i, coordinates);
    }
    itinerary.removeBogusLegs();
    itinerary.fixupDates(graph.getService(CalendarServiceData.class));
    if (itinerary.legs.size() == 0) throw new TrivialPathException();
    return itinerary;
  }
  /**
   * Generate an itinerary from a @{link GraphPath}. The algorithm here is to walk over each state
   * in the graph path, accumulating geometry, time, and length data from the incoming edge. When
   * the incoming edge and outgoing edge have different modes (or when a vehicle changes names due
   * to interlining) a new leg is generated. Street legs undergo an additional processing step to
   * generate turn-by-turn directions.
   *
   * @param path
   * @param showIntermediateStops whether intermediate stops are included in the generated itinerary
   * @return itinerary
   */
  private Itinerary generateItinerary(GraphPath path, boolean showIntermediateStops) {
    Graph graph = path.getRoutingContext().graph;
    TransitIndexService transitIndex = graph.getService(TransitIndexService.class);

    Itinerary itinerary = makeEmptyItinerary(path);
    Set<Alert> postponedAlerts = null;
    Leg leg = null;
    CoordinateArrayListSequence coordinates = new CoordinateArrayListSequence();
    double previousElevation = Double.MAX_VALUE;
    int startWalk = -1;
    int i = -1;
    boolean foldingElevatorLegIntoCycleLeg = false;
    PlanGenState pgstate = PlanGenState.START;
    String nextName = null;
    for (State state : path.states) {
      i += 1;
      Edge backEdge = state.getBackEdge();
      if (backEdge == null) {
        continue;
      }

      // debug: push vehicle late status out to UI
      //            if (backEdge instanceof PatternHop) {
      //                TripTimes tt = state.getTripTimes();
      //                int hop = ((PatternHop)backEdge).stopIndex;
      //                LOG.info("{} {}", tt.getTrip().toString(), hop);
      //                if ( ! tt.isScheduled()) {
      //                    int delay = tt.getDepartureDelay(hop);
      //                    String d = "on time";
      //                    if (Math.abs(delay) > 10) {
      //                        d = String.format("%2.1f min %s", delay / 60.0,
      //                                (delay < 0) ? "early" : "late");
      //                    }
      //                    d = "Using real-time delay information: ".concat(d);
      //                    leg.addAlert(Alert.createSimpleAlerts(d));
      //                    LOG.info(d);
      //                }
      //                else {
      //                    leg.addAlert(Alert.createSimpleAlerts("Using published timetables."));
      //                    LOG.info("sched");
      //                }
      //            }

      TraverseMode mode = state.getBackMode();
      if (mode != null) {
        long dt = state.getAbsTimeDeltaSec();
        if (mode == TraverseMode.BOARDING
            || mode == TraverseMode.ALIGHTING
            || mode == TraverseMode.STL) {
          itinerary.waitingTime += dt;
        } else if (mode.isOnStreetNonTransit()) {
          itinerary.walkDistance += backEdge.getDistance();
          itinerary.walkTime += dt;
        } else if (mode.isTransit()) {
          itinerary.transitTime += dt;
        }
      }

      if (backEdge instanceof FreeEdge) {
        if (backEdge instanceof PreBoardEdge) {
          // Add boarding alerts to the next leg
          postponedAlerts = state.getBackAlerts();
        } else if (backEdge instanceof PreAlightEdge) {
          // Add alighting alerts to the previous leg
          addNotesToLeg(itinerary.legs.get(itinerary.legs.size() - 1), state.getBackAlerts());
        }
        continue;
      }

      if (backEdge instanceof EdgeWithElevation) {
        PackedCoordinateSequence profile = ((EdgeWithElevation) backEdge).getElevationProfile();
        previousElevation = applyElevation(profile, itinerary, previousElevation);
      }

      switch (pgstate) {
        case START:
          if (mode == TraverseMode.WALK) {
            pgstate = PlanGenState.WALK;
            leg = makeLeg(itinerary, state);
            leg.from.orig = nextName;
            startWalk = i;
          } else if (mode == TraverseMode.BICYCLE) {
            pgstate = PlanGenState.BICYCLE;
            leg = makeLeg(itinerary, state);
            leg.from.orig = nextName;
            startWalk = i;
          } else if (mode == TraverseMode.CAR) {
            pgstate = PlanGenState.CAR;
            leg = makeLeg(itinerary, state);
            leg.from.orig = nextName;
            startWalk = i;
          } else if (mode == TraverseMode.BOARDING) {
            // this itinerary starts with transit
            pgstate = PlanGenState.PRETRANSIT;
            leg = makeLeg(itinerary, state);
            leg.from.orig = nextName;
            itinerary.transfers++;
            startWalk = -1;
          } else if (mode == TraverseMode.STL) {
            // this comes after an alight; do nothing
          } else if (mode == TraverseMode.TRANSFER) {
            // handle the whole thing in one step
            leg = makeLeg(itinerary, state);
            coordinates = new CoordinateArrayListSequence();
            coordinates.add(state.getBackState().getVertex().getCoordinate());
            coordinates.add(state.getVertex().getCoordinate());
            finalizeLeg(leg, state, path.states, i, i, coordinates, itinerary);
            coordinates.clear();
          } else {
            LOG.error("Unexpected state (in START): " + mode);
          }
          break;
        case WALK:
          if (leg == null) {
            leg = makeLeg(itinerary, state);
          }
          if (mode == TraverseMode.WALK) {
            // do nothing
          } else if (mode == TraverseMode.BICYCLE) {
            finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary);
            startWalk = i;
            leg = makeLeg(itinerary, state);
            pgstate = PlanGenState.BICYCLE;
          } else if (mode == TraverseMode.STL) {
            finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary);
            leg = null;
            pgstate = PlanGenState.PRETRANSIT;
          } else if (mode == TraverseMode.BOARDING) {
            // this only happens in case of a timed transfer.
            pgstate = PlanGenState.PRETRANSIT;
            finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary);
            leg = makeLeg(itinerary, state);
            itinerary.transfers++;
          } else if (backEdge instanceof LegSwitchingEdge) {
            nextName = state.getBackState().getBackState().getBackState().getVertex().getName();
            finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates, itinerary);
            leg = null;
            pgstate = PlanGenState.START;
          } else {
            LOG.error("Unexpected state (in WALK): " + mode);
          }
          break;
        case BICYCLE:
          if (leg == null) {
            leg = makeLeg(itinerary, state);
          }

          // If there are elevator edges that have mode == BICYCLE on both sides, they should
          // be folded into the bicycle leg. But ones with walk on one side or the other should
          // not
          if (state.getBackEdge() instanceof ElevatorBoardEdge) {
            int j = i + 1;
            // proceed forward from the current state until we find one that isn't on an
            // elevator, and check the traverse mode
            while (path.states.get(j).getBackEdge() instanceof ElevatorEdge) j++;

            // path.states[j] is not an elevator edge
            if (path.states.get(j).getBackMode() == TraverseMode.BICYCLE)
              foldingElevatorLegIntoCycleLeg = true;
          }

          if (foldingElevatorLegIntoCycleLeg) {
            if (state.getBackEdge() instanceof ElevatorEdge) {
              break; // from the case
            } else {
              foldingElevatorLegIntoCycleLeg = false;
              // do not break but allow it to be processed below (which will do nothing)
            }
          }

          if (mode == TraverseMode.BICYCLE) {
            // do nothing
          } else if (mode == TraverseMode.WALK) {
            finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary);
            leg = makeLeg(itinerary, state);
            startWalk = i;
            pgstate = PlanGenState.WALK;
          } else if (mode == TraverseMode.STL) {
            finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary);
            startWalk = i;
            leg = null;
            pgstate = PlanGenState.PRETRANSIT;
          } else if (backEdge instanceof LegSwitchingEdge) {
            finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates, itinerary);
            leg = null;
            pgstate = PlanGenState.START;
          } else {
            LOG.error("Unexpected state (in BICYCLE): " + mode);
          }
          break;
        case CAR:
          if (leg == null) {
            leg = makeLeg(itinerary, state);
          }
          if (mode == TraverseMode.CAR) {
            // do nothing
          } else if (mode == TraverseMode.STL) {
            finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary);
            leg = null;
            pgstate = PlanGenState.PRETRANSIT;
          } else if (backEdge instanceof LegSwitchingEdge) {
            finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates, itinerary);
            leg = null;
            pgstate = PlanGenState.START;
          } else {
            LOG.error("Unexpected state (in CAR): " + mode);
          }
          break;
        case PRETRANSIT:
          if (mode == TraverseMode.BOARDING) {
            if (leg != null) {
              LOG.error("leg unexpectedly not null (boarding loop)");
            } else {
              leg = makeLeg(itinerary, state);
              leg.from.stopIndex = ((OnBoardForwardEdge) backEdge).getStopIndex();
              leg.stop = new ArrayList<Place>();
              itinerary.transfers++;
              leg.boardRule = (String) state.getExtension("boardAlightRule");
            }
          } else if (backEdge instanceof HopEdge) {
            pgstate = PlanGenState.TRANSIT;
            fixupTransitLeg(leg, state, transitIndex);
            leg.stop = new ArrayList<Place>();
          } else {
            LOG.error("Unexpected state (in PRETRANSIT): " + mode);
          }
          break;
        case TRANSIT:
          String route = backEdge.getName();
          if (mode == TraverseMode.ALIGHTING) {
            if (showIntermediateStops && leg.stop != null && leg.stop.size() > 0) {
              if (leg.stop.isEmpty()) {
                leg.stop = null;
              }
            }
            leg.alightRule = (String) state.getExtension("boardAlightRule");
            finalizeLeg(leg, state, null, -1, -1, coordinates, itinerary);
            leg = null;
            pgstate = PlanGenState.START;
          } else if (mode.toString().equals(leg.mode)) {
            // no mode change, handle intermediate stops
            if (showIntermediateStops) {
              /*
               * any further transit edge, add "from" vertex to intermediate stops
               */
              if (!(backEdge instanceof DwellEdge)) {
                Place stop =
                    makePlace(
                        state.getBackState(), state.getBackState().getVertex().getName(), true);
                leg.stop.add(stop);
              } else if (leg.stop.size() > 0) {
                leg.stop.get(leg.stop.size() - 1).departure = makeCalendar(state);
              }
            }
            if (!route.equals(leg.route)) {
              // interline dwell
              finalizeLeg(leg, state, null, -1, -1, coordinates, itinerary);
              leg = makeLeg(itinerary, state);
              leg.stop = new ArrayList<Place>();
              fixupTransitLeg(leg, state, transitIndex);
              leg.startTime = makeCalendar(state);
              leg.interlineWithPreviousLeg = true;
            }
          } else {
            LOG.error("Unexpected state (in TRANSIT): " + mode);
          }
          break;
      }
      if (leg != null) {
        leg.distance += backEdge.getDistance();
        Geometry edgeGeometry = backEdge.getGeometry();
        if (edgeGeometry != null) {
          Coordinate[] edgeCoordinates = edgeGeometry.getCoordinates();
          if (coordinates.size() > 0
              && coordinates.getCoordinate(coordinates.size() - 1).equals(edgeCoordinates[0])) {
            coordinates.extend(edgeCoordinates, 1);
          } else {
            coordinates.extend(edgeCoordinates);
          }
        }

        if (postponedAlerts != null) {
          addNotesToLeg(leg, postponedAlerts);
          postponedAlerts = null;
        }

        addNotesToLeg(leg, state.getBackAlerts());
      }
    } /* end loop over graphPath edge list */

    if (leg != null) {
      finalizeLeg(leg, path.states.getLast(), path.states, startWalk, i, coordinates, itinerary);
    }
    itinerary.removeBogusLegs();
    itinerary.fixupDates(graph.getService(CalendarServiceData.class));
    if (itinerary.legs.size() == 0) throw new TrivialPathException();
    return itinerary;
  }