#!/usr/bin/env python3
"""
Generateur de la signature animee "Pac-Man SEO".
Pac-Man devore les vrais logos des moteurs (Google, Bing, ChatGPT, Claude,
Perplexity) rendus depuis les SVG -> PNG transparent (LOGOS ONLY, pas de fond).

Run: DYLD_LIBRARY_PATH=/opt/homebrew/lib .venv/bin/python build_gif.py
"""
import io, math
import cairosvg
from PIL import Image, ImageDraw, ImageFont

# ----------------------------------------------------------------------------
# Config
# ----------------------------------------------------------------------------
W, H        = 540, 104
BG          = (13, 17, 23)        # #0d1117 terminal
BAR         = (22, 27, 34)        # title bar
BORDER      = (33, 38, 45)
YELLOW      = (255, 215, 64)      # pac-man
GREY        = (139, 148, 158)

LOGO_H      = 38                  # hauteur uniforme des logos
ROW_Y       = 67                  # centre vertical de la rangee
SS          = 4                   # supersampling logos

# logo source -> couleur a injecter sur currentColor (None = garder couleurs)
LOGOS = [
    ("google-icon-1.svg", None),
    ("bing-1.svg",        None),
    ("chatgpt-6.svg",     "#e6edf3"),
    ("claude-logo.svg",   None),
    ("perplexity-3.svg",  "#20b8cd"),
]

# ----------------------------------------------------------------------------
# Rendu des logos
# ----------------------------------------------------------------------------
def render_logo(path, color, target_h):
    svg = open(path, encoding="utf-8").read()
    if color:
        svg = svg.replace("currentColor", color)
    big = cairosvg.svg2png(bytestring=svg.encode(),
                           output_height=target_h * SS * 3)
    im = Image.open(io.BytesIO(big)).convert("RGBA")
    im = im.crop(im.getbbox())                       # trim transparent margins
    ratio = (target_h * SS) / im.height
    im = im.resize((max(1, round(im.width * ratio)), target_h * SS),
                   Image.LANCZOS)
    im = im.resize((round(im.width / SS), target_h), Image.LANCZOS)
    return im

logos = [render_logo(p, c, LOGO_H) for p, c in LOGOS]

# positions horizontales (centres) reparties sur la largeur
x_start, x_end = 78, 470
centers = [round(x_start + (x_end - x_start) * i / (len(logos) - 1))
           for i in range(len(logos))]

# ----------------------------------------------------------------------------
# Fonts
# ----------------------------------------------------------------------------
def load_font(size, bold=False):
    cands = [
        "/System/Library/Fonts/SFNSMono.ttf",
        "/System/Library/Fonts/Menlo.ttc",
        "/Library/Fonts/Menlo.ttc",
    ]
    for c in cands:
        try:
            return ImageFont.truetype(c, size)
        except Exception:
            pass
    return ImageFont.load_default()

F_CMD   = load_font(17)
F_TITLE = load_font(12)
F_DONE  = load_font(16)

# ----------------------------------------------------------------------------
# Base frame (titlebar + prompt) — dessine une fois, copie a chaque frame
# ----------------------------------------------------------------------------
def base():
    img = Image.new("RGBA", (W, H), BG + (255,))
    d = ImageDraw.Draw(img)
    # title bar
    d.rectangle([0, 0, W, 30], fill=BAR + (255,))
    d.line([0, 30, W, 30], fill=BORDER + (255,))
    for i, col in enumerate([(255, 95, 86), (255, 189, 46), (39, 201, 63)]):
        cx = 18 + i * 18
        d.ellipse([cx - 6, 9, cx + 6, 21], fill=col + (255,))
    t = "seo@dev: ~/rankings"
    tw = d.textlength(t, font=F_TITLE)
    d.text(((W - tw) / 2, 9), t, font=F_TITLE, fill=GREY + (255,))
    return img

BASE = base()

# ----------------------------------------------------------------------------
# Pac-Man
# ----------------------------------------------------------------------------
def draw_pacman(d, cx, cy, r, mouth):
    """mouth: 0 ferme -> 1 grand ouvert. Ouvre vers la droite."""
    half = mouth * 32
    d.pieslice([cx - r, cy - r, cx + r, cy + r],
               start=half, end=360 - half, fill=YELLOW + (255,))
    # oeil
    ex, ey = cx - r * 0.15, cy - r * 0.45
    d.ellipse([ex - 2, ey - 2, ex + 2, ey + 2], fill=BG + (255,))

# ----------------------------------------------------------------------------
# Frames
# ----------------------------------------------------------------------------
frames, durations = [], []
R = 19
travel_start = 20
travel_end   = 500
STEPS = 70

def compose(pac_x, eaten_count, mouth):
    img = BASE.copy()
    d = ImageDraw.Draw(img)
    # logos restants
    for i, (lg, cx) in enumerate(zip(logos, centers)):
        if i < eaten_count:
            continue
        img.alpha_composite(lg, (round(cx - lg.width / 2),
                                  round(ROW_Y - lg.height / 2)))
    draw_pacman(d, pac_x, ROW_Y, R, mouth)
    return img.convert("RGB")

# entree + parcours mangeur
for s in range(STEPS):
    pac_x = travel_start + (travel_end - travel_start) * s / (STEPS - 1)
    # combien de logos depasses (centre du logo derriere la bouche)
    eaten = sum(1 for cx in centers if cx < pac_x - R * 0.3)
    mouth = (math.sin(s * 0.75) * 0.5 + 0.5)      # chomp continu, plus fluide
    frames.append(compose(pac_x, eaten, mouth))
    durations.append(45)

# pause finale : Pac-Man rassasie, bouche grande ouverte
for _ in range(6):
    frames.append(compose(travel_end, len(logos), 1.0))
    durations.append(80)

# ----------------------------------------------------------------------------
# Export — palette globale unique (frame 0 = tous les logos) + diff inter-frames
# ----------------------------------------------------------------------------
out = "seo_pacman.gif"
master = frames[0].convert("P", palette=Image.ADAPTIVE, colors=128)
pal_frames = [f.quantize(palette=master, dither=Image.NONE) for f in frames]
pal_frames[0].save(out, save_all=True, append_images=pal_frames[1:],
                   duration=durations, loop=0, optimize=True, disposal=1)

import os
print(f"{out}: {os.path.getsize(out)/1024:.1f} Ko, {len(frames)} frames")
