/** Make sure that stops are properly linked into the graph */
  @Test
  public void testStopLinking() throws Exception {
    AddTripPattern atp = getAddTripPattern(RouteSelector.BROAD_HIGH);
    atp.timetables.add(getTimetable(true));

    // get a graph
    Graph g = buildGraphNoTransit();
    link(g);
    g.index(new DefaultStreetVertexIndexFactory());

    // materialize the trip pattern
    atp.materialize(g);

    // there should be five stops because one point is not a stop
    assertEquals(5, atp.temporaryStops.length);

    // they should all be linked into the graph
    for (int i = 0; i < atp.temporaryStops.length; i++) {
      assertNotNull(atp.temporaryStops[i].sample);
      assertNotNull(atp.temporaryStops[i].sample.v0);
      assertNotNull(atp.temporaryStops[i].sample.v1);
    }

    // no services running: not needed for trips added on the fly.
    TimeWindow window = new TimeWindow(7 * 3600, 9 * 3600, new BitSet(), DayOfWeek.WEDNESDAY);

    Scenario scenario = new Scenario(0);
    scenario.modifications = Lists.newArrayList(atp);
    ProfileRequest req = new ProfileRequest();
    req.scenario = scenario;
    req.boardingAssumption = RaptorWorkerTimetable.BoardingAssumption.WORST_CASE;

    RaptorWorkerData data = new RaptorWorkerData(g, window, req);
    assertEquals(5, data.nStops);

    // make sure we can find the stops
    AStar aStar = new AStar();
    RoutingRequest rr = new RoutingRequest(TraverseMode.WALK);
    rr.from = new GenericLocation(39.963417, -82.980799);
    rr.batch = true;
    rr.setRoutingContext(g);
    rr.batch = true;

    ShortestPathTree spt = aStar.getShortestPathTree(rr);

    TIntIntMap stops = data.findStopsNear(spt, g);

    // we should have found stops
    assertFalse(stops.isEmpty());

    // ensure that the times made it into the data
    // This assumes worst-case departure, and the first worst departure is 10:30 after the service
    // starts running (dwell + headway)
    assertEquals(
        4 * 3600 + 600 + 30,
        data.timetablesForPattern.get(0).getFrequencyDeparture(0, 0, 39 * 360, -1, null));
  }
  /**
   * Return the graph for the given unique identifier for graph builder inputs on S3. If this is the
   * same as the last graph built, just return the pre-built graph. If not, build the graph from the
   * inputs, fetching them from S3 to the local cache as needed.
   */
  public synchronized Graph getGraph(String graphId) {

    LOG.info("Finding a graph for ID {}", graphId);

    if (graphId.equals(currGraphId)) {
      LOG.info("GraphID has not changed. Reusing the last graph that was built.");
      return currGraph;
    }

    // The location of the inputs that will be used to build this graph
    File graphDataDirectory = new File(GRAPH_CACHE_DIR, graphId);

    // If we don't have a local copy of the inputs, fetch graph data as a ZIP from S3 and unzip it
    if (!graphDataDirectory.exists() || graphDataDirectory.list().length == 0) {
      LOG.info("Downloading graph input files.");
      graphDataDirectory.mkdirs();
      S3Object graphDataZipObject = s3.getObject(graphBucket, graphId + ".zip");
      ZipInputStream zis = new ZipInputStream(graphDataZipObject.getObjectContent());
      try {
        ZipEntry entry;
        while ((entry = zis.getNextEntry()) != null) {
          File entryDestination = new File(graphDataDirectory, entry.getName());
          // Are both these mkdirs calls necessary?
          entryDestination.getParentFile().mkdirs();
          if (entry.isDirectory()) entryDestination.mkdirs();
          else {
            OutputStream entryFileOut = new FileOutputStream(entryDestination);
            IOUtils.copy(zis, entryFileOut);
            entryFileOut.close();
          }
        }
        zis.close();
      } catch (Exception e) {
        // TODO delete graph cache dir which is probably corrupted
        LOG.info("Error retrieving graph files", e);
      }
    } else {
      LOG.info("Graph input files were found locally. Using these files from the cache.");
    }

    // Now we have a local copy of these graph inputs. Make a graph out of them.
    CommandLineParameters params = new CommandLineParameters();
    params.build = new File(GRAPH_CACHE_DIR, graphId);
    params.inMemory = true;
    GraphBuilder graphBuilder = GraphBuilder.forDirectory(params, params.build);
    graphBuilder.run();
    Graph graph = graphBuilder.getGraph();
    graph.routerId = graphId;
    graph.index(new DefaultStreetVertexIndexFactory());
    graph.index.clusterStopsAsNeeded();
    this.currGraphId = graphId;
    this.currGraph = graph;
    return graph;
  }
  @Test
  public final void testOnBoardDepartureTime() {
    Coordinate[] coordinates = new Coordinate[5];
    coordinates[0] = new Coordinate(0.0, 0.0);
    coordinates[1] = new Coordinate(0.0, 1.0);
    coordinates[2] = new Coordinate(2.0, 1.0);
    coordinates[3] = new Coordinate(5.0, 1.0);
    coordinates[4] = new Coordinate(5.0, 5.0);

    PatternDepartVertex depart = mock(PatternDepartVertex.class);
    PatternArriveVertex dwell = mock(PatternArriveVertex.class);
    PatternArriveVertex arrive = mock(PatternArriveVertex.class);
    Graph graph = mock(Graph.class);
    RoutingRequest routingRequest = mock(RoutingRequest.class);
    ServiceDay serviceDay = mock(ServiceDay.class);

    when(graph.getTimeZone()).thenReturn(TimeZone.getTimeZone("GMT"));

    GeometryFactory geometryFactory = GeometryUtils.getGeometryFactory();
    CoordinateSequenceFactory coordinateSequenceFactory =
        geometryFactory.getCoordinateSequenceFactory();
    CoordinateSequence coordinateSequence = coordinateSequenceFactory.create(coordinates);
    LineString geometry = new LineString(coordinateSequence, geometryFactory);
    ArrayList<Edge> hops = new ArrayList<Edge>(2);
    RoutingContext routingContext = new RoutingContext(routingRequest, graph, null, arrive);
    AgencyAndId agencyAndId = new AgencyAndId("Agency", "ID");
    Route route = new Route();
    ArrayList<StopTime> stopTimes = new ArrayList<StopTime>(3);
    StopTime stopDepartTime = new StopTime();
    StopTime stopDwellTime = new StopTime();
    StopTime stopArriveTime = new StopTime();
    Stop stopDepart = new Stop();
    Stop stopDwell = new Stop();
    Stop stopArrive = new Stop();
    Trip trip = new Trip();

    routingContext.serviceDays = new ArrayList<ServiceDay>(Collections.singletonList(serviceDay));
    route.setId(agencyAndId);
    stopDepart.setId(agencyAndId);
    stopDwell.setId(agencyAndId);
    stopArrive.setId(agencyAndId);
    stopDepartTime.setStop(stopDepart);
    stopDepartTime.setDepartureTime(0);
    stopDwellTime.setArrivalTime(20);
    stopDwellTime.setStop(stopDwell);
    stopDwellTime.setDepartureTime(40);
    stopArriveTime.setArrivalTime(60);
    stopArriveTime.setStop(stopArrive);
    stopTimes.add(stopDepartTime);
    stopTimes.add(stopDwellTime);
    stopTimes.add(stopArriveTime);
    trip.setId(agencyAndId);
    trip.setTripHeadsign("The right");

    TripTimes tripTimes = new TripTimes(trip, stopTimes, new Deduplicator());
    StopPattern stopPattern = new StopPattern(stopTimes);
    TripPattern tripPattern = new TripPattern(route, stopPattern);

    when(depart.getTripPattern()).thenReturn(tripPattern);
    when(dwell.getTripPattern()).thenReturn(tripPattern);

    PatternHop patternHop0 = new PatternHop(depart, dwell, stopDepart, stopDwell, 0);
    PatternHop patternHop1 = new PatternHop(dwell, arrive, stopDwell, stopArrive, 1);

    hops.add(patternHop0);
    hops.add(patternHop1);

    when(graph.getEdges()).thenReturn(hops);
    when(depart.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(dwell.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(arrive.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(routingRequest.getFrom()).thenReturn(new GenericLocation());
    when(routingRequest.getStartingTransitTripId()).thenReturn(agencyAndId);
    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(9);

    patternHop0.setGeometry(geometry);
    tripPattern.add(tripTimes);
    graph.index = new GraphIndex(graph);

    coordinates = new Coordinate[3];
    coordinates[0] = new Coordinate(3.5, 1.0);
    coordinates[1] = new Coordinate(5.0, 1.0);
    coordinates[2] = new Coordinate(5.0, 5.0);

    coordinateSequence = coordinateSequenceFactory.create(coordinates);
    geometry = new LineString(coordinateSequence, geometryFactory);

    Vertex vertex = onBoardDepartServiceImpl.setupDepartOnBoard(routingContext);
    Edge edge = vertex.getOutgoing().toArray(new Edge[1])[0];

    assertEquals(vertex, edge.getFromVertex());
    assertEquals(dwell, edge.getToVertex());
    assertEquals("The right", edge.getDirection());
    assertEquals(geometry, edge.getGeometry());

    assertEquals(coordinates[0].x, vertex.getX(), 0.0);
    assertEquals(coordinates[0].y, vertex.getY(), 0.0);
  }
  @Test
  public final void testOnBoardAtStation() {
    TransitStop station0 = mock(TransitStop.class);
    TransitStop station1 = mock(TransitStop.class);
    TransitStop station2 = mock(TransitStop.class);
    PatternDepartVertex depart = mock(PatternDepartVertex.class);
    PatternArriveVertex dwell = mock(PatternArriveVertex.class);
    PatternArriveVertex arrive = mock(PatternArriveVertex.class);
    Graph graph = mock(Graph.class);
    RoutingRequest routingRequest = mock(RoutingRequest.class);
    ServiceDay serviceDay = mock(ServiceDay.class);

    when(graph.getTimeZone()).thenReturn(TimeZone.getTimeZone("GMT"));

    ArrayList<Edge> hops = new ArrayList<Edge>(2);
    RoutingContext routingContext = new RoutingContext(routingRequest, graph, null, arrive);
    AgencyAndId agencyAndId = new AgencyAndId("Agency", "ID");
    Route route = new Route();
    ArrayList<StopTime> stopTimes = new ArrayList<StopTime>(2);
    StopTime stopDepartTime = new StopTime();
    StopTime stopDwellTime = new StopTime();
    StopTime stopArriveTime = new StopTime();
    Stop stopDepart = new Stop();
    Stop stopDwell = new Stop();
    Stop stopArrive = new Stop();
    Trip trip = new Trip();

    routingContext.serviceDays = new ArrayList<ServiceDay>(Collections.singletonList(serviceDay));
    route.setId(agencyAndId);
    stopDepart.setId(new AgencyAndId("Station", "0"));
    stopDwell.setId(new AgencyAndId("Station", "1"));
    stopArrive.setId(new AgencyAndId("Station", "2"));
    stopDepartTime.setStop(stopDepart);
    stopDepartTime.setDepartureTime(0);
    stopDwellTime.setArrivalTime(20);
    stopDwellTime.setStop(stopDwell);
    stopDwellTime.setDepartureTime(40);
    stopArriveTime.setArrivalTime(60);
    stopArriveTime.setStop(stopArrive);
    stopTimes.add(stopDepartTime);
    stopTimes.add(stopDwellTime);
    stopTimes.add(stopArriveTime);
    trip.setId(agencyAndId);

    TripTimes tripTimes = new TripTimes(trip, stopTimes, new Deduplicator());
    StopPattern stopPattern = new StopPattern(stopTimes);
    TripPattern tripPattern = new TripPattern(route, stopPattern);

    when(depart.getTripPattern()).thenReturn(tripPattern);
    when(dwell.getTripPattern()).thenReturn(tripPattern);

    PatternHop patternHop0 = new PatternHop(depart, dwell, stopDepart, stopDwell, 0);
    PatternHop patternHop1 = new PatternHop(dwell, arrive, stopDwell, stopArrive, 1);

    hops.add(patternHop0);
    hops.add(patternHop1);

    when(graph.getEdges()).thenReturn(hops);
    when(depart.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(dwell.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(arrive.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(routingRequest.getFrom()).thenReturn(new GenericLocation());
    when(routingRequest.getStartingTransitTripId()).thenReturn(agencyAndId);
    when(graph.getVertex("Station_0")).thenReturn(station0);
    when(graph.getVertex("Station_1")).thenReturn(station1);
    when(graph.getVertex("Station_2")).thenReturn(station2);

    tripPattern.add(tripTimes);
    graph.index = new GraphIndex(graph);

    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(0);
    assertEquals(station0, onBoardDepartServiceImpl.setupDepartOnBoard(routingContext));

    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(20);
    assertEquals(station1, onBoardDepartServiceImpl.setupDepartOnBoard(routingContext));

    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(30);
    assertEquals(station1, onBoardDepartServiceImpl.setupDepartOnBoard(routingContext));

    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(40);
    assertEquals(station1, onBoardDepartServiceImpl.setupDepartOnBoard(routingContext));

    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(60);
    assertEquals(station2, onBoardDepartServiceImpl.setupDepartOnBoard(routingContext));
  }
  @Test
  public final void testOnBoardDepartureAtArrivalTime() {
    Coordinate[] coordinates = new Coordinate[2];
    coordinates[0] = new Coordinate(0.0, 0.0);
    coordinates[1] = new Coordinate(0.0, 1.0);

    TransitStop station0 = mock(TransitStop.class);
    TransitStop station1 = mock(TransitStop.class);
    PatternDepartVertex depart = mock(PatternDepartVertex.class);
    PatternArriveVertex arrive = mock(PatternArriveVertex.class);
    Graph graph = mock(Graph.class);
    RoutingRequest routingRequest = mock(RoutingRequest.class);
    ServiceDay serviceDay = mock(ServiceDay.class);

    when(graph.getTimeZone()).thenReturn(TimeZone.getTimeZone("GMT"));
    when(station0.getX()).thenReturn(coordinates[0].x);
    when(station0.getY()).thenReturn(coordinates[0].y);
    when(station1.getX()).thenReturn(coordinates[1].x);
    when(station1.getY()).thenReturn(coordinates[1].y);

    RoutingContext routingContext = new RoutingContext(routingRequest, graph, null, arrive);
    AgencyAndId agencyAndId = new AgencyAndId("Agency", "ID");
    Route route = new Route();
    ArrayList<StopTime> stopTimes = new ArrayList<StopTime>(2);
    StopTime stopDepartTime = new StopTime();
    StopTime stopArriveTime = new StopTime();
    Stop stopDepart = new Stop();
    Stop stopArrive = new Stop();
    Trip trip = new Trip();

    routingContext.serviceDays = new ArrayList<ServiceDay>(Collections.singletonList(serviceDay));
    route.setId(agencyAndId);
    stopDepart.setId(new AgencyAndId("Station", "0"));
    stopArrive.setId(new AgencyAndId("Station", "1"));
    stopDepartTime.setStop(stopDepart);
    stopDepartTime.setDepartureTime(0);
    stopArriveTime.setArrivalTime(10);
    stopArriveTime.setStop(stopArrive);
    stopTimes.add(stopDepartTime);
    stopTimes.add(stopArriveTime);
    trip.setId(agencyAndId);

    TripTimes tripTimes = new TripTimes(trip, stopTimes, new Deduplicator());
    StopPattern stopPattern = new StopPattern(stopTimes);
    TripPattern tripPattern = new TripPattern(route, stopPattern);

    when(depart.getTripPattern()).thenReturn(tripPattern);

    PatternHop patternHop = new PatternHop(depart, arrive, stopDepart, stopArrive, 0);

    when(graph.getEdges()).thenReturn(Collections.<Edge>singletonList(patternHop));
    when(depart.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(arrive.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(routingRequest.getFrom()).thenReturn(new GenericLocation());
    when(routingRequest.getStartingTransitTripId()).thenReturn(agencyAndId);
    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(10);
    when(graph.getVertex("Station_0")).thenReturn(station0);
    when(graph.getVertex("Station_1")).thenReturn(station1);

    tripPattern.add(tripTimes);
    graph.index = new GraphIndex(graph);

    Vertex vertex = onBoardDepartServiceImpl.setupDepartOnBoard(routingContext);

    assertEquals(coordinates[1].x, vertex.getX(), 0.0);
    assertEquals(coordinates[1].y, vertex.getY(), 0.0);
  }
  /** Make sure that transfers work */
  @Test
  public void testTransfers() throws Exception {
    AddTripPattern atp = getAddTripPattern(RouteSelector.BROAD_HIGH);
    atp.timetables.add(getTimetable(false));

    AddTripPattern atp2 = getAddTripPattern(RouteSelector.BEXLEY_CMH);
    atp2.timetables.add(getTimetable(true));

    // get a graph
    Graph g = buildGraphNoTransit();
    addTransit(g);
    link(g);
    g.index(new DefaultStreetVertexIndexFactory());

    // materialize the trip pattern
    atp.materialize(g);
    atp2.materialize(g);

    TimeWindow window =
        new TimeWindow(
            7 * 3600,
            9 * 3600,
            g.index.servicesRunning(new LocalDate(2015, 6, 10)),
            DayOfWeek.WEDNESDAY);

    Scenario scenario = new Scenario(0);
    scenario.modifications = Lists.newArrayList(atp, atp2);
    ProfileRequest req = new ProfileRequest();
    req.scenario = scenario;
    req.boardingAssumption = RaptorWorkerTimetable.BoardingAssumption.WORST_CASE;

    RaptorWorkerData data = new RaptorWorkerData(g, window, req);

    // make sure that we have transfers a) between the new lines b) from the new lines
    // to the existing lines c) from the existing lines to the new lines
    // stop IDs in the data will be 0 and 1 for existing stops, 2 - 6 for Broad/High and 7 - 11 for
    // Bexley/CMH
    int[] txFromExisting = data.transfersForStop.get(0);
    if (txFromExisting.length == 0) txFromExisting = data.transfersForStop.get(1);

    // make sure there's a transfer to stop 4 (Broad/High)
    // the AddTripPattern instructions are processed in order
    // also recall that each transfer has two ints in the array as it's a jagged array of
    // dest_pattern, distance
    assertTrue(txFromExisting.length > 0);

    boolean foundTx = false;

    for (int i = 0; i < txFromExisting.length; i += 2) {
      if (txFromExisting[i] == 4) {
        foundTx = true;
        break;
      }
    }

    assertTrue("transfer from existing to new", foundTx);

    // Check that there are transfers from the new route to the existing route
    // This is the stop at Broad and High
    int[] txToExisting = data.transfersForStop.get(4);
    assertTrue(txToExisting.length > 0);
    foundTx = false;

    for (int i = 0; i < txToExisting.length; i += 2) {
      if (txToExisting[i] == 0 || txToExisting[i] == 1) {
        // stop from existing route
        foundTx = true;
        break;
      }
    }

    assertTrue("transfer from new to existing", foundTx);

    // Check that there are transfers between the new routes
    int[] txBetweenNew = data.transfersForStop.get(7);
    assertTrue(txBetweenNew.length > 0);
    foundTx = false;

    for (int i = 0; i < txBetweenNew.length; i += 2) {
      if (txBetweenNew[i] == 2) {
        foundTx = true;
        break;
      }
    }

    assertTrue(foundTx);
  }