class SkippedPassesRegressionTests(TestCase):
    """Check that we do not skip passes"""

    # See https://github.com/satellogic/orbit-predictor/issues/99

    def setUp(self):
        self.db = MemoryTLESource()
        self.db.add_tle(TRICKY_SAT_ID, TRICKY_SAT_TLE_LINES, dt.datetime.now())
        self.predictor = TLEPredictor(TRICKY_SAT_ID, self.db)

    @pytest.mark.xfail(reason="Legacy LocationPredictor skips some passes")
    def test_pass_is_not_skipped_old(self):
        loc = Location(
            name="loc",
            latitude_deg=-15.137152171507697,
            longitude_deg=-0.4276612055384211,
            elevation_m=1.665102900005877e-05,
        )

        PASS_DATE = dt.datetime(2020, 9, 25, 9, 2, 6)
        LIMIT_DATE = dt.datetime(2020, 9, 25, 10, 36, 0)

        predicted_passes = list(
            self.predictor.passes_over(
                loc,
                when_utc=PASS_DATE,
                limit_date=LIMIT_DATE,
                aos_at_dg=0,
                max_elevation_gt=0,
                location_predictor_class=LocationPredictor,
            ))

        assert predicted_passes

    @pytest.mark.skipif(sys.version_info < (3, 5),
                        reason="Not installing SciPy in Python 3.4")
    def test_pass_is_not_skipped_smart(self):
        loc = Location(
            name="loc",
            latitude_deg=-15.137152171507697,
            longitude_deg=-0.4276612055384211,
            elevation_m=1.665102900005877e-05,
        )

        PASS_DATE = dt.datetime(2020, 9, 25, 9, 2, 6)
        LIMIT_DATE = dt.datetime(2020, 9, 25, 10, 36, 0)

        predicted_passes = list(
            self.predictor.passes_over(
                loc,
                when_utc=PASS_DATE,
                limit_date=LIMIT_DATE,
                aos_at_dg=0,
                max_elevation_gt=0,
                location_predictor_class=SmartLocationPredictor,
            ))

        assert predicted_passes
class LOSComputationRegressionTests(TestCase):
    """Check that the LOS is computed correctly"""

    # See https://github.com/satellogic/orbit-predictor/issues/104

    def setUp(self):
        tle_lines = (
            "1 42760U 17034C   19070.46618549  .00000282  00000-0  30543-4 0  9995",
            "2 42760  43.0166  56.1509 0009676 356.3576 146.0151 15.09909885 95848",
        )
        self.db = MemoryTLESource()
        self.db.add_tle("42760U", tle_lines, dt.datetime.now())
        self.predictor = TLEPredictor("42760U", self.db)

    @pytest.mark.skipif(sys.version_info < (3, 5),
                        reason="Not installing SciPy in Python 3.4")
    def test_los_is_correctly_computed(self):
        loc = Location(
            name='loc',
            latitude_deg=-34.61315,
            longitude_deg=-58.37723,
            elevation_m=30,
        )

        PASS_DATE = dt.datetime(2019, 1, 1, 0, 0)
        LIMIT_DATE = dt.datetime(2019, 1, 15, 0, 0)

        predicted_passes = list(
            self.predictor.passes_over(
                loc,
                when_utc=PASS_DATE,
                limit_date=LIMIT_DATE,
                aos_at_dg=0,
                max_elevation_gt=0,
                location_predictor_class=SmartLocationPredictor,
            ))

        assert predicted_passes
