import numpy as np
from bokeh.models import CategoricalColorMapper, LinearColorMapper

import holoviews as hv
from holoviews.plotting.bokeh.util import property_to_dict
from holoviews.testing import assert_data_equal, assert_dict_equal

from ..utils import ParamLogStream
from .test_plot import TestBokehPlot, bokeh_renderer


class TestLabelsPlot(TestBokehPlot):
    def test_labels_simple(self):
        labels = hv.Labels([(0, 1, "A"), (1, 0, "B")])
        plot = bokeh_renderer.get_plot(labels)
        source = plot.handles["source"]
        glyph = plot.handles["glyph"]
        expected = {"x": np.array([0, 1]), "y": np.array([1, 0]), "Label": ["A", "B"]}
        for k, vals in expected.items():
            np.testing.assert_array_equal(source.data[k], vals)
        assert glyph.x == "x"
        assert glyph.y == "y"
        assert glyph.text == "Label"

    def test_labels_empty(self):
        labels = hv.Labels([])
        plot = bokeh_renderer.get_plot(labels)
        source = plot.handles["source"]
        glyph = plot.handles["glyph"]
        expected = {"x": np.array([]), "y": np.array([]), "Label": []}
        for k, vals in expected.items():
            np.testing.assert_array_equal(source.data[k], vals)
        assert glyph.x == "x"
        assert glyph.y == "y"
        assert glyph.text == "Label"

    def test_labels_formatter(self):
        vdim = hv.Dimension("text", value_format=lambda x: f"{x:.1f}")
        labels = hv.Labels([(0, 1, 0.33333), (1, 0, 0.66666)], vdims=vdim)
        plot = bokeh_renderer.get_plot(labels)
        source = plot.handles["source"]
        glyph = plot.handles["glyph"]
        expected = {"x": np.array([0, 1]), "y": np.array([1, 0]), "text": ["0.3", "0.7"]}
        for k, vals in expected.items():
            np.testing.assert_array_equal(source.data[k], vals)
        assert glyph.x == "x"
        assert glyph.y == "y"
        assert glyph.text == "text"

    def test_labels_inverted(self):
        labels = hv.Labels([(0, 1, "A"), (1, 0, "B")]).opts(invert_axes=True)
        plot = bokeh_renderer.get_plot(labels)
        source = plot.handles["source"]
        glyph = plot.handles["glyph"]
        expected = {"x": np.array([0, 1]), "y": np.array([1, 0]), "Label": ["A", "B"]}
        for k, vals in expected.items():
            np.testing.assert_array_equal(source.data[k], vals)
        assert glyph.x == "y"
        assert glyph.y == "x"
        assert glyph.text == "Label"

    def test_labels_color_mapped_text_vals(self):
        labels = hv.Labels([(0, 1, 0.33333), (1, 0, 0.66666)]).opts(color_index=2)
        plot = bokeh_renderer.get_plot(labels)
        source = plot.handles["source"]
        glyph = plot.handles["glyph"]
        cmapper = plot.handles["color_mapper"]
        expected = {
            "x": np.array([0, 1]),
            "y": np.array([1, 0]),
            "Label": ["0.33333", "0.66666"],
            "text_color": np.array([0.33333, 0.66666]),
        }
        for k, vals in expected.items():
            np.testing.assert_array_equal(source.data[k], vals)
        assert glyph.x == "x"
        assert glyph.y == "y"
        assert glyph.text == "Label"
        assert property_to_dict(glyph.text_color) == {"field": "text_color", "transform": cmapper}
        assert cmapper.low == 0.33333
        assert cmapper.high == 0.66666

    def test_labels_color_mapped(self):
        labels = hv.Labels([(0, 1, 0.33333, 2), (1, 0, 0.66666, 1)], vdims=["text", "color"]).opts(
            color_index=3
        )
        plot = bokeh_renderer.get_plot(labels)
        source = plot.handles["source"]
        glyph = plot.handles["glyph"]
        cmapper = plot.handles["color_mapper"]
        expected = {
            "x": np.array([0, 1]),
            "y": np.array([1, 0]),
            "text": ["0.33333", "0.66666"],
            "color": np.array([2, 1]),
        }
        for k, vals in expected.items():
            np.testing.assert_array_equal(source.data[k], vals)
        assert glyph.x == "x"
        assert glyph.y == "y"
        assert glyph.text == "text"
        assert property_to_dict(glyph.text_color) == {"field": "color", "transform": cmapper}
        assert cmapper.low == 1
        assert cmapper.high == 2

    ###########################
    #    Styling mapping      #
    ###########################

    def test_label_color_op(self):
        labels = hv.Labels([(0, 0, "#000"), (0, 1, "#F00"), (0, 2, "#0F0")], vdims="color").opts(
            text_color="color"
        )
        plot = bokeh_renderer.get_plot(labels)
        cds = plot.handles["cds"]
        glyph = plot.handles["glyph"]
        assert cds.data["text_color"] == ["#000", "#F00", "#0F0"]
        assert property_to_dict(glyph.text_color) == {"field": "text_color"}

    def test_label_linear_color_op(self):
        labels = hv.Labels([(0, 0, 0), (0, 1, 1), (0, 2, 2)], vdims="color").opts(
            text_color="color"
        )
        plot = bokeh_renderer.get_plot(labels)
        cds = plot.handles["cds"]
        glyph = plot.handles["glyph"]
        cmapper = plot.handles["text_color_color_mapper"]
        assert isinstance(cmapper, LinearColorMapper)
        assert cmapper.low == 0
        assert cmapper.high == 2
        np.testing.assert_array_equal(cds.data["text_color"], [0, 1, 2])
        assert property_to_dict(glyph.text_color) == {"field": "text_color", "transform": cmapper}

    def test_label_categorical_color_op(self):
        labels = hv.Labels([(0, 0, "A"), (0, 1, "B"), (0, 2, "C")], vdims="color").opts(
            text_color="color"
        )
        plot = bokeh_renderer.get_plot(labels)
        cds = plot.handles["cds"]
        glyph = plot.handles["glyph"]
        cmapper = plot.handles["text_color_color_mapper"]
        assert isinstance(cmapper, CategoricalColorMapper)
        assert cmapper.factors == ["A", "B", "C"]
        assert cds.data["text_color"] == ["A", "B", "C"]
        assert property_to_dict(glyph.text_color) == {"field": "text_color", "transform": cmapper}

    def test_label_angle_op(self):
        labels = hv.Labels([(0, 0, 0), (0, 1, 45), (0, 2, 90)], vdims="angle").opts(angle="angle")
        plot = bokeh_renderer.get_plot(labels)
        cds = plot.handles["cds"]
        glyph = plot.handles["glyph"]
        np.testing.assert_array_almost_equal(cds.data["angle"], [0, 0.785398, 1.570796])
        assert property_to_dict(glyph.angle) == {"field": "angle"}

    def test_label_alpha_op(self):
        labels = hv.Labels([(0, 0, 0), (0, 1, 0.2), (0, 2, 0.7)], vdims="alpha").opts(
            text_alpha="alpha"
        )
        plot = bokeh_renderer.get_plot(labels)
        cds = plot.handles["cds"]
        glyph = plot.handles["glyph"]
        assert_data_equal(cds.data["text_alpha"], np.array([0, 0.2, 0.7]))
        assert property_to_dict(glyph.text_alpha) == {"field": "text_alpha"}

    def test_label_font_size_op_strings(self):
        labels = hv.Labels([(0, 0, "10pt"), (0, 1, "4pt"), (0, 2, "8pt")], vdims="size").opts(
            text_font_size="size"
        )
        plot = bokeh_renderer.get_plot(labels)
        cds = plot.handles["cds"]
        glyph = plot.handles["glyph"]
        assert cds.data["text_font_size"] == ["10pt", "4pt", "8pt"]
        assert property_to_dict(glyph.text_font_size) == {"field": "text_font_size"}

    def test_label_font_size_op_ints(self):
        labels = hv.Labels([(0, 0, 10), (0, 1, 4), (0, 2, 8)], vdims="size").opts(
            text_font_size="size"
        )
        plot = bokeh_renderer.get_plot(labels)
        cds = plot.handles["cds"]
        glyph = plot.handles["glyph"]
        assert cds.data["text_font_size"] == ["10pt", "4pt", "8pt"]
        assert property_to_dict(glyph.text_font_size) == {"field": "text_font_size"}

    def test_labels_color_index_color_clash(self):
        labels = hv.Labels([(0, 0, 0), (0, 1, 1), (0, 2, 2)], vdims="color").opts(
            text_color="color", color_index="color"
        )
        with ParamLogStream() as log:
            bokeh_renderer.get_plot(labels)
        log_msg = log.stream.read()
        warning = (
            "The `color_index` parameter is deprecated in favor of color style mapping, "
            "e.g. `color=dim('color')` or `line_color=dim('color')`\nCannot declare style "
            "mapping for 'text_color' option and declare a color_index; ignoring the "
            "color_index.\n"
        )
        assert log_msg == warning

    def test_labels_text_color_cycle(self):
        hm = hv.HoloMap(
            {i: hv.Labels([(0, 0 + i, "Label 1"), (1, 1 + i, "Label 2")]) for i in range(3)}
        ).overlay()
        assert isinstance(hm[0].opts["text_color"], hv.Cycle)

    def test_labels_hover_with_vdims(self):
        labels = hv.Labels(
            [(0, 1, "A", 10.5, 100), (1, 0, "B", 20.3, 200)], vdims=["text", "value", "count"]
        ).opts(hover_tooltips=[("Text", "@text"), ("Value", "@value"), ("Count", "@count")])
        plot = bokeh_renderer.get_plot(labels)
        source = plot.handles["source"]
        glyph = plot.handles["glyph"]
        hover = plot.handles["hover"]

        expected = {
            "x": np.array([0, 1]),
            "y": np.array([1, 0]),
            "text": ["A", "B"],
            "value": np.array([10.5, 20.3]),
            "count": np.array([100, 200]),
        }
        assert_dict_equal(source.data, expected)

        assert glyph.x == "x"
        assert glyph.y == "y"
        assert glyph.text == "text"

        assert hover.tooltips == [
            ("Text", "@{text}"),
            ("Value", "@{value}"),
            ("Count", "@{count}"),
        ]
