#!/usr/bin/env python3
"""Apply the original San Marco Vision logo and exact microcopy to V3 image bases."""

from __future__ import annotations

import argparse
import json
from pathlib import Path

import numpy as np
from PIL import Image, ImageDraw, ImageFont


ROOT = Path(__file__).resolve().parents[1]
DEFAULT_FONT = Path("/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf")


def crop_logo(path: Path) -> Image.Image:
    image = Image.open(path).convert("RGBA")
    rgb = np.asarray(image)[..., :3]
    visible = np.any(rgb < 248, axis=2)
    ys, xs = np.where(visible)
    if not len(xs):
        raise ValueError(f"Logo without visible pixels: {path}")
    return image.crop((int(xs.min()), int(ys.min()), int(xs.max()) + 1, int(ys.max()) + 1))


def perspective_coefficients(destination, source):
    matrix = []
    target = []
    for (dx, dy), (sx, sy) in zip(destination, source):
        matrix.append([dx, dy, 1, 0, 0, 0, -sx * dx, -sx * dy])
        matrix.append([0, 0, 0, dx, dy, 1, -sy * dx, -sy * dy])
        target.extend([sx, sy])
    return np.linalg.solve(np.asarray(matrix, dtype=float), np.asarray(target, dtype=float))


def denormalize_quad(quad, width: int, height: int):
    return [(float(x) * width, float(y) * height) for x, y in quad]


def warp_patch(patch: Image.Image, output_size, destination_quad) -> Image.Image:
    width, height = patch.size
    source = [(0, 0), (width, 0), (width, height), (0, height)]
    coeffs = perspective_coefficients(destination_quad, source)
    return patch.transform(
        output_size,
        Image.Transform.PERSPECTIVE,
        coeffs,
        resample=Image.Resampling.BICUBIC,
        fillcolor=(0, 0, 0, 0),
    )


def with_opacity(image: Image.Image, opacity: float) -> Image.Image:
    if opacity >= 1:
        return image
    result = image.copy()
    alpha = result.getchannel("A").point(lambda value: round(value * opacity))
    result.putalpha(alpha)
    return result


def fit_font(text: str, max_width: int, max_height: int, font_path: Path) -> ImageFont.FreeTypeFont:
    size = max_height
    while size > 12:
        font = ImageFont.truetype(str(font_path), size=size)
        bounds = font.getbbox(text)
        if bounds[2] - bounds[0] <= max_width and bounds[3] - bounds[1] <= max_height:
            return font
        size -= 2
    return ImageFont.truetype(str(font_path), size=12)


def text_patch(text: str, color: str, background=None) -> Image.Image:
    size = (1600, 260)
    fill = tuple(background) if background else (0, 0, 0, 0)
    patch = Image.new("RGBA", size, fill)
    draw = ImageDraw.Draw(patch)
    font = fit_font(text, 1480, 190, DEFAULT_FONT)
    bounds = draw.textbbox((0, 0), text, font=font)
    text_width = bounds[2] - bounds[0]
    text_height = bounds[3] - bounds[1]
    draw.text(((size[0] - text_width) / 2, (size[1] - text_height) / 2 - bounds[1]), text, font=font, fill=color)
    return patch


def composite_entry(entry, logo: Image.Image):
    base_path = ROOT / entry["base"]
    output_path = ROOT / entry["output"]
    output_path.parent.mkdir(parents=True, exist_ok=True)
    base = Image.open(base_path).convert("RGBA")
    width, height = base.size

    if entry.get("logo_quad"):
        quad = denormalize_quad(entry["logo_quad"], width, height)
        layer = warp_patch(logo, base.size, quad)
        base = Image.alpha_composite(base, with_opacity(layer, float(entry.get("logo_opacity", .96))))

    if entry.get("text") and entry.get("text_quad"):
        patch = text_patch(
            entry["text"].upper(),
            entry.get("text_color", "#0A2540"),
            entry.get("text_background"),
        )
        quad = denormalize_quad(entry["text_quad"], width, height)
        layer = warp_patch(patch, base.size, quad)
        base = Image.alpha_composite(base, with_opacity(layer, float(entry.get("text_opacity", .94))))

    base.convert("RGB").save(output_path, quality=96)
    return output_path


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--manifest", default="assets/img/services/source/v3-brand/brand-overlays.json")
    parser.add_argument("--logo", default="lavorare-assets/logo-01.png")
    args = parser.parse_args()

    manifest = json.loads((ROOT / args.manifest).read_text(encoding="utf-8"))
    logo = crop_logo(ROOT / args.logo)
    for entry in manifest["assets"]:
        print(composite_entry(entry, logo).relative_to(ROOT))


if __name__ == "__main__":
    main()
