#!/usr/bin/env python3
"""Render branded social frames from a data file.

    python3 social_frame.py                                  # both formats, default paths
    python3 social_frame.py --format svg                     # vector only
    python3 social_frame.py --data input/frames.json --out ../out/frames

Reads one JSON file of frame records and writes one image per record at 1080 by 1080. Nothing in
the layout is typed twice: the copy lives in the data file, the brand values live in brandkit.py,
and this script only decides where things sit. Without Pillow it writes SVG and says so.

Apex Instruments is fictional. Every name and figure in the input file is demo data.
"""

import argparse
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import brandkit as bk  # noqa: E402

SIZE = 1080
M = 84                      # page margin
COL = SIZE - M * 2          # text column

THEMES = {
    "ink": {"bg": bk.INK, "fg": bk.WHITE, "dim": bk.ICE, "accent": bk.ORANGE, "rule": "#0C3B41"},
    "paper": {"bg": bk.PAPER, "fg": bk.INK, "dim": bk.MUTED, "accent": bk.ORANGE, "rule": bk.LINE},
    "steel": {"bg": bk.STEEL, "fg": bk.WHITE, "dim": "#C6DADC", "accent": bk.ORANGE, "rule": "#3C767C"},
}


def chrome(t, wordmark, footer, accent_spent):
    """The parts every frame has: the mark, the wordmark, the top rule, the footer.

    The brand allows one orange accent per view. A stat frame spends it on the figure, so its rule
    is drawn in the quiet colour instead. Every other kind spends it on the rule."""
    s = [bk.rect(0, 0, SIZE, SIZE, t["bg"])]
    s.append(bk.spark(M, M + 4, 46, t["fg"], 14))
    s.append(bk.text(M + 66, M + 40, wordmark, 25, "mono", t["fg"], "600", 0.12))
    s.append(bk.line(M, M + 92, SIZE - M, M + 92, t["rule"], 2))
    s.append(bk.line(M, SIZE - M - 74, M + 118, SIZE - M - 74,
                     t["dim"] if accent_spent else t["accent"], 6))
    s.append(bk.text(M, SIZE - M + 6, footer, 23, "mono", t["dim"], "400", 0.1))
    return s


def block(shapes, y, lines, size, family, fill, weight, leading, tracking=0.0):
    for ln in lines:
        shapes.append(bk.text(M, y, ln, size, family, fill, weight, tracking))
        y += leading
    return y


def layout(frame, defaults):
    t = THEMES.get(frame.get("theme", "paper"), THEMES["paper"])
    wordmark = frame.get("wordmark", defaults.get("wordmark", "APEX INSTRUMENTS"))
    footer = frame.get("footer", defaults.get("footer", ""))
    kind = frame.get("kind", "announcement")
    s = chrome(t, wordmark, footer, accent_spent=(kind == "stat"))
    y = 330

    if frame.get("eyebrow"):
        s.append(bk.text(M, 268, frame["eyebrow"], 24, "mono", t["dim"], "600", 0.14))

    if kind == "announcement":
        head = bk.wrap(frame["headline"], 74, "display", COL, max_lines=4)
        y = block(s, 400, head, 74, "display", t["fg"], "800", 88)
        sub = bk.wrap(frame.get("sub", ""), 33, "sans", COL, max_lines=4)
        block(s, y + 34, sub, 33, "sans", t["dim"], "400", 46)

    elif kind == "stat":
        s.append(bk.text(M, 560, frame["stat"], 210, "display", t["accent"], "900", -0.02))
        cap = bk.wrap(frame.get("caption", ""), 38, "sans", COL, max_lines=5)
        block(s, 660, cap, 38, "sans", t["fg"], "400", 54)

    elif kind == "quote":
        s.append(bk.text(M, 360, "\u201c", 150, "display", t["dim"], "900"))
        q = bk.wrap(frame["quote"], 54, "display", COL, max_lines=6)
        y = block(s, 440, q, 54, "display", t["fg"], "700", 70)
        s.append(bk.text(M, y + 40, frame.get("attribution", ""), 27, "mono", t["dim"], "400", 0.1))

    elif kind == "event":
        head = bk.wrap(frame["headline"], 72, "display", COL, max_lines=3)
        y = block(s, 400, head, 72, "display", t["fg"], "800", 86)
        det = bk.wrap(frame.get("detail", ""), 34, "sans", COL, max_lines=4)
        y = block(s, y + 30, det, 34, "sans", t["dim"], "400", 48)
        s.append(bk.line(M, y + 12, SIZE - M, y + 12, t["rule"], 2))
        meta = bk.wrap(frame.get("meta", ""), 27, "mono", COL, tracking=0.06, max_lines=2)
        block(s, y + 62, meta, 27, "mono", t["fg"], "400", 40, 0.06)

    else:
        raise ValueError("unknown frame kind: %s" % kind)

    return s, t


def main(argv=None):
    here = os.path.dirname(os.path.abspath(__file__))
    ap = argparse.ArgumentParser(description="Render branded social frames from a data file.")
    ap.add_argument("--data", default=os.path.join(here, "input/frames.json"))
    ap.add_argument("--out", default=os.path.join(here, "../out/frames"))
    ap.add_argument("--format", default="both", choices=["both", "png", "svg"])
    a = ap.parse_args(argv)

    data = bk.load_json(a.data)
    defaults = data.get("defaults", {})
    formats = ["png", "svg"] if a.format == "both" else [a.format]
    if "png" in formats and not bk.have_pillow():
        print("Pillow is not installed, so this run writes SVG only.")

    made, report = [], []
    for frame in data["frames"]:
        shapes, theme = layout(frame, defaults)
        base = os.path.join(a.out, frame["id"])
        files = bk.render(shapes, SIZE, SIZE, base, formats, background=theme["bg"],
                          title="%s, %s" % (frame["id"], frame.get("kind", "frame")))
        made += files
        report.append({"id": frame["id"], "kind": frame.get("kind"), "theme": frame.get("theme"),
                       "size": [SIZE, SIZE], "files": [os.path.relpath(f, a.out) for f in files],
                       "bytes": {os.path.basename(f): os.path.getsize(f) for f in files}})
        print("frame %-9s %s" % (frame["id"], ", ".join(os.path.basename(f) for f in files)))

    bk.save_json(os.path.join(a.out, "frames-built.json"),
                 {"source": os.path.basename(a.data), "count": len(report),
                  "pillow": bk.have_pillow(), "frames": report})
    print("%d frames, %d files, into %s" % (len(report), len(made), a.out))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
