#!/usr/bin/env python
import typer
from rich import print
from typing_extensions import Annotated
import numpy as np
import segno
from segno import DataOverflowError, writers
from typing import List
from enum import Enum



class Direction(str, Enum):
    LEFT_TO_RIGHT = "LTR"
    TOP_TO_BOTTOM = "TTB"
    RIGHT_TO_LEFT = "RTL"

# 5x5 block of bits that can be used to place decoration next to barcode.
class VisualMarker:
    size = 5

    def __init__(self, bits):
        # true means 1 bit, false means 0 bit (eg: 1 means black, 0 means white)
        self.bits = bits

    marker_dict = {
        'A': bytearray([
            0x00, 0x01, 0x01, 0x01, 0x00,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x01, 0x01, 0x01, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x01,
        ]),
        'B': bytearray([
            0x01, 0x01, 0x01, 0x01, 0x00,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x01, 0x01, 0x01, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x01, 0x01, 0x01, 0x00,
        ]),
        'C': bytearray([
            0x00, 0x01, 0x01, 0x01, 0x01,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x01, 0x01, 0x01, 0x01,
        ]),
        'D': bytearray([
            0x01, 0x01, 0x01, 0x01, 0x00,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x01, 0x01, 0x01, 0x00,
        ]),
        'E': bytearray([
            0x01, 0x01, 0x01, 0x01, 0x01,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x01, 0x01, 0x01, 0x01, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x01, 0x01, 0x01, 0x01, 0x01,
        ]),
        'F': bytearray([
            0x01, 0x01, 0x01, 0x01, 0x01,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x01, 0x01, 0x01, 0x01, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x00,
        ]),
        'G': bytearray([
            0x00, 0x01, 0x01, 0x01, 0x01,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x01, 0x00, 0x01, 0x01, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x00, 0x01, 0x01, 0x01, 0x01,
        ]),
        'H': bytearray([
            0x01, 0x00, 0x00, 0x00, 0x01,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x01, 0x01, 0x01, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x01,
        ]),
        'I': bytearray([
            0x00, 0x00, 0x01, 0x00, 0x00,  # preformatted
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
        ]),
        'J': bytearray([
            0x00, 0x00, 0x01, 0x00, 0x00,  # preformatted
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x01, 0x00, 0x00, 0x00,
        ]),
        'K': bytearray([
            0x01, 0x00, 0x00, 0x00, 0x01,  # preformatted
            0x01, 0x00, 0x00, 0x01, 0x00,
            0x01, 0x01, 0x01, 0x00, 0x00,
            0x01, 0x00, 0x00, 0x01, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x01,
        ]),
        'L': bytearray([
            0x01, 0x00, 0x00, 0x00, 0x00,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x01, 0x01, 0x01, 0x01, 0x01,
        ]),
        'M': bytearray([
            0x01, 0x01, 0x00, 0x01, 0x01,  # preformatted
            0x01, 0x00, 0x01, 0x00, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x01,
        ]),
        'N': bytearray([
            0x01, 0x00, 0x00, 0x00, 0x01,  # preformatted
            0x01, 0x01, 0x00, 0x00, 0x01,
            0x01, 0x00, 0x01, 0x00, 0x01,
            0x01, 0x00, 0x00, 0x01, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x01,
        ]),
        'O': bytearray([
            0x00, 0x01, 0x01, 0x01, 0x00,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x00, 0x01, 0x01, 0x01, 0x00,
        ]),
        'P': bytearray([
            0x01, 0x01, 0x01, 0x01, 0x00,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x01, 0x01, 0x01, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x00,
        ]),
        'Q': bytearray([
            0x00, 0x01, 0x01, 0x01, 0x00,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x00, 0x01, 0x00, 0x01,
            0x01, 0x00, 0x00, 0x01, 0x00,
            0x00, 0x01, 0x01, 0x00, 0x01,
        ]),
        'R': bytearray([
            0x01, 0x01, 0x01, 0x01, 0x00,  # preformatted
            0x01, 0x00, 0x00, 0x01, 0x00,
            0x01, 0x01, 0x01, 0x00, 0x00,
            0x01, 0x00, 0x00, 0x01, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x01,
        ]),
        'S': bytearray([
            0x00, 0x01, 0x01, 0x01, 0x01,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x01, 0x01, 0x01, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x01, 0x01, 0x01, 0x00,
        ]),
        'T': bytearray([
            0x01, 0x01, 0x01, 0x01, 0x01,  # preformatted
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
        ]),
        'U': bytearray([
            0x01, 0x00, 0x00, 0x00, 0x01,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x00, 0x01, 0x01, 0x01, 0x00,
        ]),
        'V': bytearray([
            0x01, 0x00, 0x00, 0x00, 0x01,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x00, 0x01, 0x00, 0x01, 0x00,
            0x00, 0x01, 0x01, 0x01, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
        ]),
        'W': bytearray([
            0x01, 0x00, 0x00, 0x00, 0x01,  # preformatted
            0x01, 0x00, 0x01, 0x00, 0x01,
            0x01, 0x00, 0x01, 0x00, 0x01,
            0x00, 0x01, 0x01, 0x01, 0x00,
            0x00, 0x01, 0x00, 0x01, 0x00,
        ]),
        'X': bytearray([
            0x01, 0x00, 0x00, 0x00, 0x01,  # preformatted
            0x00, 0x01, 0x00, 0x01, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x01, 0x00, 0x01, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x01,
        ]),
        'Y': bytearray([
            0x01, 0x00, 0x00, 0x00, 0x01,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x00, 0x01, 0x01, 0x01, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
        ]),
        'Z': bytearray([
            0x01, 0x01, 0x01, 0x01, 0x01,  # preformatted
            0x00, 0x00, 0x00, 0x01, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x01, 0x00, 0x00, 0x00,
            0x01, 0x01, 0x01, 0x01, 0x01,
        ]),
        '0': bytearray([
            0x00, 0x00, 0x01, 0x00, 0x00,  # preformatted
            0x00, 0x01, 0x00, 0x01, 0x00,
            0x00, 0x01, 0x00, 0x01, 0x00,
            0x00, 0x01, 0x00, 0x01, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
        ]),
        '1': bytearray([
            0x00, 0x00, 0x01, 0x00, 0x00,  # preformatted
            0x00, 0x01, 0x01, 0x00, 0x00,
            0x01, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
        ]),
        '2': bytearray([
            0x00, 0x01, 0x01, 0x01, 0x00,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x00, 0x00, 0x01, 0x01, 0x00,
            0x00, 0x01, 0x00, 0x00, 0x00,
            0x01, 0x01, 0x01, 0x01, 0x01,
        ]),
        '3': bytearray([
            0x01, 0x01, 0x01, 0x01, 0x00,  # preformatted
            0x00, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x01, 0x01, 0x01, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x01, 0x01, 0x01, 0x00,
        ]),
        '4': bytearray([
            0x01, 0x00, 0x00, 0x00, 0x00,  # preformatted
            0x01, 0x00, 0x00, 0x01, 0x00,
            0x01, 0x01, 0x01, 0x01, 0x01,
            0x00, 0x00, 0x00, 0x01, 0x00,
            0x00, 0x00, 0x00, 0x01, 0x00,
        ]),
        '5': bytearray([
            0x01, 0x01, 0x01, 0x01, 0x01,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x01, 0x01, 0x01, 0x01, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x01,
            0x01, 0x01, 0x01, 0x01, 0x00,
        ]),
        '6': bytearray([
            0x00, 0x01, 0x01, 0x01, 0x00,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x00,
            0x01, 0x01, 0x01, 0x01, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x00, 0x01, 0x01, 0x01, 0x00,
        ]),
        '7': bytearray([
            0x01, 0x01, 0x01, 0x01, 0x01,  # preformatted
            0x00, 0x00, 0x00, 0x01, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x01, 0x00, 0x00, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x00,
        ]),
        '8': bytearray([
            0x00, 0x01, 0x01, 0x01, 0x00,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x00, 0x01, 0x01, 0x01, 0x00,
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x00, 0x01, 0x01, 0x01, 0x00,
        ]),
        '9': bytearray([
            0x00, 0x01, 0x01, 0x01, 0x00,  # preformatted
            0x01, 0x00, 0x00, 0x00, 0x01,
            0x00, 0x01, 0x01, 0x01, 0x01,
            0x00, 0x00, 0x00, 0x00, 0x01,
            0x00, 0x01, 0x01, 0x01, 0x00,
        ]),
        '.': bytearray([
            0x00, 0x00, 0x00, 0x00, 0x00,  # preformatted
            0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00,
        ]),
        ',': bytearray([
            0x00, 0x00, 0x00, 0x00, 0x00,  # preformatted
            0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
        ]),
        ':': bytearray([
            0x00, 0x00, 0x00, 0x00, 0x00,  # preformatted
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
        ]),
        ';': bytearray([
            0x00, 0x00, 0x00, 0x00, 0x00,  # preformatted
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x00,
        ]),
        '-': bytearray([
            0x00, 0x00, 0x00, 0x00, 0x00,  # preformatted
            0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x01, 0x01, 0x01, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00,
        ])
    }
    
    def generate_from_text(chars: str, direction:Direction, size_in_modules: int):
        # fill up with "."
        squares_cnt = (size_in_modules + 1) // (VisualMarker.size+1)
        leftover = (size_in_modules + 1) % (VisualMarker.size+1)
        if len(chars) < squares_cnt:
            chars = chars + '.' * (squares_cnt - len(chars))
        result: List[bytearray] = []
        for char in chars[:squares_cnt]:
            if char in VisualMarker.marker_dict:
                marker = VisualMarker.marker_dict[char]
            else:
                marker = VisualMarker.marker_dict['.']
            result.append(marker)
        return VisualMarker.generate_from_squares(result, direction, leftover)

    def generate_from_squares(marker_squares: List[bytearray], direction:Direction, padding_at_end: int):
        # spacer is a column of zeros if RTL/LTR, or a row if TTB/BTT
        axis = 0 if direction == Direction.TOP_TO_BOTTOM else 1
        spacer_dim = (1, VisualMarker.size) if axis==0 else (VisualMarker.size, 1)
        spacer = np.zeros(spacer_dim, dtype=np.uint8)
        result = np.array([])
        for marker in marker_squares:
            if len(marker) != VisualMarker.size*VisualMarker.size:
                raise ValueError("All markers must be 5x5")

            np_marker = np.array(marker).reshape(VisualMarker.size, VisualMarker.size)
            if result.size == 0:
                # the first item doesn't need a spacer
                result = np_marker
            else:
                # append in direction specified
                if direction == Direction.RIGHT_TO_LEFT:
                    result = np.concatenate((np_marker, spacer, result), axis=1)
                else:
                    result = np.concatenate((result, spacer, np_marker), axis=axis)
        if(padding_at_end > 0):
            padding_dim = (padding_at_end, VisualMarker.size) if axis==0 else (VisualMarker.size, padding_at_end)
            padding= np.zeros(padding_dim, dtype=np.uint8)

            # append in direction specified
            if direction == Direction.RIGHT_TO_LEFT:
                result = np.concatenate((padding, result), axis=1)
            else:
                result = np.concatenate((result, padding), axis=axis)
        return result


