# SPDX-FileCopyrightText: Copyright (c) 2021, 2023 Jose David M.
#
# SPDX-License-Identifier: MIT
"""

`scales`
================================================================================

Allows display data in a graduated level


* Author(s): Jose David M.

Implementation Notes
--------------------

Scales version in CircuitPython

"""

################################
# A scales library for CircuitPython, using `displayio`` and `vectorio``
#
# Features:
#  - Vertical and Horizontal direction
#  - Animation to use with different sensor

try:
    from typing import Union, Tuple, Optional
except ImportError:
    pass

import displayio
import terminalio
from adafruit_display_text.bitmap_label import Label
from vectorio import Polygon, Rectangle
import ulab.numpy as np

try:
    from typing import Tuple
except ImportError:
    pass

__version__ = "0.6.1"
__repo__ = "https://github.com/jposada202020/CircuitPython_scales.git"

# pylint: disable=too-many-arguments, too-few-public-methods,too-many-instance-attributes
# pylint: disable=invalid-unary-operand-type
class Axes(displayio.Group):
    """
    :param int x: pixel position. Defaults to :const:`0`
    :param int y: pixel position. Defaults to :const:`0`

    :param int,int limits: tuple of value range for the scale. Defaults to (0, 100)
    :param list ticks: list to ticks to display. If this is not enter a equally spaced scale
     will be created between the given limits.

    :param str direction: direction of the scale either :attr:`horizontal` or :attr:`vertical`
     defaults to :attr:`horizontal`

    :param int stroke: width in pixels of the scale axes. Defaults to :const:`3`

    :param int length: scale length in pixels. Defaults to :const:`100`

    :param int color: 24-bit hex value axes line color, Defaults to Purple :const:`0x990099`

    """

    def __init__(
        self,
        x: int = 0,
        y: int = 0,
        limits: Tuple[int, int] = (0, 100),
        ticks: Optional[Union[np.array, list]] = None,
        direction: str = "horizontal",
        stroke: int = 3,
        length: int = 100,
        color: int = 0x990099,
    ):

        super().__init__()

        if direction == "horizontal":
            self.direction = True
        else:
            self.direction = False

        self.x = x
        self.y = y
        self.limits = limits

        self._valuemin = limits[0]
        self._valuemax = limits[1]
        self._newvalmin = length
        self._newvalmax = 0

        self.stroke = stroke
        self.length = length

        if ticks:
            self.ticks = np.array(ticks)
        else:
            self.ticks = np.array(list(range(self._valuemin, self._valuemax, 10)))

        self.ticksynorm = np.array(
            transform(
                self._valuemin,
                self._valuemax,
                self._newvalmax,
                self._newvalmin,
                self.ticks,
            ),
            dtype=np.int16,
        )

        self._palette = displayio.Palette(2)
        self._palette.make_transparent(0)
        self._palette[1] = color

        self._tick_length = None
        self._tick_stroke = None

        self.text_ticks = []

    def _draw_line(self) -> None:
        """Private function to draw the Axe.
        :return: None
        """
        if self.direction:
            self.append(rectangle_draw(0, 0, self.stroke, self.length, self._palette))
        else:
            self.append(
                rectangle_draw(0, -self.length, self.length, self.stroke, self._palette)
            )

    def _draw_ticks(self, tick_length: int = 10, tick_stroke: int = 4) -> None:
        """Private function to draw the ticks
        :param int tick_length: tick length in pixels
        :param int tick_stroke: tick thickness in pixels
        :return: None
        """

        self._tick_length = tick_length
        self._tick_stroke = tick_stroke

        if self.direction:
            for val in self.ticksynorm[:-1]:
                self.append(
                    rectangle_draw(
                        int(val) - 1,
                        -self._tick_length,
                        self._tick_length,
                        3,
                        self._palette,
                    )
                )
        else:
            for val in self.ticksynorm[:-1]:
                self.append(
                    rectangle_draw(0, -int(val), 3, self._tick_length, self._palette)
                )

    def _draw_text(self) -> None:
        """Private function to draw the text, uses values found in ``_conversion``
        :return: None
        """
        index = 0
        separation = 20
        font_width = 12
        if self.direction:
            print("aca")
            for tick_text in self.ticks[:-1]:
                dist_x = self.ticksynorm[index] - font_width // 2
                dist_y = separation // 2
                text = str(int(tick_text))
                tick_label = Label(terminalio.FONT, text=text, x=dist_x, y=dist_y)
                self.append(tick_label)
                index = index + 1
        else:
            for tick_text in self.ticks[:-1]:
                dist_x = -separation
                dist_y = -self.ticksynorm[index]
                text = str(int(tick_text))
                tick_label = Label(terminalio.FONT, text=text, x=dist_x, y=dist_y)
                self.append(tick_label)
                index = index + 1


