from amuse.support.testing import amusetest
import numpy

from amuse.support.exceptions import AmuseException


from amuse.units import core
from amuse.units.units import *
from amuse.units.constants import *


class TestUnitConversions(amusetest.TestCase):
    def test1(self):
        km = 1000 * m
        self.assertEqual(1000, km.value_in(m))
        self.assertEqual(0.001, m.value_in(km))

    def test2(self):
        km = 1000 * m
        val = km(10)
        self.assertEqual(10000, val.value_in(m))

    def test3(self):
        km = 1000.0 * m
        val = km(10)
        self.assertEqual("10 1000.0 * m", str(val))

        km = named('kilometer', 'km', 1000 * m)
        val = km(10)
        self.assertEqual("10 km", str(val))

    def test4(self):
        km = named('kilometer', 'km', 1000 * m)
        h = named('hour', 'h', 60 * 60 * s)
        kmh = km/h
        ms = m/s
        val = 10 | m/s
        self.assertEqual(36, val.value_in(kmh))

    def test5(self):
        km = named('kilometer', 'km', 1000 * m)
        h = named('hour', 'h', 60 * 60 * s)
        kmh = km/h
        ms = m/s
        val = 10 | ms
        self.assertEqual(36, val.value_in(kmh))

    def test6(self):
        no1 = m / m
        no2 = no_unit
        self.assertEqual("m / m", str(no1))
        self.assertTrue(no1.has_same_base_as(no2))

    def test7(self):
        x = (100 * kg ** 2) / kg
        self.assertEqual("100 * kg**2 / kg", str(x))
        self.assertEqual("100.0 * kg", str(x.to_simple_form()))

    def test8(self):
        x = (10 | g) * (2 | m)
        self.assertEqual("20 0.001 * m * kg", str(x))
        self.assertEqual("0.02 kg * m", str(x.as_quantity_in(kg * m)))
        x = (10 | kg) * (2000 | g)
        self.assertEqual("20000 0.001 * kg**2", str(x))

    def test9(self):
        speed_of_light = 1 | (lightyear * yr**-1)
        time = 1e-9 | s
        length = speed_of_light * time
        length_in_m = length.value_in(m)
        self.assertAlmostEqual(0.2997988, length_in_m, 6)

    def test10(self):
        eps0_1 = mu0**-1*c**-2
        eps0_2 = (1 | none)/(mu0*(c**2))
        self.assertTrue((eps0_1 - eps0_2) / eps0_1 < (1e-10 | none))
        b = ((1. | e)**2)
        f = (hbar*c*4.*numpy.pi * eps0)**-1
        fine_structure_constant_calculated = (b * f)
        fine_structure_constant = 7.297352537650e-3
        self.assertAlmostEqual(fine_structure_constant_calculated, fine_structure_constant, 5)

    def test11(self):
        vel1 = 1 | m / s
        self.assertRaises(core.IncompatibleUnitsException, vel1.as_quantity_in, s / m,
            expected_message="Cannot express m / s in s / m, the units do not have the same bases")

    def test12(self):
        self.assertEqual((1234 | g).as_string_in(g), '1234 g')
        self.assertEqual((1234 | g).as_string_in(kg), '1.234 kg')
        self.assertEqual((1.234 | kg).as_string_in(g), '1234.0 g')
        self.assertEqual((1.0 | km * s**-1).as_string_in(m / s), '1000.0 m / s')
        self.assertEqual((1.0 | km * s**-1).as_string_in(s**-1 * m), '1000.0 s**-1 * m')
        self.assertEqual((1.0 | km / s).as_string_in((10*J/g)**0.5), '10.0 (10 * J / g)**0.5')

    def test13(self):
        self.assertAlmostRelativeEquals((1234 | km ** (5/3.)).value_in((km ** 5.0) ** (1.0/3.0)), 1234.0, 12)
        self.assertAlmostRelativeEquals((1234 | km ** (5/3.)).value_in((m ** 5.0) ** (1.0/3.0)), 123400000.0)

    def test15(self):
        self.assertAlmostRelativeEquals((m / s).to_array_of_floats(), [1, 1,  1, 0, -1, 0, 0, 0, 0])
        self.assertAlmostRelativeEquals((s / m).to_array_of_floats(), [1, 1, -1, 0, 1, 0, 0, 0, 0])
        self.assertAlmostRelativeEquals((kg ** 2 / s).to_array_of_floats(), [1, 1,  0, 2, -1, 0, 0, 0, 0])


class TestNonNumericUnits(amusetest.TestCase):
    def test1(self):
        string1 = "string" | string
        self.assertRaises(AmuseException, string1.as_quantity_in, m,
            expected_message="Cannot convert non-numeric quantities in to another unit")

    def test2(self):
        x = "test" | string
        self.assertEqual("test", x.value_in(string))

    def test3(self):
        test_unit = core.enumeration_unit(
            "test",
            "test",
            [1, 2, 3],
            ["one", "two", "three"]
        )
        x = 1 | test_unit
        self.assertEqual(1, x.value_in(test_unit))
        self.assertEqual("one", str(x))
        self.assertRaises(Exception, lambda: 4 | test_unit,
            expected_message="<4> is not a valid value for unit<test>")

    def test4(self):
        self.assertRaises(Exception, lambda: 1 | string,
            expected_message="<1> is not a valid value for unit<string>")

    def test5(self):
        test_unit = core.enumeration_unit(
            "test",
            "test",
            [1, 2, 3],
            ["one", "two", "three"]
        )
        self.assertEqual(3, len(list(test_unit.quantities())))
        for x, y in zip(test_unit.quantities(), ["one", "two", "three"]):
            self.assertEqual(str(x), y)

    def test6(self):
        test_unit = core.enumeration_unit(
            "test",
            "test",
            [1, 4, 7]
        )
        self.assertEqual(3, len(list(test_unit.quantities())))
        for x, y in zip(test_unit.quantities(), ["1", "4", "7"]):
            self.assertEqual(str(x), y)

    def test7(self):
        test_unit = core.enumeration_unit(
            "test",
            "test",
            range(5)
        )
        self.assertEqual(5, len(list(test_unit.quantities())))
        self.assertEqual(1 | test_unit, 1 | test_unit)
        self.assertTrue(1 | test_unit == 1 | test_unit)
        self.assertFalse(1 | test_unit == 2 | test_unit)
        self.assertTrue(1 | test_unit != 2 | test_unit)
        self.assertFalse(1 | test_unit != 1 | test_unit)
        self.assertTrue(1 | test_unit >= 1 | test_unit)
        self.assertFalse(1 | test_unit >= 2 | test_unit)
        self.assertTrue(1 | test_unit <= 1 | test_unit)
        self.assertFalse(1 | test_unit <= 0 | test_unit)
        self.assertTrue(1 | test_unit > 0 | test_unit)
        self.assertFalse(1 | test_unit > 1 | test_unit)
        self.assertTrue(1 | test_unit < 3 | test_unit)
        self.assertFalse(1 | test_unit < 0 | test_unit)
