"""Tests of whether units behave."""

from assay import assert_raises
from numpy import array, nan
from skyfield.units import (
    Angle, Distance, Velocity, UnpackingError, WrongUnitError,
)

try:
    from astropy import units as u
except ImportError:
    u = None

def needs_astropy(test):
    """Skip `test` if AstroPy is not available."""
    return None if (u is None) else test

def test_angle_units():
    a = Angle(degrees=123.0)
    assert a.degrees == 123.0
    assert a._degrees == 123.0

    a = Angle(hours=12.0)
    assert a.hours == 12.0
    assert a._hours == 12.0

def test_degree_rounding():
    tenth = 0.1 / 60.0 / 60.0  # of an arcsecond

    assert str(Angle(degrees=tenth * -600.75)) == '-00deg 01\' 00.1"'
    assert str(Angle(degrees=tenth * -600.25)) == '-00deg 01\' 00.0"'
    assert str(Angle(degrees=tenth * -599.75)) == '-00deg 01\' 00.0"'
    assert str(Angle(degrees=tenth * -599.25)) == '-00deg 00\' 59.9"'

    assert str(Angle(degrees=tenth * -1.75)) == '-00deg 00\' 00.2"'
    assert str(Angle(degrees=tenth * -1.25)) == '-00deg 00\' 00.1"'
    assert str(Angle(degrees=tenth * -0.75)) == '-00deg 00\' 00.1"'
    assert str(Angle(degrees=tenth * -0.25)) == '00deg 00\' 00.0"'

    assert str(Angle(degrees=0.0)) == '00deg 00\' 00.0"'

    assert str(Angle(degrees=tenth * 0.25)) == '00deg 00\' 00.0"'
    assert str(Angle(degrees=tenth * 0.75)) == '00deg 00\' 00.1"'
    assert str(Angle(degrees=tenth * 1.25)) == '00deg 00\' 00.1"'
    assert str(Angle(degrees=tenth * 1.75)) == '00deg 00\' 00.2"'

    assert str(Angle(degrees=tenth * 599.25)) == '00deg 00\' 59.9"'
    assert str(Angle(degrees=tenth * 599.75)) == '00deg 01\' 00.0"'
    assert str(Angle(degrees=tenth * 600.25)) == '00deg 01\' 00.0"'
    assert str(Angle(degrees=tenth * 600.75)) == '00deg 01\' 00.1"'

def test_angle_scalar_strs():
    assert str(Angle(degrees=array(91))) == '''91deg 00' 00.0"'''
    assert str(Angle(degrees=array(91), signed=True)) == '''+91deg 00' 00.0"'''
    assert str(Angle(hours=array(12))) == '''12h 00m 00.00s'''

def test_angle_array_strs():
    h = Angle(hours=array([0.5, nan, -13]))
    d = Angle(degrees=h.degrees)

    assert str(h) == '3 values from 00h 30m 00.00s to -13h 00m 00.00s'
    assert str(d) == '''3 values from 07deg 30' 00.0" to -195deg 00' 00.0"'''

    with assert_raises(WrongUnitError):
        h.dstr()
        d.hstr()

    assert h.hstr() == d.hstr(warn=False) == [
        '00h 30m 00.00s',
        'nan',
        '-13h 00m 00.00s',
    ]
    assert d.dstr() == h.dstr(warn=False) == [
        '07deg 30\' 00.0"',
        'nan',
        '-195deg 00\' 00.0"',
    ]

    empty = Angle(radians=[])
    assert str(empty) == 'Angle []'
    assert empty.hstr(warn=False) == []
    assert empty.dstr() == []

    assert h.hstr(format='{0} {1} {2} {3} {4} {5}', places=6) == [
        ' 0 30 0 0 6', 'nan', '- 13 0 0 0 6']
    assert d.dstr(format='{0} {1} {2} {3} {4} {5}', places=6) == [
        ' 7 30 0 0 6', 'nan', '- 195 0 0 0 6']

