#!/usr/bin/env python3

"""
Convert a sprite sheet image to python a module for use with indexed bitmap method. The Sprite sheet
width and height should be a multiple of sprite width and height. There should be no extra pixels
between sprites. All sprites will share the same palette.

.. seealso::
    - :ref:`tiny_toasters.py<tiny_toasters>`.

Example
^^^^^^^

.. code-block:: console

    # create a sprite sheet with 7 colored sprites 32x32 pixels each
    ./make_colorbars_bitmap.py 227 32 3 --png sprites.png

    # convert the sprite sheet to a python module with 7 sprites
    ./sprites_converter.py sprites.png 32 32 4 > sprites.py

.. code-block:: python

    import tft_config
    import sprites
    tft = tft_config.config(1)
    for i in range(sprites.BITMAPS):
        tft.bitmap(sprites, 0, 0, i)

Usage
^^^^^

.. code-block:: console

    usage: sprites_converter.py [-h] image_file sprite_width sprite_height bits_per_pixel

    Convert image file to python module for use with bitmap method.

    positional arguments:
    image_file      Name of file containing image to convert
    sprite_width    Width of sprites in pixels
    sprite_height   Height of sprites in pixels
    bits_per_pixel  The number of bits to use per pixel (1..8)

    optional arguments:
    -h, --help      show this help message and exit

"""

import sys
import argparse
from PIL import Image


def convert_image_to_bitmap(image_file, bits, sprite_width, sprite_height):
    """
    Convert image to bitmap representation.

    Args:
        img (PIL.Image.Image): Image object to convert.
        bits (int): The number of bits to use per pixel (1..8).
        sprite_width (int): Width of sprites in pixels.
        sprite_height (int): Height of sprites in pixels.

    Returns:
        tuple: Tuple containing the bitmap parameters and the bitmap data.

    Raises:
        None
    """
    colors_requested = 1 << bits
    img = Image.open(image_file).convert("RGB")
    img = img.convert(mode="P", palette=Image.Palette.ADAPTIVE, colors=colors_requested)

    palette = img.getpalette()
    palette_colors = len(palette) // 3
    actual_colors = min(palette_colors, colors_requested)

    colors = []
    for color in range(actual_colors):
        color565 = (
            ((palette[color * 3] & 0xF8) << 8)
            | ((palette[color * 3 + 1] & 0xFC) << 3)
            | ((palette[color * 3 + 2] & 0xF8) >> 3)
        )
        colors.append(f"{color565:04x}")

    image_bitstring = ""
    bitmaps = 0

    sprite_cols = img.width // sprite_width
    width_of_sprites = sprite_cols * sprite_width
    sprite_rows = img.height // sprite_height
    height_of_sprites = sprite_rows * sprite_height

    for y in range(0, height_of_sprites, sprite_height):
        for x in range(0, width_of_sprites, sprite_width):
            bitmaps += 1
            for yy in range(y, y + sprite_height):
                for xx in range(x, x + sprite_width):
                    try:
                        pixel = img.getpixel((xx, yy))
                    except IndexError:
                        print(
                            f"IndexError: xx={xx}, yy={yy} check your sprite width and height",
                            file=sys.stderr,
                        )
                        pixel = 0
                    color = pixel
                    image_bitstring += "".join(
                        "1" if (color & (1 << bit - 1)) else "0"
                        for bit in range(bits, 0, -1)
                    )

    bitmap_bits = len(image_bitstring)

    # Create python source with image parameters
    print(f"BITMAPS = {bitmaps}")
    print(f"HEIGHT = {sprite_height}")
    print(f"WIDTH = {sprite_width}")
    print(f"COLORS = {actual_colors}")
    print(f"BITS = {bitmap_bits}")
    print(f"BPP = {bits}")
    print("PALETTE = [", sep="", end="")

    for color, rgb in enumerate(colors):
        if color:
            print(",", sep="", end="")
        print(f"0x{rgb}", sep="", end="")
    print("]")

    # Run though image bit string 8 bits at a time
    # and create python array source for memoryview

    print("_bitmap =\\", sep="")
    print("b'", sep="", end="")

    for i in range(0, bitmap_bits, 8):
        if i and i % (16 * 8) == 0:
            print("'\\\nb'", end="", sep="")

        value = image_bitstring[i : i + 8]
        color = int(value, 2)
        print(f"\\x{color:02x}", sep="", end="")

    print("'\nBITMAP = memoryview(_bitmap)")


def main():
    """
    Convert images to python modules for use with indexed bitmap method.

    Args:
        input (str): image file to convert.
        sprite_width (int): Width of sprites in pixels.
        sprite_height (int): Height of sprites in pixels.
        bits_per_pixel (int): The number of color bits to use per pixel (1..8).

    """

    parser = argparse.ArgumentParser(
        description="Convert image file to python module for use with bitmap method.",
    )

    parser.add_argument("image_file", help="Name of file containing image to convert")
    parser.add_argument("sprite_width", type=int, help="Width of sprites in pixels")
    parser.add_argument("sprite_height", type=int, help="Height of sprites in pixels")

    parser.add_argument(
        "bits_per_pixel",
        type=int,
        choices=range(1, 9),
        default=1,
        metavar="bits_per_pixel",
        help="The number of bits to use per pixel (1..8)",
    )

    args = parser.parse_args()

    convert_image_to_bitmap(
        args.image_file, args.bits_per_pixel, args.sprite_width, args.sprite_height
    )


main()
