@Test
  public void testExceedHistoryBackward() throws OrekitException, IOException {
    final double period = 900.0;

    // the raw detector should trigger one event at each 900s period
    final DateDetector raw =
        new DateDetector(orbit.getDate().shiftedBy(+0.5 * period))
            .withMaxCheck(period / 3)
            .withHandler(new ContinueOnEvent<DateDetector>());
    for (int i = 0; i < 300; ++i) {
      raw.addEventDate(orbit.getDate().shiftedBy(-(i + 0.5) * period));
    }

    // in fact, we will filter out half of these events, so we get only one event every 2 periods
    final EventEnablingPredicateFilter<DateDetector> filtered =
        new EventEnablingPredicateFilter<DateDetector>(
            raw,
            new EnablingPredicate<DateDetector>() {
              public boolean eventIsEnabled(
                  SpacecraftState state, DateDetector eventDetector, double g) {
                double nbPeriod = orbit.getDate().durationFrom(state.getDate()) / period;
                return ((int) FastMath.floor(nbPeriod)) % 2 == 1;
              }
            });
    Propagator propagator = new KeplerianPropagator(orbit);
    EventsLogger logger = new EventsLogger();
    propagator.addEventDetector(logger.monitorDetector(filtered));
    propagator.propagate(orbit.getDate().shiftedBy(-301 * period));
    List<LoggedEvent> events = logger.getLoggedEvents();

    // 300 periods, 150 events as half of them are filtered out
    Assert.assertEquals(150, events.size());

    // as we have encountered a lot of enabling status changes, we exceeded the internal history
    // if we try to display again the filtered g function for dates far in the future,
    // we will not see the zero crossings anymore, they have been lost
    propagator.clearEventsDetectors();
    for (double dt = -5000.0; dt > -10000.0; dt -= 3.0) {
      double filteredG = filtered.g(propagator.propagate(orbit.getDate().shiftedBy(dt)));
      Assert.assertTrue(filteredG < 0.0);
    }

    // on the other hand, if we try to display again the filtered g function for future dates
    // that are still inside the history, we still see the zero crossings
    for (double dt = -195400.0; dt > -196200.0; dt -= 3.0) {
      double filteredG = filtered.g(propagator.propagate(orbit.getDate().shiftedBy(dt)));
      if (dt < -195750) {
        Assert.assertTrue(filteredG < 0.0);
      } else {
        Assert.assertTrue(filteredG > 0.0);
      }
    }
  }
  @Test
  public void testResetState() throws OrekitException {
    final List<AbsoluteDate> reset = new ArrayList<AbsoluteDate>();
    DateDetector raw =
        new DateDetector(orbit.getDate().shiftedBy(3600.0))
            .withMaxCheck(1000.0)
            .withHandler(
                new EventHandler<DateDetector>() {
                  public SpacecraftState resetState(
                      DateDetector detector, SpacecraftState oldState) {
                    reset.add(oldState.getDate());
                    return oldState;
                  }

                  public Action eventOccurred(
                      SpacecraftState s, DateDetector detector, boolean increasing) {
                    return Action.RESET_STATE;
                  }
                });
    for (int i = 2; i < 10; ++i) {
      raw.addEventDate(orbit.getDate().shiftedBy(i * 3600.0));
    }
    EventEnablingPredicateFilter<DateDetector> filtered =
        new EventEnablingPredicateFilter<DateDetector>(
            raw,
            new EnablingPredicate<DateDetector>() {
              public boolean eventIsEnabled(
                  SpacecraftState state, DateDetector eventDetector, double g) {
                return state.getDate().durationFrom(orbit.getDate()) > 20000.0;
              }
            });
    Propagator propagator = new KeplerianPropagator(orbit);
    EventsLogger logger = new EventsLogger();
    propagator.addEventDetector(logger.monitorDetector(filtered));
    propagator.propagate(orbit.getDate().shiftedBy(Constants.JULIAN_DAY));
    List<LoggedEvent> events = logger.getLoggedEvents();
    Assert.assertEquals(4, events.size());
    Assert.assertEquals(
        6 * 3600, events.get(0).getState().getDate().durationFrom(orbit.getDate()), 1.0e-6);
    Assert.assertEquals(
        7 * 3600, events.get(1).getState().getDate().durationFrom(orbit.getDate()), 1.0e-6);
    Assert.assertEquals(
        8 * 3600, events.get(2).getState().getDate().durationFrom(orbit.getDate()), 1.0e-6);
    Assert.assertEquals(
        9 * 3600, events.get(3).getState().getDate().durationFrom(orbit.getDate()), 1.0e-6);
    Assert.assertEquals(4, reset.size());
    Assert.assertEquals(6 * 3600, reset.get(0).durationFrom(orbit.getDate()), 1.0e-6);
    Assert.assertEquals(7 * 3600, reset.get(1).durationFrom(orbit.getDate()), 1.0e-6);
    Assert.assertEquals(8 * 3600, reset.get(2).durationFrom(orbit.getDate()), 1.0e-6);
    Assert.assertEquals(9 * 3600, reset.get(3).durationFrom(orbit.getDate()), 1.0e-6);
  }
  @Test
  public void testFrance() throws OrekitException {

    final BodyShape earth =
        new OneAxisEllipsoid(
            Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
            Constants.WGS84_EARTH_FLATTENING,
            FramesFactory.getITRF(IERSConventions.IERS_2010, true));

    GeographicZoneDetector d =
        new GeographicZoneDetector(20.0, 1.e-3, earth, buildFrance(), FastMath.toRadians(0.5))
            .withHandler(new ContinueOnEvent<GeographicZoneDetector>());

    Assert.assertEquals(20.0, d.getMaxCheckInterval(), 1.0e-15);
    Assert.assertEquals(1.0e-3, d.getThreshold(), 1.0e-15);
    Assert.assertEquals(0.5, FastMath.toDegrees(d.getMargin()), 1.0e-15);
    Assert.assertEquals(AbstractDetector.DEFAULT_MAX_ITER, d.getMaxIterationCount());

    final TimeScale utc = TimeScalesFactory.getUTC();
    final Vector3D position = new Vector3D(-6142438.668, 3492467.56, -25767.257);
    final Vector3D velocity = new Vector3D(505.848, 942.781, 7435.922);
    final AbsoluteDate date = new AbsoluteDate(2003, 9, 16, utc);
    final Orbit orbit =
        new EquinoctialOrbit(
            new PVCoordinates(position, velocity),
            FramesFactory.getEME2000(),
            date,
            Constants.EIGEN5C_EARTH_MU);

    Propagator propagator =
        new EcksteinHechlerPropagator(
            orbit,
            Constants.EIGEN5C_EARTH_EQUATORIAL_RADIUS,
            Constants.EIGEN5C_EARTH_MU,
            Constants.EIGEN5C_EARTH_C20,
            Constants.EIGEN5C_EARTH_C30,
            Constants.EIGEN5C_EARTH_C40,
            Constants.EIGEN5C_EARTH_C50,
            Constants.EIGEN5C_EARTH_C60);

    EventsLogger logger = new EventsLogger();
    propagator.addEventDetector(logger.monitorDetector(d));

    propagator.propagate(date.shiftedBy(10 * Constants.JULIAN_DAY));
    Assert.assertEquals(26, logger.getLoggedEvents().size());
  }
  private void doElevationTest(
      final double minElevation,
      final AbsoluteDate start,
      final AbsoluteDate end,
      final int expectedEvents,
      final boolean sameSign)
      throws OrekitException {

    final ElevationExtremumDetector raw =
        new ElevationExtremumDetector(0.001, 1.e-6, new TopocentricFrame(earth, gp, "test"))
            .withHandler(new ContinueOnEvent<ElevationExtremumDetector>());
    final EventEnablingPredicateFilter<ElevationExtremumDetector> aboveGroundElevationDetector =
        new EventEnablingPredicateFilter<ElevationExtremumDetector>(
                raw,
                new EnablingPredicate<ElevationExtremumDetector>() {
                  public boolean eventIsEnabled(
                      final SpacecraftState state,
                      final ElevationExtremumDetector eventDetector,
                      final double g)
                      throws OrekitException {
                    return eventDetector.getElevation(state) > minElevation;
                  }
                })
            .withMaxCheck(60.0);

    Assert.assertEquals(0.001, raw.getMaxCheckInterval(), 1.0e-15);
    Assert.assertEquals(60.0, aboveGroundElevationDetector.getMaxCheckInterval(), 1.0e-15);
    Assert.assertEquals(1.0e-6, aboveGroundElevationDetector.getThreshold(), 1.0e-15);
    Assert.assertEquals(
        AbstractDetector.DEFAULT_MAX_ITER, aboveGroundElevationDetector.getMaxIterationCount());

    Propagator propagator =
        new EcksteinHechlerPropagator(
            orbit,
            Constants.EIGEN5C_EARTH_EQUATORIAL_RADIUS,
            Constants.EIGEN5C_EARTH_MU,
            Constants.EIGEN5C_EARTH_C20,
            Constants.EIGEN5C_EARTH_C30,
            Constants.EIGEN5C_EARTH_C40,
            Constants.EIGEN5C_EARTH_C50,
            Constants.EIGEN5C_EARTH_C60);

    EventsLogger logger = new EventsLogger();
    propagator.addEventDetector(logger.monitorDetector(aboveGroundElevationDetector));

    propagator.propagate(start, end);
    for (LoggedEvent e : logger.getLoggedEvents()) {
      final double eMinus = raw.getElevation(e.getState().shiftedBy(-10.0));
      final double e0 = raw.getElevation(e.getState());
      final double ePlus = raw.getElevation(e.getState().shiftedBy(+10.0));
      Assert.assertTrue(e0 > eMinus);
      Assert.assertTrue(e0 > ePlus);
      Assert.assertTrue(e0 > minElevation);
    }
    Assert.assertEquals(expectedEvents, logger.getLoggedEvents().size());

    propagator.clearEventsDetectors();
    double g1Raw = raw.g(propagator.propagate(orbit.getDate().shiftedBy(18540.0)));
    double g2Raw = raw.g(propagator.propagate(orbit.getDate().shiftedBy(18624.0)));
    double g1 =
        aboveGroundElevationDetector.g(propagator.propagate(orbit.getDate().shiftedBy(18540.0)));
    double g2 =
        aboveGroundElevationDetector.g(propagator.propagate(orbit.getDate().shiftedBy(18624.0)));
    Assert.assertTrue(g1Raw > 0);
    Assert.assertTrue(g2Raw < 0);
    if (sameSign) {
      Assert.assertTrue(g1 > 0);
      Assert.assertTrue(g2 < 0);
    } else {
      Assert.assertTrue(g1 < 0);
      Assert.assertTrue(g2 > 0);
    }
  }