# SPDX-FileCopyrightText: 2018 Kattni Rembor for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
`gfx`
====================================================

CircuitPython pixel graphics drawing library.

* Author(s): Kattni Rembor, Tony DiCola, Jonah Yolles-Murphy, based on code by Phil Burgess

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

**Hardware:**

**Software and Dependencies:**

* Adafruit CircuitPython firmware for the supported boards:
  https://github.com/adafruit/circuitpython/releases

"""

__version__ = "1.1.15"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_GFX.git"

# pylint: disable=invalid-name
class GFX:
    # pylint: disable=too-many-instance-attributes
    """Create an instance of the GFX drawing class.

    :param width: The width of the drawing area in pixels.
    :param height: The height of the drawing area in pixels.
    :param pixel: A function to call when a pixel is drawn on the display. This function
                  should take at least an x and y position and then any number of optional
                  color or other parameters.
    :param hline: A function to quickly draw a horizontal line on the display.
                  This should take at least an x, y, and width parameter and
                  any number of optional color or other parameters.
    :param vline: A function to quickly draw a vertical line on the display.
                  This should take at least an x, y, and height paraemter and
                  any number of optional color or other parameters.
    :param fill_rect: A function to quickly draw a solid rectangle with four
                  input parameters: x,y, width, and height. Any number of other
                  parameters for color or screen specific data.
    :param text: A function to quickly place text on the screen. The inputs include:
                  x, y data(top left as starting point).
    :param font: An optional input to augment the default text method with a new font.
                  The input should be a properly formatted dict.
    """
    # pylint: disable=too-many-arguments
    def __init__(
        self,
        width,
        height,
        pixel,
        hline=None,
        vline=None,
        fill_rect=None,
        text=None,
        font=None,
    ):
        # pylint: disable=too-many-instance-attributes
        self.width = width
        self.height = height
        self._pixel = pixel
        # Default to slow horizontal & vertical line implementations if no
        # faster versions are provided.
        if hline is None:
            self.hline = self._slow_hline
        else:
            self.hline = hline
        if vline is None:
            self.vline = self._slow_vline
        else:
            self.vline = vline
        if fill_rect is None:
            self.fill_rect = self._fill_rect
        else:
            self.fill_rect = fill_rect
        if text is None:
            self.text = self._very_slow_text
            # if no supplied font set to std
            if font is None:
                from adafruit_gfx.fonts.gfx_standard_font_01 import (  # pylint: disable=import-outside-toplevel
                    text_dict as std_font,
                )

                self.font = std_font
                self.set_text_background()
            else:
                self.font = font
                if not isinstance(self.font, dict):
                    raise ValueError(
                        "Font definitions must be contained in a dictionary object."
                    )
                del self.set_text_background

        else:
            self.text = text

    def pixel(self, x0, y0, *args, **kwargs):
        """A function to pass through in input pixel functionality."""
        # This was added to mainitatn the abstraction between gfx and the dislay library
        self._pixel(x0, y0, *args, **kwargs)

    def _slow_hline(self, x0, y0, width, *args, **kwargs):
        """Slow implementation of a horizontal line using pixel drawing.
        This is used as the default horizontal line if no faster override
        is provided."""
        if y0 < 0 or y0 > self.height or x0 < -width or x0 > self.width:
            return
        for i in range(width):
            self._pixel(x0 + i, y0, *args, **kwargs)

    def _slow_vline(self, x0, y0, height, *args, **kwargs):
        """Slow implementation of a vertical line using pixel drawing.
        This is used as the default vertical line if no faster override
        is provided."""
        if y0 < -height or y0 > self.height or x0 < 0 or x0 > self.width:
            return
        for i in range(height):
            self._pixel(x0, y0 + i, *args, **kwargs)

    def rect(self, x0, y0, width, height, *args, **kwargs):
        """Rectangle drawing function.  Will draw a single pixel wide rectangle
        starting in the upper left x0, y0 position and width, height pixels in
        size."""
        if y0 < -height or y0 > self.height or x0 < -width or x0 > self.width:
            return
        self.hline(x0, y0, width, *args, **kwargs)
        self.hline(x0, y0 + height - 1, width, *args, **kwargs)
        self.vline(x0, y0, height, *args, **kwargs)
        self.vline(x0 + width - 1, y0, height, *args, **kwargs)

    def _fill_rect(self, x0, y0, width, height, *args, **kwargs):
        """Filled rectangle drawing function.  Will draw a single pixel wide
        rectangle starting in the upper left x0, y0 position and width, height
        pixels in size."""
        if y0 < -height or y0 > self.height or x0 < -width or x0 > self.width:
            return
        for i in range(x0, x0 + width):
            self.vline(i, y0, height, *args, **kwargs)

    def line(self, x0, y0, x1, y1, *args, **kwargs):
        """Line drawing function.  Will draw a single pixel wide line starting at
        x0, y0 and ending at x1, y1."""
        steep = abs(y1 - y0) > abs(x1 - x0)
        if steep:
            x0, y0 = y0, x0
            x1, y1 = y1, x1
        if x0 > x1:
            x0, x1 = x1, x0
            y0, y1 = y1, y0
        dx = x1 - x0
        dy = abs(y1 - y0)
        err = dx // 2
        ystep = 0
        if y0 < y1:
            ystep = 1
        else:
            ystep = -1
        while x0 <= x1:
            if steep:
                self._pixel(y0, x0, *args, **kwargs)
            else:
                self._pixel(x0, y0, *args, **kwargs)
            err -= dy
            if err < 0:
                y0 += ystep
                err += dx
            x0 += 1

    def circle(self, x0, y0, radius, *args, **kwargs):
        """Circle drawing function.  Will draw a single pixel wide circle with
        center at x0, y0 and the specified radius."""
        f = 1 - radius
        ddF_x = 1
        ddF_y = -2 * radius
        x = 0
        y = radius
        self._pixel(x0, y0 + radius, *args, **kwargs)  # bottom
        self._pixel(x0, y0 - radius, *args, **kwargs)  # top
        self._pixel(x0 + radius, y0, *args, **kwargs)  # right
        self._pixel(x0 - radius, y0, *args, **kwargs)  # left
        while x < y:
            if f >= 0:
                y -= 1
                ddF_y += 2
                f += ddF_y
            x += 1
            ddF_x += 2
            f += ddF_x
            # angle notations are based on the unit circle and in diection of being drawn
            self._pixel(x0 + x, y0 + y, *args, **kwargs)  # 270 to 315
            self._pixel(x0 - x, y0 + y, *args, **kwargs)  # 270 to 255
            self._pixel(x0 + x, y0 - y, *args, **kwargs)  # 90 to 45
            self._pixel(x0 - x, y0 - y, *args, **kwargs)  # 90 to 135
            self._pixel(x0 + y, y0 + x, *args, **kwargs)  # 0 to 315
            self._pixel(x0 - y, y0 + x, *args, **kwargs)  # 180 to 225
            self._pixel(x0 + y, y0 - x, *args, **kwargs)  # 0 to 45
            self._pixel(x0 - y, y0 - x, *args, **kwargs)  # 180 to 135

    def fill_circle(self, x0, y0, radius, *args, **kwargs):
        """Filled circle drawing function.  Will draw a filled circle with
        center at x0, y0 and the specified radius."""
        self.vline(x0, y0 - radius, 2 * radius + 1, *args, **kwargs)
        f = 1 - radius
        ddF_x = 1
        ddF_y = -2 * radius
        x = 0
        y = radius
        while x < y:
            if f >= 0:
                y -= 1
                ddF_y += 2
                f += ddF_y
            x += 1
            ddF_x += 2
            f += ddF_x
            self.vline(x0 + x, y0 - y, 2 * y + 1, *args, **kwargs)
            self.vline(x0 + y, y0 - x, 2 * x + 1, *args, **kwargs)
            self.vline(x0 - x, y0 - y, 2 * y + 1, *args, **kwargs)
            self.vline(x0 - y, y0 - x, 2 * x + 1, *args, **kwargs)

    def triangle(self, x0, y0, x1, y1, x2, y2, *args, **kwargs):
        # pylint: disable=too-many-arguments
        """Triangle drawing function.  Will draw a single pixel wide triangle
        around the points (x0, y0), (x1, y1), and (x2, y2)."""
        self.line(x0, y0, x1, y1, *args, **kwargs)
        self.line(x1, y1, x2, y2, *args, **kwargs)
        self.line(x2, y2, x0, y0, *args, **kwargs)

    def fill_triangle(self, x0, y0, x1, y1, x2, y2, *args, **kwargs):
        # pylint: disable=too-many-arguments, too-many-locals, too-many-statements, too-many-branches
        """Filled triangle drawing function.  Will draw a filled triangle around
        the points (x0, y0), (x1, y1), and (x2, y2)."""
        if y0 > y1:
            y0, y1 = y1, y0
            x0, x1 = x1, x0
        if y1 > y2:
            y2, y1 = y1, y2
            x2, x1 = x1, x2
        if y0 > y1:
            y0, y1 = y1, y0
            x0, x1 = x1, x0
        a = 0
        b = 0
        y = 0
        last = 0
        if y0 == y2:
            a = x0
            b = x0
            if x1 < a:
                a = x1
            elif x1 > b:
                b = x1
            if x2 < a:
                a = x2
            elif x2 > b:
                b = x2
            self.hline(a, y0, b - a + 1, *args, **kwargs)
            return
        dx01 = x1 - x0
        dy01 = y1 - y0
        dx02 = x2 - x0
        dy02 = y2 - y0
        dx12 = x2 - x1
        dy12 = y2 - y1
        if dy01 == 0:
            dy01 = 1
        if dy02 == 0:
            dy02 = 1
        if dy12 == 0:
            dy12 = 1
        sa = 0
        sb = 0
        if y1 == y2 or y0 == y1:  # pylint: disable=consider-using-in
            last = y1
        else:
            last = y1 - 1
        for y in range(y0, last + 1):
            a = x0 + sa // dy01
            b = x0 + sb // dy02
            sa += dx01
            sb += dx02
            if a > b:
                a, b = b, a
            self.hline(a, y, b - a + 1, *args, **kwargs)
        sa = dx12 * (y - y1)
        sb = dx02 * (y - y0)
        while y <= y2:
            a = x1 + sa // dy12
            b = x0 + sb // dy02
            sa += dx12
            sb += dx02
            if a > b:
                a, b = b, a
            self.hline(a, y, b - a + 1, *args, **kwargs)
            y += 1

    def round_rect(self, x0, y0, width, height, radius, *args, **kwargs):
        """Rectangle with rounded corners drawing function.
        This works like a regular rect though! if radius = 0
        Will draw the outline of a rectangle with rounded corners with (x0,y0) at the top left"""
        # shift to correct for start point location
        x0 += radius
        y0 += radius

        # ensure that the radius will only ever half of the shortest side or less
        radius = int(min(radius, width / 2, height / 2))

        if radius:
            f = 1 - radius
            ddF_x = 1
            ddF_y = -2 * radius
            x = 0
            y = radius
            self.vline(
                x0 - radius, y0, height - 2 * radius + 1, *args, **kwargs
            )  # left
            self.vline(
                x0 + width - radius, y0, height - 2 * radius + 1, *args, **kwargs
            )  # right
            self.hline(
                x0, y0 + height - radius + 1, width - 2 * radius + 1, *args, **kwargs
            )  # bottom
            self.hline(x0, y0 - radius, width - 2 * radius + 1, *args, **kwargs)  # top
            while x < y:
                if f >= 0:
                    y -= 1
                    ddF_y += 2
                    f += ddF_y
                x += 1
                ddF_x += 2
                f += ddF_x
                # angle notations are based on the unit circle and in diection of being drawn

                # top left
                self._pixel(x0 - y, y0 - x, *args, **kwargs)  # 180 to 135
                self._pixel(x0 - x, y0 - y, *args, **kwargs)  # 90 to 135
                # top right
                self._pixel(
                    x0 + x + width - 2 * radius, y0 - y, *args, **kwargs
                )  # 90 to 45
                self._pixel(
                    x0 + y + width - 2 * radius, y0 - x, *args, **kwargs
                )  # 0 to 45
                # bottom right
                self._pixel(
                    x0 + y + width - 2 * radius,
                    y0 + x + height - 2 * radius,
                    *args,
                    **kwargs,
                )  # 0 to 315
                self._pixel(
                    x0 + x + width - 2 * radius,
                    y0 + y + height - 2 * radius,
                    *args,
                    **kwargs,
                )  # 270 to 315
                # bottom left
                self._pixel(
                    x0 - x, y0 + y + height - 2 * radius, *args, **kwargs
                )  # 270 to 255
                self._pixel(
                    x0 - y, y0 + x + height - 2 * radius, *args, **kwargs
                )  # 180 to 225

    def fill_round_rect(self, x0, y0, width, height, radius, *args, **kwargs):
        """Filled circle drawing function.  Will draw a filled circle with
        center at x0, y0 and the specified radius."""
        # shift to correct for start point location
        x0 += radius
        y0 += radius

        # ensure that the radius will only ever half of the shortest side or less
        radius = int(min(radius, width / 2, height / 2))

        self.fill_rect(
            x0, y0 - radius, width - 2 * radius + 2, height + 2, *args, **kwargs
        )

        if radius:
            f = 1 - radius
            ddF_x = 1
            ddF_y = -2 * radius
            x = 0
            y = radius
            while x < y:
                if f >= 0:
                    y -= 1
                    ddF_y += 2
                    f += ddF_y
                x += 1
                ddF_x += 2
                f += ddF_x
                # part notation starts with 0 on left and 1 on right, and direction is noted
                # top left
                self.vline(
                    x0 - y, y0 - x, 2 * x + 1 + height - 2 * radius, *args, **kwargs
                )  # 0 to .25
                self.vline(
                    x0 - x, y0 - y, 2 * y + 1 + height - 2 * radius, *args, **kwargs
                )  # .5 to .25
                # top right
                self.vline(
                    x0 + x + width - 2 * radius,
                    y0 - y,
                    2 * y + 1 + height - 2 * radius,
                    *args,
                    **kwargs,
                )  # .5 to .75
                self.vline(
                    x0 + y + width - 2 * radius,
                    y0 - x,
                    2 * x + 1 + height - 2 * radius,
                    *args,
                    **kwargs,
                )  # 1 to .75

    def _place_char(self, x0, y0, char, size, *args, **kwargs):
        """A sub class used for placing a single character on the screen"""
        # pylint: disable=undefined-loop-variable
        arr = self.font[char]
        width = arr[0]
        height = arr[1]
        # extract the char section of the data
        data = arr[2:]
        for x in range(width):
            for y in range(height):
                bit = bool(data[x] & 2**y)
                # char pixel
                if bit:
                    self.fill_rect(
                        size * x + x0,
                        size * (height - y - 1) + y0,
                        size,
                        size,
                        *args,
                        **kwargs,
                    )
                # else background pixel
                else:
                    try:
                        self.fill_rect(
                            size * x + x0,
                            size * (height - y - 1) + y0,
                            size,
                            size,
                            *self.text_bkgnd_args,
                            **self.text_bkgnd_kwargs,
                        )
                    except TypeError:
                        pass
        del arr, width, height, data, x, y, x0, y0, char, size

    def _very_slow_text(self, x0, y0, string, size, *args, **kwargs):
        """A function to place text on the display.(temporary)
        to use special characters put "__" on either side of the desired characters.
        letter format:
        {'character_here' : bytearray(b',WIDTH,HEIGHT,right-most-data,
                                            more-bytes-here,left-most-data') ,}

        (replace the "," with backslashes!!)
        each byte:
                        | lower most bit(lowest on display)
                        V
                 x0110100
                  ^c
                  | top most bit (highest on display)"""

        x_roll = x0  # rolling x
        y_roll = y0  # rolling y

        # highest_height = 0#wrap
        sep_string = string.split("__")

        for chunk in sep_string:
            # print(chunk)
            try:
                self._place_char(x_roll, y_roll, chunk, size, *args, **kwargs)
                x_roll += size * self.font[chunk][0] + size
                # highest_height = max(highest_height, size*self.font[chunk][1] + 1) #wrap
            except KeyError:
                while chunk:
                    char = chunk[0]

                    # make sure something is sent even if not in font dict
                    try:
                        self._place_char(x_roll, y_roll, char, size, *args, **kwargs)
                    except KeyError:
                        self._place_char(
                            x_roll, y_roll, "?CHAR?", size, *args, **kwargs
                        )
                        char = "?CHAR?"

                    x_roll += size * self.font[char][0]

                    # gap between letters
                    try:
                        self.fill_rect(
                            x_roll,
                            y_roll,
                            size,
                            size * self.font[char][1],
                            *self.text_bkgnd_args,
                            **self.text_bkgnd_kwargs,
                        )
                    except TypeError:
                        pass

                    x_roll += size
                    # highest_height = max(highest_height, size*self.font[char][1] + 1) #wrap
                    chunk = chunk[1:]  # wrap
                    # if (x_roll >= self.width) or (chunk[0:2] == """\n"""): #wrap
                    # self._text(x0,y0+highest_height,"__".join(sep_string),size) #wrap
                    # print(highest_height) #wrap

    def set_text_background(self, *args, **kwargs):
        """A function to change the background color of text, input any and all color params.
        Run without any inputs to return to "clear" background
        """
        self.text_bkgnd_args = args
        self.text_bkgnd_kwargs = kwargs

    # pylint: enable=too-many-arguments
