#!/usr/bin/env python3
"""Generate Inno Setup wizard artwork from the app's own brand assets.

Everything here is derived from frontend/public/*.svg so the installer and the
running application are visibly the same product. Nothing is redrawn by hand.

Inno stretches artwork to fit and does not resample well, so render at the exact
sizes it asks for and supply the 125%/250% variants for high-DPI displays.

  WizardImageFile       164x314, 192x386, 384x772
  WizardSmallImageFile   55x55,  64x64,  138x138
  SetupIconFile          .ico with 16/24/32/48/64/128/256

Usage: python3 make-branding.py [output-dir]
"""
import io
import sys
from pathlib import Path

import cairosvg
from PIL import Image, ImageDraw, ImageFont

ASSETS = Path.home() / "projects/shopdb-flask/frontend/public"
OUT = Path(sys.argv[1] if len(sys.argv) > 1 else Path(__file__).parent)

# Sampled from the application's own palette so the installer does not look like
# a different product wearing the same badge.
NAVY = (10, 34, 74)       # deep base
BLUE = (16, 74, 150)      # GE blue
CYAN = (0, 158, 224)      # accent
WHITE = (255, 255, 255)
MUTED = (176, 197, 226)


def render_svg(name, width=None, height=None):
    png = cairosvg.svg2png(url=str(ASSETS / name), output_width=width, output_height=height)
    return Image.open(io.BytesIO(png)).convert("RGBA")


def recolour(img, colour):
    """Replace RGB while keeping the alpha mask. Source marks are dark-on-light;
    on a dark panel they must be inverted or they disappear."""
    solid = Image.new("RGBA", img.size, colour + (255,))
    solid.putalpha(img.getchannel("A"))
    return solid


def font(size, bold=False):
    for path in (
        f"/usr/share/fonts/truetype/dejavu/DejaVuSans{'-Bold' if bold else ''}.ttf",
        f"/usr/share/fonts/truetype/liberation/LiberationSans{'-Bold' if bold else '-Regular'}.ttf",
    ):
        if Path(path).exists():
            return ImageFont.truetype(path, size)
    return ImageFont.load_default()


def vertical_gradient(size, top, bottom):
    w, h = size
    img = Image.new("RGB", size)
    draw = ImageDraw.Draw(img)
    for y in range(h):
        t = y / max(1, h - 1)
        # Ease the ramp so the middle does not look flat.
        t = t * t * (3 - 2 * t)
        draw.line(
            [(0, y), (w, y)],
            fill=tuple(int(top[i] + (bottom[i] - top[i]) * t) for i in range(3)),
        )
    return img


def banner(w, h):
    img = vertical_gradient((w, h), BLUE, NAVY)
    draw = ImageDraw.Draw(img)
    k = w / 164.0                      # scale factor from the 100% design

    # Faint diagonal wash: stops the flat area under the text reading as empty.
    glow = Image.new("RGBA", (w, h), (0, 0, 0, 0))
    gd = ImageDraw.Draw(glow)
    gd.polygon([(0, int(h * 0.52)), (w, int(h * 0.30)), (w, h), (0, h)],
               fill=(255, 255, 255, 10))
    img = Image.alpha_composite(img.convert("RGBA"), glow).convert("RGB")
    draw = ImageDraw.Draw(img)

    margin = int(22 * k)

    # GE Aerospace wordmark at the top, above the product name - the corporate
    # mark leads, the product sits under it. (Previously the bare monogram was
    # here and the wordmark was stranded at the bottom.)
    mark_w = w - (margin * 2)
    mark = render_svg("ge-aerospace-logo.svg", mark_w, int(mark_w * 32 / 138))
    mark = recolour(mark, WHITE)
    img.paste(mark, (margin, int(34 * k)), mark)

    # Product name, directly beneath it.
    y = int(34 * k) + mark.height + int(30 * k)
    draw.text((margin, y), "ShopDB", font=font(int(26 * k), bold=True), fill=WHITE)
    y += int(31 * k)

    # Hairline rule, then the descriptor. Cheap way to look considered.
    draw.rectangle([margin, y, margin + int(30 * k), y + max(1, int(2 * k))], fill=CYAN)
    y += int(14 * k)
    for line in ("Asset management", "for the shop floor"):
        draw.text((margin, y), line, font=font(int(10.5 * k)), fill=MUTED)
        y += int(15 * k)

    # Accent bar flush to the bottom edge.
    bar = max(2, int(4 * k))
    draw.rectangle([0, h - bar, w, h], fill=CYAN)
    return img


def small(size):
    """Header mark on every page after the welcome page. White plate so it sits
    correctly on the wizard's own header, in light or dark mode."""
    img = Image.new("RGB", (size, size), WHITE)
    m = int(size * 0.80)
    mono = recolour(render_svg("ge-monogram.svg", m, m), BLUE)
    off = (size - m) // 2
    img.paste(mono, (off, off), mono)
    return img


def icon(path):
    """Installer icon. Rounded navy tile with the monogram, so it reads at 16px
    instead of turning into mush."""
    base = 256
    img = Image.new("RGBA", (base, base), (0, 0, 0, 0))
    d = ImageDraw.Draw(img)
    d.rounded_rectangle([0, 0, base - 1, base - 1], radius=int(base * 0.22), fill=BLUE + (255,))
    d.rounded_rectangle([0, 0, base - 1, int(base * 0.5)], radius=int(base * 0.22),
                        fill=(30, 96, 175, 255))
    d.rounded_rectangle([0, int(base * 0.3), base - 1, base - 1], radius=int(base * 0.22),
                        fill=BLUE + (255,))
    m = int(base * 0.62)
    mono = recolour(render_svg("ge-monogram.svg", m, m), WHITE)
    img.paste(mono, ((base - m) // 2, (base - m) // 2), mono)
    img.save(path, sizes=[(16, 16), (24, 24), (32, 32), (48, 48), (64, 64),
                          (128, 128), (256, 256)])


def main():
    OUT.mkdir(parents=True, exist_ok=True)
    made = []
    for w, h, name in [(164, 314, "wizard-image.bmp"),
                       (192, 386, "wizard-image@125.bmp"),
                       (384, 772, "wizard-image@250.bmp")]:
        banner(w, h).save(OUT / name, "BMP"); made.append((name, f"{w}x{h}"))
    for s, name in [(55, "wizard-small.bmp"), (64, "wizard-small@125.bmp"),
                    (138, "wizard-small@250.bmp")]:
        small(s).save(OUT / name, "BMP"); made.append((name, f"{s}x{s}"))
    icon(OUT / "shopdb.ico"); made.append(("shopdb.ico", "multi-size"))
    for name, dims in made:
        print(f"  {name:<26} {dims:<10} {(OUT / name).stat().st_size // 1024} KB")


if __name__ == "__main__":
    main()