class Scale(Axes):
    """
    :param int x: pixel position. Defaults to :const:`0`
    :param int y: pixel position. Defaults to :const:`0`

    :param str direction: direction of the scale either :attr:`horizontal` or :attr:`vertical`
     defaults to :attr:`horizontal`

    :param int stroke: width in pixels of the axes line. Defaults to :const:`3` pixels

    :param int length: scale length in pixels. Defaults to :const:`100` pixels

    :param int color: 24-bit hex value axes line color, Defaults to Purple :const:`0x990099`

    :param int width: scale width in pixels. Defaults to :const:`50` pixels

    :param limits: tuple of value range for the scale. Defaults to :const:`(0, 100)`
    :param list ticks: list to ticks to display. If this is not enter a equally spaced scale
     will be created between the given limits.

    :param int back_color: 24-bit hex value axes line color.
     Defaults to Light Blue :const:`0x9FFFFF`

    :param int tick_length: Scale tick length in pixels. Defaults to :const:`10`
    :param int tick_stroke: Scale tick width in pixels. Defaults to :const:`4`

    :param int pointer_length: length in pixels for the point. Defaults to :const:`20` pixels
    :param int pointer_stroke: pointer thickness in pixels. Defaults to :const:`6` pixels


    **Quickstart: Importing and using Scales**

    Here is one way of importing the `Scale` class, so you can use it as
    the name ``my_scale``:

    .. code-block::

        from scale import Scale

    Now you can create a vertical Scale at pixel position x=50, y=180 and a range
    of 0 to 80 using:

    .. code-block::

        my_scale = Scale(x=50, y=180, direction="vertical", limits=(0, 80))

    Once you setup your display, you can now add ``my_scale`` to your display using:

    .. code-block::

        display.show(my_scale)

    If you want to have multiple display elements, you can create a group and then
    append the scale and the other elements to the group. Then, you can add the full
    group to the display as in this example:

    .. code-block:: python

        my_scale= Scale(x=20, y=30)
        my_group = displayio.Group() # make a group
        my_group.append(my_scale) # Add my_slider to the group

        #
        # Append other display elements to the group
        #

        display.show(my_group) # add the group to the display


    **Summary: Slider Features and input variables**

    The `Scale` class has some options for controlling its position, visible appearance,
    and value through a collection of input variables:

        - **position**: :attr:`x``, :attr:`y`

        - **size**: :attr:`length` and :attr:`width`

        - **color**: :attr:`color`, :attr:`back_color`

        - **linewidths**: :attr:`stroke` and :attr:`tick_stroke`

        - **range**: :attr:`limits`


    .. figure:: scales.png
      :scale: 100 %
      :align: center
      :alt: Diagram of scales

      Diagram showing a simple scale.


    """

    def __init__(
        self,
        x: int = 0,
        y: int = 0,
        direction: str = "horizontal",
        stroke: int = 3,
        length: int = 100,
        color: int = 0x990099,
        width: int = 50,
        limits: Tuple[int, int] = (0, 100),
        back_color: int = 0x9FFFFF,
        ticks: Optional[Union[np.array, list]] = None,
        tick_length: int = 5,
        tick_stroke: int = 4,
        pointer_length: int = 20,
        pointer_stroke: int = 6,
    ):

        super().__init__(
            x=x,
            y=y,
            direction=direction,
            stroke=stroke,
            length=length,
            limits=limits,
            ticks=ticks,
            color=color,
        )

        self._width = width
        self._back_color = back_color
        self._draw_background()
        self._draw_line()
        self._draw_ticks()
        self.value = 0

        self._tick_length = tick_length
        self._tick_stroke = tick_stroke
        self._pointer_length = pointer_length
        self._pointer_stroke = pointer_stroke

        # Pointer Definitions
        self._x0 = 0
        self._y0 = 0

        if self.direction:
            self.center = width // 2
            self._x1 = pointer_stroke
            self._y1 = pointer_length
            self._posx = 0
            self._posy = -self.center - self._pointer_length // 2
        else:
            self.center = width // 2
            self._x1 = pointer_length
            self._y1 = pointer_stroke
            self._posx = self.center - self._pointer_length // 2
            self._posy = -10
        self.pointer = None

        self._draw_text()
        self._draw_pointer()

    def _draw_background(self):
        """Private function to draw the background for the scale
        :return: None
        """
        back_palette = displayio.Palette(2)
        back_palette.make_transparent(0)
        back_palette[1] = self._back_color

        if self.direction:
            self.append(
                rectangle_draw(0, -self._width, self._width, self.length, back_palette)
            )
        else:
            self.append(
                rectangle_draw(0, -self.length, self.length, self._width, back_palette)
            )

    def _draw_pointer(
        self,
        color: int = 0xFF0000,
    ):
        """Private function to initial draw the pointer.

        :param int color: 24-bit hex value axes line color. Defaults to red :const:`0xFF0000`
        :param int val_ini: initial value to draw the pointer


        :return: None

        """

        pointer_palette = displayio.Palette(2)
        pointer_palette.make_transparent(0)
        pointer_palette[1] = color

        points = [
            (self._x0, self._y0),
            (self._x1, self._y0),
            (self._x1, self._y1),
            (self._x0, self._y1),
        ]
        self.pointer = Polygon(
            pixel_shader=pointer_palette,
            points=points,
            x=self._posx,
            y=self._posy,
            color_index=1,
        )

        self.append(self.pointer)

    def animate_pointer(self, new_value):
        """Public function to animate the pointer

        :param new_value: value to draw the pointer
        :return: None

        """
        value = int(
            transform(
                self._valuemin,
                self._valuemax,
                self._newvalmax,
                self._newvalmin,
                new_value,
            )
        )
        if self.direction:
            self.pointer.x = value - self._pointer_stroke // 2
        else:
            self.pointer.y = -value - self._pointer_stroke // 2


