"""
Test cases for Dimension and Dimensioned object behaviour.
"""

import numpy as np
import pandas as pd
import pytest

import holoviews as hv
from holoviews.core import Dimensioned
from holoviews.core.util import NUMPY_GE_2_0_0
from holoviews.testing import assert_element_equal

from ..utils import LoggingComparison


class DimensionNameLabelTest(LoggingComparison):
    def test_dimension_name(self):
        dim = hv.Dimension("test")
        assert dim.name == "test"

    def test_dimension_name_and_label(self):
        dim = hv.Dimension("test")
        assert dim.name == "test"
        assert dim.label == "test"

    def test_dimension_name_tuple(self):
        dim = hv.Dimension(("test", "A test"))
        assert dim.name == "test"

    def test_dimension_label_tuple(self):
        dim = hv.Dimension(("test", "A test"))
        assert dim.label == "A test"

    def test_dimension_label_kwarg(self):
        dim = hv.Dimension("test", label="A test")
        assert dim.label == "A test"

    def test_dimension_dict_empty(self):
        with pytest.raises(ValueError, match='must contain a "name" key'):
            hv.Dimension({})

    def test_dimension_dict_label(self):
        with pytest.raises(ValueError, match='must contain a "name" key'):
            hv.Dimension(dict(label="A test"))

    def test_dimension_dict_name(self):
        dim = hv.Dimension(dict(name="test"))
        assert dim.name == "test"
        assert dim.label == "test"

    def test_dimension_dict_name_and_label(self):
        dim = hv.Dimension(dict(name="test", label="A test"))
        assert dim.name == "test"
        assert dim.label == "A test"

    def test_dimension_label_kwarg_and_tuple(self):
        dim = hv.Dimension(("test", "A test"), label="Another test")
        substr = (
            "Using label as supplied by keyword ('Another test'), ignoring tuple value 'A test'"
        )
        self.log_handler.assert_endswith("WARNING", substr)
        assert dim.label == "Another test"

    def test_dimension_invalid_name(self):
        regexp = "Dimension name must only be passed as the positional argument"
        with pytest.raises(KeyError, match=regexp):
            hv.Dimension("test", name="something else")

    def test_dimension_invalid_name_tuple(self):
        regexp = "Dimension name must only be passed as the positional argument"
        with pytest.raises(KeyError, match=regexp):
            hv.Dimension(("test", "test dimension"), name="something else")


class DimensionReprTest:
    def test_name_dimension_repr(self):
        dim = hv.Dimension("test")
        assert repr(dim) == "Dimension('test')"

    def test_name_dimension_repr_eval_equality(self):
        dim = hv.Dimension("test")
        assert eval(repr(dim), {"Dimension": hv.Dimension}) == dim

    def test_name_dimension_repr_tuple(self):
        dim = hv.Dimension(("test", "Test Dimension"))
        assert repr(dim) == "Dimension('test', label='Test Dimension')"

    def test_name_dimension_repr_tuple_eval_equality(self):
        dim = hv.Dimension(("test", "Test Dimension"))
        assert eval(repr(dim), {"Dimension": hv.Dimension}) == dim

    def test_name_dimension_repr_params(self):
        dim = hv.Dimension("test", label="Test Dimension", unit="m")
        assert repr(dim) == "Dimension('test', label='Test Dimension', unit='m')"

    def test_name_dimension_repr_params_eval_equality(self):
        dim = hv.Dimension("test", label="Test Dimension", unit="m")
        assert eval(repr(dim), {"Dimension": hv.Dimension}) == dim

    def test_pprint_value_boolean(self):
        # https://github.com/holoviz/holoviews/issues/5378
        dim = hv.Dimension("test")
        assert dim.pprint_value(True) == "True"
        assert dim.pprint_value(False) == "False"