class AccuratePredictorTests(TestCase):
    def setUp(self):
        # Source
        self.db = MemoryTLESource()
        self.start = dt.datetime(2017, 3, 6, 7, 51)
        self.db.add_tle(SATE_ID, LINES, self.start)
        # Predictor
        self.predictor = TLEPredictor(SATE_ID, self.db)
        self.end = self.start + dt.timedelta(days=5)

    def test_predicted_passes_are_equal_between_executions(self):
        location = Location('bad-case-1', 11.937501570612568,
                            -55.35189435098657, 1780.674044538666)
        first_set = list(
            self.predictor.passes_over(location, self.start, self.end))
        second_set = list(
            self.predictor.passes_over(location,
                                       self.start + dt.timedelta(seconds=3),
                                       self.end))

        # We use delta=ONE_SECOND because
        # that's the hardcoded value for the precision
        self.assertAlmostEqual(first_set[0].aos,
                               second_set[0].aos,
                               delta=ONE_SECOND)
        self.assertAlmostEqual(first_set[0].los,
                               second_set[0].los,
                               delta=ONE_SECOND)

    def test_predicted_passes_have_elevation_positive_and_visible_on_date(
            self):
        end = self.start + dt.timedelta(days=60)
        for pass_ in self.predictor.passes_over(ARG, self.start, end):
            self.assertGreater(pass_.max_elevation_deg, 0)
            position = self.predictor.get_position(pass_.max_elevation_date)
            ARG.is_visible(position)
            self.assertGreaterEqual(pass_.off_nadir_deg, -90)
            self.assertLessEqual(pass_.off_nadir_deg, 90)

    def test_predicted_passes_off_nadir_angle_works(self):
        start = dt.datetime(2017, 3, 6, 13, 30)
        end = start + dt.timedelta(hours=1)
        location = Location('bad-case-1', 11.937501570612568,
                            -55.35189435098657, 1780.674044538666)

        pass_ = self.predictor.get_next_pass(location,
                                             when_utc=start,
                                             limit_date=end)
        self.assertGreaterEqual(0, pass_.off_nadir_deg)

    @given(start=datetimes(
        min_value=dt.datetime(2017, 1, 1),
        max_value=dt.datetime(2020, 12, 31),
    ),
           location=tuples(floats(min_value=-90, max_value=90),
                           floats(min_value=0, max_value=180),
                           floats(min_value=-200, max_value=9000)))
    @settings(max_examples=10000, deadline=None)
    @example(start=dt.datetime(2017, 1, 26, 11, 51, 51),
             location=(-37.69358328273305, 153.96875, 0.0))
    def test_pass_is_always_returned(self, start, location):
        location = Location('bad-case-1', *location)
        pass_ = self.predictor.get_next_pass(location, start)
        self.assertGreater(pass_.max_elevation_deg, 0)

    def test_aos_deg_can_be_used_in_get_next_pass(self):
        start = dt.datetime(2017, 3, 6, 13, 30)
        end = start + dt.timedelta(hours=1)
        location = Location('bad-case-1', 11.937501570612568,
                            -55.35189435098657, 1780.674044538666)
        complete_pass = self.predictor.get_next_pass(location,
                                                     when_utc=start,
                                                     limit_date=end)

        pass_with_aos = self.predictor.get_next_pass(location,
                                                     when_utc=start,
                                                     limit_date=end,
                                                     aos_at_dg=5)

        self.assertGreater(pass_with_aos.aos, complete_pass.aos)
        self.assertLess(pass_with_aos.aos, complete_pass.max_elevation_date)
        self.assertAlmostEqual(pass_with_aos.max_elevation_date,
                               complete_pass.max_elevation_date,
                               delta=dt.timedelta(seconds=1))

        self.assertGreater(pass_with_aos.los, complete_pass.max_elevation_date)
        self.assertLess(pass_with_aos.los, complete_pass.los)

        position = self.predictor.get_position(pass_with_aos.aos)
        _, elev = location.get_azimuth_elev_deg(position)

        self.assertAlmostEqual(elev, 5, delta=0.1)

        position = self.predictor.get_position(pass_with_aos.los)
        _, elev = location.get_azimuth_elev_deg(position)

        self.assertAlmostEqual(elev, 5, delta=0.1)

    def test_predicted_passes_whit_aos(self):
        end = self.start + dt.timedelta(days=60)
        for pass_ in self.predictor.passes_over(ARG,
                                                self.start,
                                                end,
                                                aos_at_dg=5):
            self.assertGreater(pass_.max_elevation_deg, 5)
            position = self.predictor.get_position(pass_.aos)
            _, elev = ARG.get_azimuth_elev_deg(position)
            self.assertAlmostEqual(elev, 5, delta=0.1)