# SPDY-FileCopyrightText: 2012 jacksongabbard
# SPDX-FileCopyrightText: 2019 Dave Astels for Adafruit Industries
# SPDX-FileCopyrightText: 2021 Kevin Matocha, Jose David M.
#
# SPDX-License-Identifier: MIT
# SPDY-License-Identifier: Unlicense

"""
`wheel_maker`
================================================================================

Save a displayio.Bitmap (and associated displayio.Palette) in a BMP file.
This script is adapted in the works from Dave Astels on the ``adafruit_bitmapsaver``
and the works of  Jackson Glabbard
https://jg.gg/2012/05/28/generating-a-color-picker-style-color-wheel-in-python/
https://github.com/jacksongabbard/Python-Color-Gamut-Generator/blob/master/color-wheel-generator.py
and Kevin Matocha on the ``switch_round`` for the ``_color_to_tuple`` function

* Author(s): Dave Astels, Jackson Glabbard, Kevin Matocha, Jose David M.

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

**Hardware:**


**Software and Dependencies:**

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

"""

import math
import struct
import gc
import board
import digitalio
import busio

try:
    import adafruit_sdcard
    import storage
except ImportError:
    pass

# pylint: disable=invalid-name, no-member, too-many-locals


def _write_bmp_header(output_file, filesize):
    output_file.write(bytes("BM", "ascii"))
    output_file.write(struct.pack("<I", filesize))
    output_file.write(b"\00\x00")
    output_file.write(b"\00\x00")
    output_file.write(struct.pack("<I", 54))


def _bytes_per_row(source_width):
    pixel_bytes = 3 * source_width
    padding_bytes = (4 - (pixel_bytes % 4)) % 4
    return pixel_bytes + padding_bytes


def _write_dib_header(output_file, width, height):
    output_file.write(struct.pack("<I", 40))
    output_file.write(struct.pack("<I", width))
    output_file.write(struct.pack("<I", height))
    output_file.write(struct.pack("<H", 1))
    output_file.write(struct.pack("<H", 24))
    for _ in range(24):
        output_file.write(b"\x00")


def make_color(base, adj, ratio, shade):
    """
    Go through each bit of the colors adjusting blue with blue, red with red,
    green with green, etc.
    """
    output = 0x0
    bit = 0
    for pos in range(3):
        base_chan = color_wheel[base][pos]
        adj_chan = color_wheel[adj][pos]
        new_chan = int(round(base_chan * (1 - ratio) + adj_chan * ratio))

        # now alter the channel by the shade
        if shade < 1:
            new_chan = new_chan * shade
        elif shade > 1:
            shade_ratio = shade - 1
            new_chan = (0xFF * shade_ratio) + (new_chan * (1 - shade_ratio))

        output = output + (int(new_chan) << bit)
        bit = bit + 8
    return output


def color_to_tuple(value):
    """Converts a color from a 24-bit integer to a tuple.
    :param value: RGB LED desired value - can be a RGB tuple or a 24-bit integer.
    """
    if isinstance(value, tuple):
        return value
    if isinstance(value, int):
        if value >> 24:
            raise ValueError("Only bits 0->23 valid for integer input")
        r = value >> 16
        g = (value >> 8) & 0xFF
        b = value & 0xFF
        return [r, g, b]

    raise ValueError("Color must be a tuple or 24-bit integer value.")


color_wheel = [
    [0xFF, 0x00, 0xFF],
    [0xFF, 0x00, 0x00],
    [0xFF, 0xFF, 0x00],
    [0x00, 0xFF, 0x00],
    [0x00, 0xFF, 0xFF],
    [0x00, 0x00, 0xFF],
    [0xFF, 0x00, 0xFF],
]


def make_wheel(image_name, img_size, bg_color=0x000000):
    """
    :param image_name: Name of the ouput bitmap image
    :param img_size: size of the bitmap image in pixels height=width
    :param bg_color: color of the background in 24 bit format. Defaults 0x000000
    :return: color


    **Quickstart: Importing and using the make_wheel**

    Here is one way of importing the ``make_wheel`` module so you can use:

    .. code-block:: python

        from adafruit_displayio_color_picker import wheel_maker

    Now you can create a wheel of 200 pixels with a black background using:

    .. code-block:: python

        make_wheel("wheel200.bmp", 200 , 0x000000)


    """
    img_size_width = img_size
    img_size_height = img_size
    img_half = img_size / 2
    outer_radius = img_size // 2
    background_color = color_to_tuple(bg_color)
    row_buffer = bytearray(_bytes_per_row(img_size_width))
    result_buffer = bytearray(2048)

    spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
    cs = digitalio.DigitalInOut(board.SD_CS)
    sdcard = adafruit_sdcard.SDCard(spi, cs)
    vfs = storage.VfsFat(sdcard)
    storage.mount(vfs, "/sd")
    file_path = "/sd" + image_name
    print("saving starts")
    output_file = open(file_path, "wb")
    filesize = 54 + img_size_height * _bytes_per_row(img_size_width)
    _write_bmp_header(output_file, filesize)
    _write_dib_header(output_file, img_size_width, img_size_height)

    for y in range(img_size, 0, -1):
        buffer_index = 0
        for x in range(img_size):
            dist = abs(math.sqrt((x - img_half) ** 2 + (y - img_half) ** 2))
            shade = 1 * dist / outer_radius
            if x - img_half == 0:
                angle = -90
                if y > img_half:
                    angle = 90
            else:
                angle = math.atan2((y - img_half), (x - img_half)) * 180 / math.pi

            angle = (angle - 30) % 360

            idx = angle / 60
            if idx < 0:
                idx = 6 + idx
            base = int(round(idx))

            adj = (6 + base + (-1 if base > idx else 1)) % 6
            ratio = max(idx, base) - min(idx, base)
            color = make_color(base, adj, ratio, shade)

            if dist > outer_radius:
                color_rgb = background_color
            else:
                color_rgb = color_to_tuple(color)

            for b in color_rgb:
                row_buffer[buffer_index] = b & 0xFF
                buffer_index += 1
        output_file.write(row_buffer)
        for i in range(img_size_width * 2):
            result_buffer[i] = 0
        gc.collect()

    output_file.close()
    print("saving done")