class DimensionEqualityTest:
    def test_simple_dim_equality(self):
        dim1 = hv.Dimension("test")
        dim2 = hv.Dimension("test")
        assert dim1 == dim2

    def test_simple_str_equality(self):
        dim1 = hv.Dimension("test")
        dim2 = hv.Dimension("test")
        assert dim1 == str(dim2)

    def test_simple_dim_inequality(self):
        dim1 = hv.Dimension("test1")
        dim2 = hv.Dimension("test2")
        assert not dim1 == dim2

    def test_simple_str_inequality(self):
        dim1 = hv.Dimension("test1")
        dim2 = hv.Dimension("test2")
        assert not dim1 == str(dim2)

    def test_label_dim_inequality(self):
        dim1 = hv.Dimension(("test", "label1"))
        dim2 = hv.Dimension(("test", "label2"))
        assert not dim1 == dim2

    def test_label_str_equality(self):
        dim1 = hv.Dimension(("test", "label1"))
        dim2 = hv.Dimension(("test", "label2"))
        assert dim1 == str(dim2)

    def test_weak_dim_equality(self):
        dim1 = hv.Dimension("test", cyclic=True, unit="m", type=float)
        dim2 = hv.Dimension("test", cyclic=False, unit="km", type=int)
        assert dim1 == dim2

    def test_weak_str_equality(self):
        dim1 = hv.Dimension("test", cyclic=True, unit="m", type=float)
        dim2 = hv.Dimension("test", cyclic=False, unit="km", type=int)
        assert dim1 == str(dim2)


class DimensionValuesTest:
    def setup_method(self):
        self.values1 = [0, 1, 2, 3, 4, 5, 6]
        self.values2 = ["a", "b", "c", "d"]

        self.duplicates1 = [0, 1, 0, 2, 3, 4, 3, 2, 5, 5, 6]
        self.duplicates2 = ["a", "b", "b", "a", "c", "a", "c", "d", "d"]

    def test_dimension_values_list1(self):
        dim = hv.Dimension("test", values=self.values1)
        assert dim.values == self.values1

    def test_dimension_values_list2(self):
        dim = hv.Dimension("test", values=self.values2)
        assert dim.values == self.values2

    def test_dimension_values_list_duplicates1(self):
        dim = hv.Dimension("test", values=self.duplicates1)
        assert dim.values == self.values1

    def test_dimension_values_list_duplicates2(self):
        dim = hv.Dimension("test", values=self.duplicates2)
        assert dim.values == self.values2

    def test_dimension_values_array1(self):
        dim = hv.Dimension("test", values=np.array(self.values1))
        assert dim.values == self.values1

    def test_dimension_values_array2(self):
        dim = hv.Dimension("test", values=np.array(self.values2))
        assert dim.values == self.values2

    def test_dimension_values_array_duplicates1(self):
        dim = hv.Dimension("test", values=np.array(self.duplicates1))
        assert dim.values == self.values1

    def test_dimension_values_array_duplicates2(self):
        dim = hv.Dimension("test", values=np.array(self.duplicates2))
        assert dim.values == self.values2

    def test_dimension_values_series1(self):
        df = pd.DataFrame({"col": self.values1})
        dim = hv.Dimension("test", values=df["col"])
        assert dim.values == self.values1

    def test_dimension_values_series2(self):
        df = pd.DataFrame({"col": self.values2})
        dim = hv.Dimension("test", values=df["col"])
        assert dim.values == self.values2

    def test_dimension_values_series_duplicates1(self):
        df = pd.DataFrame({"col": self.duplicates1})
        dim = hv.Dimension("test", values=df["col"])
        assert dim.values == self.values1

    def test_dimension_values_series_duplicates2(self):
        df = pd.DataFrame({"col": self.duplicates2})
        dim = hv.Dimension("test", values=df["col"])
        assert dim.values == self.values2


