#!/usr/bin/env python3
"""Meme maker: classic (Impact, white with black outline, top / bottom), modern (white caption bar above the picture)
or label (small tags anywhere) memes on images, GIFs or videos.

Examples
  python meme.py --input https://.../cat.png --top "when the build passes" --bottom "on the first try"
  python meme.py --input reaction.mp4 --style modern --top "me explaining my side project to my family"
  python meme.py --input photo.jpg --style label --label "0.2,0.6=me" --label "0.75,0.4=new AI tool"
"""
from __future__ import annotations

import argparse
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
import kmedia as K  # noqa: E402


def overlay_for(W: int, H: int, a, pad_top: int = 0):
    """Return (overlay RGBA WxH', canvas height) for the chosen style."""
    from PIL import Image, ImageDraw
    if a.style == "modern":
        bar = max(int(W * 0.22), int(H * 0.2))
        f, lines, lh, bh = K.fit_text(a.top or a.bottom, int(W * 0.9), int(bar * 0.85), int(W * 0.075), 14, "regular", a.font, max_lines=4)
        bar = (max(bar, bh + int(W * 0.08)) + 1) // 2 * 2
        img = Image.new("RGBA", (W, H + bar), (0, 0, 0, 0))
        ImageDraw.Draw(img).rectangle((0, 0, W, bar), fill=(255, 255, 255, 255))
        K.draw_block(img, a.top or a.bottom, (int(W * 0.05), 0, int(W * 0.95), bar), fill=(17, 17, 17), align="left",
                     max_size=int(W * 0.075), min_size=14, kind="regular", font_override=a.font, max_lines=4)
        return img, bar
    img = Image.new("RGBA", (W, H), (0, 0, 0, 0))
    if a.style == "label":
        f = K.load_font(max(14, int(min(W, H) * 0.05)), "bold", a.font)
        d = ImageDraw.Draw(img)
        for lab in a.label:
            pos, _, text = lab.partition("=")
            x, y = (float(v) for v in pos.split(","))
            st = max(2, int(getattr(f, "size", 20) * 0.12))
            bb = d.textbbox((0, 0), text, font=f, stroke_width=st)
            tx, ty = int(W * x - (bb[2] - bb[0]) / 2), int(H * y - (bb[3] - bb[1]) / 2)
            d.text((tx, ty), text, font=f, fill=(255, 255, 255), stroke_width=st, stroke_fill=(0, 0, 0))
        return img, 0
    m = int(H * 0.03)
    bh = int(H * 0.26)
    for text, box in ((a.top, (int(W * 0.04), m, int(W * 0.96), m + bh)), (a.bottom, (int(W * 0.04), H - m - bh, int(W * 0.96), H - m))):
        if text:
            K.draw_block(img, text, box, fill=(255, 255, 255), valign="top" if box[1] == m else "bottom",
                         max_size=int(min(W, H) * 0.14), min_size=14, kind="impact", font_override=a.font,
                         stroke_ratio=0.07, upper=True, max_lines=3)
    return img, 0


def main() -> None:
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("--input", required=True, help="image, GIF or video (URL or file)")
    ap.add_argument("--top", default="")
    ap.add_argument("--bottom", default="")
    ap.add_argument("--style", choices=["classic", "modern", "label"], default="classic")
    ap.add_argument("--label", action="append", default=[], help="label style: 'x,y=text' with x,y in 0..1")
    K.add_font_args(ap)
    ap.add_argument("--max-width", type=int, default=1080)
    ap.add_argument("--out", default="", help="default kenerate-out/meme-maker/meme.<ext>")
    a = K.parse_args(ap)
    if not (a.top or a.bottom or a.label):
        K.fail("Nothing to write.", "Pass --top and/or --bottom (or --label for the label style).")

    st = K.Status(3, "meme-maker")
    tmp = K.workdir()
    st.step("Fetching input")
    kind = K.kind_of(a.input)
    K.require(ffmpeg=kind != "image", status=st)
    src = K.fetch(a.input, tmp, "input", st)
    ext = Path(src).suffix.lower()
    is_gif = ext == ".gif"
    kind = K.kind_of(str(src))
    out = Path(a.out or f"kenerate-out/meme-maker/meme{'.gif' if is_gif else ('.mp4' if kind == 'video' else '.jpg')}")
    out.parent.mkdir(parents=True, exist_ok=True)

    if kind == "image" and not is_gif:
        st.step(f"Rendering ({a.style})")
        from PIL import Image
        im = K.open_image(src).convert("RGB")
        if im.width > a.max_width:
            im = im.resize((a.max_width, int(im.height * a.max_width / im.width)), Image.LANCZOS)
        ov, bar = overlay_for(im.width, im.height, a)
        canvas = Image.new("RGBA", ov.size, (255, 255, 255, 255))
        canvas.paste(im, (0, bar))
        canvas.alpha_composite(ov)
        st.step("Saving")
        canvas.convert("RGB").save(out, quality=92)
    else:
        info = K.probe(src)
        W, H = info["width"], info["height"]
        if W > a.max_width:
            H, W = int(H * a.max_width / W) // 2 * 2, a.max_width
        W, H = W // 2 * 2, H // 2 * 2
        st.step(f"Rendering ({a.style}) over {W}x{H} {'GIF' if is_gif else 'video'}")
        ov, bar = overlay_for(W, H, a)
        oh = (H + bar) // 2 * 2
        png = tmp / "overlay.png"
        ov.save(png)
        pad = f",pad={W}:{oh}:0:{bar}:white" if bar else ""
        graph = f"[0:v]scale={W}:{H}:flags=lanczos,setsar=1{pad}[b];[b][1:v]overlay=0:0"
        st.step("Encoding")
        if is_gif:
            graph += ",split[x][y];[x]palettegen=max_colors=192[p];[y][p]paletteuse=dither=sierra2_4a[o]"
            K.run_ffmpeg(["-i", str(src), "-i", str(png), "-filter_complex", graph, "-map", "[o]", "-loop", "0", str(out)],
                         duration=info["duration"], status=st)
        else:
            graph += ",format=yuv420p[o]"
            K.run_ffmpeg(["-i", str(src), "-loop", "1", "-i", str(png), "-filter_complex", graph, "-map", "[o]", "-map", "0:a?",
                          "-shortest"] + K.video_codec_args(20) + ["-c:a", "aac", "-b:a", "160k", str(out)],
                         duration=info["duration"], status=st)
    K.finish({"output": K.file_info(out), "style": a.style, "elapsed_s": st.elapsed()})


if __name__ == "__main__":
    K.main_guard(main)