app = typer.Typer()


def _generate_qr_with_markers(qr_str, text, title, direction): 
    if title:
    #try to use standard size 10. Go bigger if 10 does not fit the data
        try:
            qr = segno.make_qr(qr_str, error="L", version=10)
        except DataOverflowError:
            qr = segno.make_qr(qr_str, error="L")
    else: 
        qr = segno.make_qr(qr_str, error="L")
    
    if(qr.mode != "alphanumeric"):
        print("[bold yellow]Large QR:[/bold yellow] Provided URL is not alphanumeric!")
    block_count = len(qr.matrix)
    print(f"[bold]Size:[/bold] {block_count}")
    print(f"[bold]Version:[/bold] {qr.version}")
    print(f"[bold]Error Level:[/bold] {qr.error}")
    
    
    qr_matrix = np.array(qr.matrix)
    visual_marker = VisualMarker.generate_from_text(text.upper(), direction, block_count)
    if title:
        title_marker = VisualMarker.generate_from_text(title.upper(), direction, block_count)
    
    append_axis = 1 if direction == Direction.TOP_TO_BOTTOM else 0
    padding_dim = (4, block_count) if append_axis==0 else (block_count, qr.default_border_size)
    padding= np.zeros(padding_dim, dtype=np.uint8)
    if title:
        combined_matrix = np.concatenate((title_marker, padding, qr_matrix, padding, visual_marker), axis=append_axis)
    else:
        combined_matrix = np.concatenate((qr_matrix, padding, visual_marker), axis=append_axis)
        
    return combined_matrix

def save_qr_with_markers(url, text="PAC", title=None, direction = Direction.LEFT_TO_RIGHT, fmt='png', path='qr'):
    combined_matrix = _generate_qr_with_markers(url, text="PAC", title=None, direction = Direction.LEFT_TO_RIGHT)
    outfile = f'{path}.{fmt}'
    match fmt:
        case 'png':
            writers.write_png(combined_matrix, combined_matrix.shape[::-1], out=outfile, border=9)
        case 'svg':
            writers.write_svg(combined_matrix, combined_matrix.shape[::-1], out=outfile, border=9)
            



def main(url: Annotated[str, typer.Argument(help="The PAC-ID to be rendered as QR.")],
    outfile: Annotated[typer.FileBinaryWrite, typer.Option(help="The file the qr will be written to.")] = "qr.svg",
    text: Annotated[str, typer.Option(help="The text of the PAC decoration.")] = "PAC",
    direction: Annotated[Direction, typer.Option(help="The position/direction of the PAC decoration.")] = Direction.TOP_TO_BOTTOM):

    save_qr_with_markers(url, text=text, direction=direction, path=outfile) 
    


if __name__ == "__main__":
    typer.run(main)