# pylint: disable=invalid-name
def rectangle_draw(x0: int, y0: int, height: int, width: int, palette):
    """rectangle_draw function

    Draws a rectangle using or `vectorio.Rectangle`

    :param int x0: rectangle lower corner x position
    :param int y0: rectangle lower corner y position

    :param int width: rectangle upper corner x position
    :param int height: rectangle upper corner y position

    :param `~displayio.Palette` palette: palette object to be used to draw the rectangle

    """

    return Rectangle(
        pixel_shader=palette, width=width, height=height, x=x0, y=y0, color_index=1
    )


def transform(
    oldrangemin: Union[float, int],
    oldrangemax: Union[float, int],
    newrangemin: Union[float, int],
    newrangemax: Union[float, int],
    value: Union[float, int],
) -> Union[float, int]:
    """
    This function converts the original value into a new defined value in the new range

    :param int|float oldrangemin: minimum of the original range
    :param int|float oldrangemax: maximum of the original range
    :param int|float newrangemin: minimum of the new range
    :param int|float newrangemax: maximum of the new range
    :param int|float value: value to be converted

    :return int|float: converted value

    """

    return (
        ((value - oldrangemin) * (newrangemax - newrangemin))
        / (oldrangemax - oldrangemin)
    ) + newrangemin
