""" Defines simple line geometry to plot a list of y values agains x.
"""

from typing import Any, Callable, List, Optional, Tuple, Union
import bokeh as bk

from data.tools.zplot2 import renderers
from data.tools.zplot2.data import DataType, LineData
from data.tools.zplot2.data_set import LineDataSet
from data.tools.zplot2.geometries import Geometry
from data.tools.zplot2.errors import CallbackError, InvalidArgumentError

RendererType = Union[renderers.LineRenderer, renderers.MarkerRenderer]
RendererStyle = Union[renderers.LineStyle, renderers.MarkerStyle]


class Line(Geometry):
    """Line Geometry Class"""

    def __init__(self, input_data: LineData = None, name: str = ""):
        """
        Arguments:
            input_data: LineData object
            name: Name of the Line object. Used to plot legends, in checkboxes,
                  etc.
        Attributes:
            CDS_data: input_data in ColumnDataSource format.
            renderers: list of renderers associated with this object.
        """
        if input_data is None:
            input_data = LineData.fromDict({"x": [], "y": []})
        super().__init__(input_data, name)

    def create_renderer(
        self,
        renderer_type: str,
        style: RendererStyle,
        renderer_id: Optional[str] = None,
        x_coord: str = "x",
        y_coord: str = "y",
    ) -> renderers.Renderer:
        # TODO convert this into a factory method and add some factories for
        # each type of data
        """Adds a new renderers to this object.
        Arguments:
            renderer_type: type of renderer to add (e.g. line, blob)
            style: Style of the renderer to add.
            renderer_id: Optional id for the renderer.
        """
        if renderer_type == "line":
            if not isinstance(style, renderers.LineStyle):
                raise InvalidArgumentError(
                    "Style incompatible with " "line renderer."
                )
            return renderers.LineRenderer(style, renderer_id, x_coord, y_coord)
        if renderer_type == "marker":
            if not isinstance(style, renderers.MarkerStyle):
                raise InvalidArgumentError(
                    "Style incompatible with " "marker renderer"
                )
            return renderers.MarkerRenderer(
                style, renderer_id, x_coord, y_coord
            )
        raise InvalidArgumentError("Renderer type does not exist")

    PointData = Union[int, float]
    SingleArray = List[PointData]
    TwoArrays = Tuple[SingleArray, SingleArray]
    ValidLineCallbackReturnTypes = Union[SingleArray, TwoArrays]

    @staticmethod
    def generate_data_from_callback(
        cb: Callable[[int, float], ValidLineCallbackReturnTypes],
        index: int,
        value: float,
    ) -> dict:
        """ Uses the input callback to generate a line data set.
        Arguments:
            cb: Callback function. It should return either a ([x_vals], [y_val])
                tuple, or a list of x_values.
            index: Index of the slider
            value: Value of the slider
        Returns:
            temp_dict: dictionary populated with values from callback
        """
        try:
            cb_result = cb(index, value)
        except Exception as err:
            raise CallbackError(
                f"Callback raised an exception when provided: "
                f"idx={index}, value={value}"
            ) from err
        if isinstance(cb_result, tuple):
            x = cb_result[0]
            y = cb_result[1]
            if len(x) != len(y):
                raise ArgumentValueError(
                    "Callback returns 2 lists of "
                    "different length. len(x) = {}, "
                    "len(y) = {}".format(len(x), len(y))
                )
        elif isinstance(cb_result, list):
            y = cb_result
            x = [n for n in range(len(y))]
        temp_dict = {"x": x, "y": y}
        return temp_dict
