import qrcode
import textwrap
from string import ascii_letters

from PIL import ImageFont, ImageDraw, Image as PILImage

from labeler.domain.objects import Image, LabelDefinition
from labeler.interfaces import Renderer


DPI = 360.0


def points_to_pixels(point_size: float):
    return int(point_size * (72 / DPI))


class PILRenderer(Renderer):
    def __init__(self):
        self.font_path = "/Library/Fonts/Arial Unicode.ttf"

    def render_label(self, label_definition: LabelDefinition) -> Image:
        if label_definition.length is None:
            pil_image = self.__render_no_fixed_lenth(label_definition)
        else:
            pil_image = self.__render_fixed_length(label_definition)

        return Image.from_pil(pil_image)

    def __render_fixed_length(self, label_definition: LabelDefinition):
        width = label_definition.pixel_width
        length = label_definition.pixel_length
        font, text = self.__get_font(
            label_definition.text,
            width,
            length,
        )
        im = PILImage.new("1", (length, width), 1)
        draw = ImageDraw.Draw(im)
        draw.text(
            (length / 2, width / 2),
            text,
            font=font,
            fill=0,
            anchor="mm",
            align="center",
        )
        return im

    def __render_no_fixed_lenth(self, label_definition: LabelDefinition):
        lines_to_print = label_definition.text.count("\n") + 1
        text = "\n".join([line.strip() for line in label_definition.text.split("\n")])

        text_height = label_definition.pixel_width // lines_to_print

        while text_height > 0:
            font = ImageFont.truetype(
                "/Library/Fonts/Arial Unicode.ttf",
                text_height,
            )
            if lines_to_print > 1:
                occupied_height = font.getsize_multiline(text)[1]
            else:
                occupied_height = font.getsize(text)[1]
            if occupied_height <= label_definition.pixel_width:
                break
            text_height -= 1

        sizes = [font.getsize(line) for line in text.split("\n")]

        length = max(length for length, height in sizes)

        im = PILImage.new("1", (length, label_definition.pixel_width), 1)
        draw = ImageDraw.Draw(im)
        draw.text(
            (length / 2, label_definition.pixel_width / 2),
            text,
            font=font,
            fill=0,
            anchor="mm",
            align="center",
        )
        return im

    def render_qrcode(self, label_definition: LabelDefinition) -> Image:
        width = label_definition.pixel_width
        length = label_definition.pixel_length
        text = label_definition.text
        font = self.__get_font_for_qr()
        qr = qrcode.QRCode(box_size=7)
        qr.add_data(f"https://hs3.pl/db/{text}")
        qr.make(fit=True)
        qr_img = qr.make_image()
        print(width, length)
        pil_image = PILImage.new("1", (width, length), 1)
        draw = ImageDraw.Draw(pil_image)
        pil_image.paste(qr_img)
        draw.text(
            (width / 2, 232),
            "HS3-DB",
            font=font,
            fill=0,
            anchor="mm",
            align="center",
        )
        draw.text(
            (20, 232 + 34),
            f"ID: {text}",
            font=font,
            fill=0,
            align="left",
        )

        return Image.from_pil(pil_image.transpose(PILImage.Transpose.ROTATE_90))

    def __get_font_for_qr(self):
        font_size = int(360 / 9)

        return ImageFont.truetype("fonts/SourceCodePro-SemiBold.ttf", font_size)

    def __get_font(self, text: str, max_width: int, max_length: int):
        font_size = max_width
        step = max_width // 2
        last_good = None
        last_corrected = None
        while step > 1:
            fits, corrected = self.__will_font_fit(
                text, self.font_path, font_size, max_width, max_length
            )
            if fits:
                last_good = font_size
                last_corrected = corrected
                font_size += step
            else:
                font_size -= step
            step //= 2

        return ImageFont.truetype(self.font_path, last_good), last_corrected

    def __will_font_fit(
        self, text: str, font_path: str, font_size: int, max_width: int, max_length: int
    ):
        font = ImageFont.truetype(font_path, font_size)
        if "\n" in text:
            text_width, text_height = font.getsize_multiline(text)
        else:
            text_width, text_height = font.getsize(text)

        if text_height > max_width:
            return False, None

        if text_width <= max_length:
            # Now we know that the text fits. We can stop trying
            return True, text

        avg_char_width = sum(font.getsize(char)[0] for char in ascii_letters) / len(
            ascii_letters
        )
        charachters_per_line = max_length // avg_char_width
        if charachters_per_line < max(len(line) for line in text.split(" ")):
            return False, None

        wrapped = textwrap.fill(text, charachters_per_line)
        wrapped_width, wrapped_height = font.getsize_multiline(wrapped)
        if wrapped_height <= max_width and wrapped_width <= max_length:
            # Now we know that the text fits. We can stop trying
            return True, wrapped

        return False, None
