"""Tests of the Skyfield `api` module and user-facing exceptions."""

import numpy as np
from assay import assert_raises
from skyfield import api, positionlib
from skyfield.api import Topos
from skyfield.errors import EphemerisRangeError

def ts():
    yield api.load.timescale()

def test_sending_jd_that_is_not_a_julian_date():
    earth = api.load('de421.bsp')['earth']
    with assert_raises(ValueError, r"please provide the at\(\) method"
                       " with a Time instance as its argument,"
                       " instead of the value 'blah'"):
        earth.at('blah')

def test_apparent_position_class(ts):
    e = api.load('de421.bsp')
    p = e['earth'].at(ts.utc(2014, 2, 9, 14, 50)).observe(e['mars']).apparent()
    assert isinstance(p, positionlib.Apparent)

def test_astrometric_position_class(ts):
    e = api.load('de421.bsp')
    p = e['earth'].at(ts.utc(2014, 2, 9, 14, 50)).observe(e['mars'])
    assert isinstance(p, positionlib.Astrometric)

def test_astrometric_position_does_not_allow_altaz(ts):
    e = api.load('de421.bsp')
    o = e['earth'] + api.wgs84.latlon(36.7138, -112.2169)
    a = o.at(ts.utc(2014, 2, 9, 14, 50)).observe(e['mars'])
    with assert_raises(ValueError, 'it is not useful to call .altaz'):
        a.altaz()

def test_ephemeris_contains_method(ts):
    e = api.load('de421.bsp')
    assert (399 in e) is True
    assert (398 in e) is False
    assert ('earth' in e) is True
    assert ('Earth' in e) is True
    assert ('EARTH' in e) is True
    assert ('ceres' in e) is False

def test_exception_raised_for_dates_outside_ephemeris(ts):
    eph = api.load('de421.bsp')
    message = (
        'ephemeris segment only covers dates 1899-07-29 through 2053-10-09'
    )
    with assert_raises(EphemerisRangeError, message) as a:
        eph['earth'].at(ts.tt(4096))

    e = a.exception
    assert e.args == (message,)
    assert e.start_time.tdb == 2414864.5
    assert e.end_time.tdb == 2471184.5
    assert e.time_mask == [True]
    assert e.segment is eph['earth'].vector_functions[0].spk_segment

def test_planet_position_class(ts):
    e = api.load('de421.bsp')
    p = e['mars'].at(ts.utc(2014, 2, 9, 14, 50))
    assert isinstance(p, positionlib.Barycentric)

def test_star_position_class(ts):
    e = api.load('de421.bsp')
    star = api.Star(ra_hours=0, dec_degrees=0)
    p = e['earth'].at(ts.utc(2014, 2, 9, 15, 1)).observe(star)
    assert isinstance(p, positionlib.Astrometric)

def test_star_vector_from_earth(ts):
    t = ts.tt_jd(api.T0)
    eph = api.load('de421.bsp')
    e = eph['earth'].at(t)

    star = api.Star(ra_hours=[1.0, 2.0], dec_degrees=[+3.0, +4.0])
    p = e.observe(star)
    assert p.position.au.shape == (3, 2)
    assert p.velocity.au_per_d.shape == (3, 2)
    assert p.t.shape == (2,)
    assert (p.t.tt == api.T0).all()
    a = p.apparent()

    a1 = e.observe(api.Star(ra_hours=1.0, dec_degrees=+3.0)).apparent()
    a2 = e.observe(api.Star(ra_hours=2.0, dec_degrees=+4.0)).apparent()
    assert (a1.position.au == a.position.au[:,0]).all()
    assert (a2.position.au == a.position.au[:,1]).all()

def test_star_vector_from_topos(ts):
    t = ts.tt_jd(api.T0)
    eph = api.load('de421.bsp')
    boston = eph['earth'] + Topos('42.3583 N', '71.0636 W')
    b = boston.at(t)

    star = api.Star(ra_hours=[1.0, 2.0], dec_degrees=[+3.0, +4.0])
    p = b.observe(star)
    assert p.position.au.shape == (3, 2)
    assert p.velocity.au_per_d.shape == (3, 2)
    assert p.t.shape == (2,)
    assert (p.t.tt == api.T0).all()
    a = p.apparent()

    a1 = b.observe(api.Star(ra_hours=1.0, dec_degrees=+3.0)).apparent()
    a2 = b.observe(api.Star(ra_hours=2.0, dec_degrees=+4.0)).apparent()
    assert (a1.position.au == a.position.au[:,0]).all()
    assert (a2.position.au == a.position.au[:,1]).all()

def test_hadec_needs_a_longitude(ts):
    e = api.load('de421.bsp')
    earth = e['earth']
    moon = e['moon']
    apparent = earth.at(ts.utc(2016)).observe(moon)
    with assert_raises(ValueError, 'from a specific latitude and longitude'):
        apparent.hadec()

def test_altaz_needs_topos(ts):
    e = api.load('de421.bsp')
    earth = e['earth']
    moon = e['moon']
    apparent = earth.at(ts.utc(2016)).observe(moon).apparent()
    with assert_raises(ValueError, 'from a specific latitude and longitude'):
        apparent.altaz()

def test_from_altaz_needs_topos():
    p = positionlib.ICRF([0.0, 0.0, 0.0])
    with assert_raises(ValueError, 'to compute altitude and azimuth'):
        p.from_altaz(alt_degrees=0, az_degrees=0)

def test_from_altaz_parameters(ts):
    usno = Topos('38.9215 N', '77.0669 W', elevation_m=92.0)
    t = ts.tt(jd=api.T0)
    p = usno.at(t)
    a = api.Angle(degrees=10.0)
    d = api.Distance(au=0.234)
    with assert_raises(ValueError, 'the alt= parameter with an Angle'):
        p.from_altaz(alt='Bad value', alt_degrees=0, az_degrees=0)
    with assert_raises(ValueError, 'the az= parameter with an Angle'):
        p.from_altaz(az='Bad value', alt_degrees=0, az_degrees=0)
    p.from_altaz(alt=a, alt_degrees='bad', az_degrees=0)
    p.from_altaz(az=a, alt_degrees=0, az_degrees='bad')
    assert str(p.from_altaz(alt=a, az=a).distance()) == '0.1 au'
    assert str(p.from_altaz(alt=a, az=a, distance=d).distance()) == '0.234 au'

def test_github_81(ts):
    t = ts.utc(1980, 1, 1)
    assert t == t
    assert (t == 61) is False  # used to die with AttributeError for "tt"

def test_github_500_does_zero_position_trigger_numpy_warnings(ts):
    zero_vector = np.array([0.0, 0.0, 0.0])
    t = ts.utc(2020, 12, 14)
    p = positionlib.Astrometric(zero_vector, zero_vector, t=t, center=0)
    p._ephemeris = api.load('de421.bsp')
    with np.errstate(all='raise'):
        p.apparent().radec()