class DimensionCloneTest:
    def test_simple_clone(self):
        dim = hv.Dimension("test")
        assert dim.name == "test"
        assert dim.clone("bar").name == "bar"

    def test_simple_label_clone(self):
        dim = hv.Dimension("test")
        assert dim.name == "test"
        clone = dim.clone(label="label")
        assert clone.name == "test"
        assert clone.label == "label"

    def test_simple_values_clone(self):
        dim = hv.Dimension("test", values=[1, 2, 3])
        assert dim.values == [1, 2, 3]
        clone = dim.clone(values=[4, 5, 6])
        assert clone.name == "test"
        assert clone.values == [4, 5, 6]

    def test_tuple_clone(self):
        dim = hv.Dimension("test")
        assert dim.name == "test"
        clone = dim.clone(("test", "A test"))
        assert clone.name == "test"
        assert clone.label == "A test"


class DimensionDefaultTest:
    def test_validate_default_against_values(self):
        if NUMPY_GE_2_0_0:
            msg = r"Dimension\('A'\) default 1\.1 not found in declared values: \[np\.int64\(0\), np\.int64\(1\)\]"
        else:
            msg = r"Dimension\('A'\) default 1\.1 not found in declared values: \[0, 1\]"
        with pytest.raises(ValueError, match=msg):
            hv.Dimension("A", values=[0, 1], default=1.1)

    def test_validate_default_against_range(self):
        msg = r"Dimension\('A'\) default 1\.1 not in declared range: \(0, 1\)"
        with pytest.raises(ValueError, match=msg):
            hv.Dimension("A", range=(0, 1), default=1.1)


class DimensionedTest:
    def test_dimensioned_init(self):
        Dimensioned("An example of arbitrary data")

    def test_dimensioned_constant_label(self):
        label = "label"
        view = Dimensioned("An example of arbitrary data", label=label)
        assert view.label == label
        try:
            view.label = "another label"
            raise AssertionError("Label should be a constant parameter.")
        except TypeError:
            pass

    def test_dimensioned_redim_string(self):
        dimensioned = Dimensioned("Arbitrary Data", kdims=["x"])
        redimensioned = dimensioned.clone(kdims=["Test"])
        assert_element_equal(redimensioned, dimensioned.redim(x="Test"))

    def test_dimensioned_redim_dict_label(self):
        dimensioned = Dimensioned("Arbitrary Data", kdims=["x"])
        redimensioned = dimensioned.clone(kdims=[("x", "Test")])
        assert_element_equal(redimensioned, dimensioned.redim.label(x="Test"))

    def test_dimensioned_redim_dict_label_existing_error(self):
        dimensioned = Dimensioned("Arbitrary Data", kdims=[("x", "Test1")])
        with pytest.raises(ValueError, match="Cannot override an existing Dimension label"):
            dimensioned.redim.label(x="Test2")

    def test_dimensioned_redim_dimension(self):
        dimensioned = Dimensioned("Arbitrary Data", kdims=["x"])
        redimensioned = dimensioned.clone(kdims=["Test"])
        assert_element_equal(redimensioned, dimensioned.redim(x=hv.Dimension("Test")))

    def test_dimensioned_redim_dict(self):
        dimensioned = Dimensioned("Arbitrary Data", kdims=["x"])
        redimensioned = dimensioned.clone(kdims=["Test"])
        assert_element_equal(redimensioned, dimensioned.redim(x={"name": "Test"}))

    def test_dimensioned_redim_dict_range(self):
        redimensioned = Dimensioned("Arbitrary Data", kdims=["x"]).redim(x={"range": (0, 10)})
        assert redimensioned.kdims[0].range == (0, 10)

    def test_dimensioned_redim_range_aux(self):
        dimensioned = Dimensioned("Arbitrary Data", kdims=["x"])
        redimensioned = dimensioned.redim.range(x=(-10, 42))
        assert redimensioned.kdims[0].range == (-10, 42)

    def test_dimensioned_redim_cyclic_aux(self):
        dimensioned = Dimensioned("Arbitrary Data", kdims=["x"])
        redimensioned = dimensioned.redim.cyclic(x=True)
        assert redimensioned.kdims[0].cyclic == True
