"""
.. codeauthor:: David Zwicker <david.zwicker@ds.mpg.de>
"""

import json
from pathlib import Path

import numpy as np
import pytest

from pde.tools import misc


@pytest.mark.parametrize("cache", [True, False])
def test_module_available(cache):
    """Test the module_available function."""
    # clear cache to provide a defined environment
    misc._MODULE_AVAILABILITY_CACHE = {}

    # run each test twice to check the cache
    assert misc.module_available("numpy", cache=cache)
    assert misc.module_available("numpy", cache=cache)
    assert cache == ("numpy" in misc._MODULE_AVAILABILITY_CACHE)

    assert not misc.module_available("not available", cache=cache)
    assert not misc.module_available("not available", cache=cache)
    assert cache == ("not available" in misc._MODULE_AVAILABILITY_CACHE)


def test_ensure_directory_exists(tmp_path):
    """Tests the ensure_directory_exists function."""
    # create temporary name
    path = tmp_path / "test_ensure_directory_exists"
    assert not path.exists()
    # create the folder
    misc.ensure_directory_exists(path)
    assert path.is_dir()
    # check that a second call has the same result
    misc.ensure_directory_exists(path)
    assert path.is_dir()
    # remove the folder again
    Path.rmdir(path)
    assert not path.exists()


def test_hybridmethod():
    """Test the hybridmethod decorator."""

    class Test:
        @misc.hybridmethod
        def method(cls):
            return "class"

        @method.instancemethod
        def method(self):
            return "instance"

    assert Test.method() == "class"
    assert Test().method() == "instance"


def test_estimate_computation_speed():
    """Test estimate_computation_speed method."""

    def f(x):
        return 2 * x

    def g(x):
        return np.sin(x) * np.cos(x) ** 2

    assert misc.estimate_computation_speed(f, 1) > misc.estimate_computation_speed(g, 1)


def test_classproperty():
    """Test classproperty decorator."""

    class Test:
        _value = 2

        @misc.classproperty
        def value(cls):
            return cls._value

    assert Test.value == 2


@pytest.mark.skipif(not misc.module_available("h5py"), reason="requires `h5py` module")
def test_hdf_write_attributes(tmp_path):
    """Test hdf_write_attributes function."""
    import h5py

    path = tmp_path / "test_hdf_write_attributes.hdf5"

    # test normal case
    data = {"a": 3, "b": "asd"}
    with h5py.File(path, "w") as hdf_file:
        misc.hdf_write_attributes(hdf_file, data)
        data2 = {k: json.loads(v) for k, v in hdf_file.attrs.items()}

    assert data == data2
    assert data is not data2

    # test silencing of problematic items
    with h5py.File(path, "w") as hdf_file:
        misc.hdf_write_attributes(hdf_file, {"a": 1, "b": object()})
        data2 = {k: json.loads(v) for k, v in hdf_file.attrs.items()}
    assert data2 == {"a": 1}

    # test raising problematic items
    with h5py.File(path, "w") as hdf_file, pytest.raises(TypeError):
        misc.hdf_write_attributes(
            hdf_file, {"a": object()}, raise_serialization_error=True
        )


def test_number():
    """Test the conversion to a number."""
    assert misc.number(1) == 1
    assert misc.number(1.5) == 1.5
    assert misc.number(-2 + 4.5j) == -2 + 4.5j
    assert misc.number(1e2) == 1e2

    assert misc.number("1") == 1
    assert misc.number("1.5") == 1.5
    assert misc.number("-2 + 4.5j") == -2 + 4.5j
    assert misc.number("-2+4.5j") == -2 + 4.5j
    assert misc.number("1e2") == 100

    with pytest.raises(ValueError):
        misc.number("invalid")
