import pickle
import random

import pytest
from openff.utilities.testing import skip_if_missing

from openff.units import Quantity, unit


class TestQuantity:
    @skip_if_missing("openmm.unit")
    def test_to_openmm_method(self):
        """
        Test the basic behavior of `Quantity.to_openmm` as an API call. Testing of the underlying
        behavior of the standalone `to_openmm` function is in opeff/units/tests/test_openmm.py.
        """
        from openmm import unit as openmm_unit

        quantity = unit.Quantity(0.5, "nanometer")
        converted = quantity.to_openmm()

        assert converted == openmm_unit.Quantity(0.5, openmm_unit.nanometer)


class TestPickle:
    """Test pickle-based serialization of Quantity, Unit, and Measurement objects

    See:
      * https://github.com/hgrecco/pint/issues/1017
      * https://github.com/openforcefield/openff-evaluator/pull/341

    """

    def test_pickle_unit(self):
        x = unit.kelvin
        y = pickle.loads(pickle.dumps(x))

        assert x == y

    def test_pickle_quantity(self):
        x = 1.0 * unit.kelvin
        y = pickle.loads(pickle.dumps(x))

        assert x == y

    @skip_if_missing("uncertainties")
    def test_pickle_quantity_uncertainties(self):
        x = (1.0 * unit.kelvin).plus_minus(0.05)
        y = pickle.loads(pickle.dumps(x))

        assert x.value == y.value and x.error == y.error


class TestCompChemUnits:
    """Test some non-standard units used in comp chem stacks."""

    @pytest.mark.parametrize(
        "shorthand_string,full_string",
        [
            (
                "2000.0 * kilocalories_per_mole/angstrom**2",
                "2000.0 * kilocalories/mole/angstrom**2",
            ),
            (
                "2000.0 * kilocalorie_per_mole/angstrom**2",
                "2000.0 * kilocalorie/mole/angstrom**2",
            ),
            (
                "2000.0 * kilojoules_per_mole/angstrom**2",
                "2000.0 * kilojoules/mole/angstrom**2",
            ),
            (
                "2000.0 * kilojoule_per_mole/angstrom**2",
                "2000.0 * kilojoule/mole/angstrom**2",
            ),
        ],
    )
    def test_parse_molar_units_string(self, shorthand_string, full_string):
        assert unit.Quantity(shorthand_string) == unit.Quantity(full_string)

    def test_timestep_creation(self):
        # basic sanity check, can I make the unit and does it serialize
        q = 10 * unit.timestep

        assert q.m == 10
        assert str(q) == "10 timestep"

    def test_timestep_compatibility(self):
        # timesteps aren't a form of time
        q = 20 * unit.timestep

        assert not q.is_compatible_with(10 * unit.picosecond)


class TestNMRUnits:
    @pytest.mark.parametrize(
        ["unit_name", "symbol"],
        [
            ("tesla", "T"),
            ("ohm", "Ω"),
            ("henry", "H"),
            ("siemens", "S"),
            ("watt", "W"),
            ("weber", "Wb"),
        ],
    )
    def test_symbols(self, unit_name, symbol):
        number = random.random()
        assert Quantity(f"{number} {unit_name}") == Quantity(f"{number} {symbol}")

    def test_nmr_equivalents(self):
        assert Quantity("1 ohm") == Quantity("1 henry / second")
        assert Quantity("1 ohm") == 1 / Quantity("1 siemens")
        assert Quantity("1 ohm") == Quantity("1 volt / ampere")
        assert Quantity("1 watt") == Quantity("1 joule / second")
        assert Quantity("1 henry") == Quantity("1 weber / ampere")
        assert Quantity("1 tesla") == Quantity("1 weber / meter**2")