def test_angle_sexagesimal_args():
    assert str(Angle(degrees=(90,))) == '''90deg 00' 00.0"'''
    assert str(Angle(hours=(12,))) == '''12h 00m 00.00s'''

    assert str(Angle(degrees=(90, 15))) == '''90deg 15' 00.0"'''
    assert str(Angle(hours=(12, 30))) == '''12h 30m 00.00s'''

    assert str(Angle(degrees=(90, 15, 30))) == '''90deg 15' 30.0"'''
    assert str(Angle(hours=(12, 30, 15))) == '''12h 30m 15.00s'''

def test_arcminutes_and_arcseconds_and_mas():
    angle = Angle(degrees=1.0)
    assert angle.arcminutes() == 60
    assert angle.arcseconds() == 60 * 60
    assert angle.mas() == 60 * 60 * 1000

def test_distance_input_units():
    for d in (
        Distance(1.0),
        Distance(au=1.0),       # deprecated
        Distance(m=149597870700),
        Distance(km=149597870.700),
        Distance.au(1.0),       # modern
        Distance.m(149597870700),
        Distance.km(149597870.700),
    ):
        assert abs(d.au - 1.0) <= 0

def test_velocity_input_units():
    v1 = Velocity(au_per_d=2.0)
    v2 = Velocity(km_per_s=3462.9137)
    assert abs(v1.au_per_d - v2.au_per_d) < 1e-7

    v1 = Velocity.au_per_d(2.0)
    v2 = Velocity.km_per_s(3462.9137)
    assert abs(v1.au_per_d - v2.au_per_d) < 1e-7

def test_stringifying_vector_distance():
    a = array([1.23, 4.56])
    s = str(Distance(au=a))
    if '[1' in s:
        # Python 3.5, says Travis CI.  No idea.
        assert s == '[1.23 4.56] au'
    else:
        # Every other version of Python.
        assert s == '[ 1.23  4.56] au'

def test_helpful_exceptions():
    distance = Distance(1.234)
    expect = '''\
to use this Distance, ask for its value in a particular unit:

    distance.au
    distance.km
    distance.m'''

    with assert_raises(UnpackingError) as a:
        x, y, z = distance
    assert str(a.exception) == expect

    with assert_raises(UnpackingError) as a:
        distance[0]
    assert str(a.exception) == expect

    velocity = Velocity(1.234)
    expect = '''\
to use this Velocity, ask for its value in a particular unit:

    velocity.au_per_d
    velocity.km_per_s
    velocity.m_per_s'''

    with assert_raises(UnpackingError) as a:
        x, y, z = velocity
    assert str(a.exception) == expect

    with assert_raises(UnpackingError) as a:
        velocity[0]
    assert str(a.exception) == expect

    angle = Angle(radians=1.234)
    expect = '''\
to use this Angle, ask for its value in a particular unit:

    angle.degrees
    angle.hours
    angle.radians'''

    with assert_raises(UnpackingError) as a:
        x, y, z = angle
    assert str(a.exception) == expect

    with assert_raises(UnpackingError) as a:
        angle[0]
    assert str(a.exception) == expect

def test_constructors_accept_plain_lists():
    Distance(au=[1,2,3])
    Distance(km=[1,2,3])
    Distance(m=[1,2,3])
    Velocity(au_per_d=[1,2,3])
    Velocity(km_per_s=[1,2,3])

def test_converting_from_km_to_m():
    distance = Distance(km=1.234)
    assert abs(distance.m - 1234.0) < 1e-15

def test_converting_from_m_to_km():
    distance = Distance(m=1234.0)
    assert abs(distance.km - 1.234) < 1e-15

def test_deprecated_method_from_au():
    distance = Distance.from_au(1.25)
    assert distance.au == 1.25

@needs_astropy
def test_converting_distance_with_astropy():
    distance = Distance(au=1.234)
    value1 = distance.km
    value2 = distance.to(u.km)
    epsilon = 0.02         # definitions of AU seem to disagree slightly
    assert abs(value1 - value2.value) < epsilon

@needs_astropy
def test_converting_velocity_with_astropy():
    velocity = Velocity(au_per_d=1.234)
    value1 = velocity.km_per_s
    value2 = velocity.to(u.km / u.s)
    epsilon = 1e-6
    assert abs(value1 - value2.value) < epsilon
