#!/usr/bin/env python3
"""Build a contact sheet from a folder of images.

    python3 contact_sheet.py
    python3 contact_sheet.py --src ../out/frames --out ../out/contact-sheet --cols 3 --format svg

One page, every image in the folder, each with its file name and its pixel size printed under it.
This is the cheapest review tool there is: a person can see in five seconds that a set is complete,
that nothing is the wrong crop, and that no slot is empty.

The raster sheet pastes the source PNGs. The vector sheet nests each source SVG inside the page, so
the sheet is genuinely the same artwork rather than a picture of it, and it still works on a machine
with no Pillow.

Apex Instruments is fictional. Every name and figure in the source frames is demo data.
"""

import argparse
import math
import os
import sys

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

MARGIN = 48
GUTTER = 24
CELL = 300
LABEL = 54
HEADER = 132


def geometry(count, cols):
    rows = int(math.ceil(count / float(cols)))
    width = MARGIN * 2 + cols * CELL + (cols - 1) * GUTTER
    height = HEADER + rows * (CELL + LABEL) + max(0, rows - 1) * GUTTER + MARGIN
    return rows, width, height


def cell_origin(i, cols):
    c, r = i % cols, i // cols
    return (MARGIN + c * (CELL + GUTTER), HEADER + r * (CELL + LABEL + GUTTER))


def chrome(title, subtitle, count, width, height):
    s = [bk.rect(0, 0, width, height, bk.WHITE)]
    s.append(bk.spark(MARGIN, MARGIN - 6, 30, bk.INK, 16))
    s.append(bk.text(MARGIN + 44, MARGIN + 16, title, 22, "display", bk.INK, "800"))
    s.append(bk.text(MARGIN, MARGIN + 46, subtitle, 13, "mono", bk.MUTED, "400", 0.1))
    s.append(bk.text(width - MARGIN, MARGIN + 16, "%d items" % count, 15, "mono", bk.ORANGE,
                     "600", 0.1, anchor="end"))
    s.append(bk.line(MARGIN, HEADER - 26, width - MARGIN, HEADER - 26, bk.LINE, 2))
    return s


def labels(items, cols):
    s = []
    for i, it in enumerate(items):
        x, y = cell_origin(i, cols)
        s.append(bk.rect(x, y, CELL, CELL, bk.MIST))
        s.append(bk.text(x, y + CELL + 22, it["name"], 13, "sans", bk.INK, "600"))
        s.append(bk.text(x, y + CELL + 40, "%d x %d  %s  %s" %
                         (it["w"], it["h"], it["ext"].lstrip(".").upper(), it["kb"]),
                         11, "mono", bk.MUTED, "400", 0.08))
    return s


def collect(src, ext):
    items = []
    for name in sorted(os.listdir(src)):
        base, e = os.path.splitext(name)
        if e.lower() != ext:
            continue
        path = os.path.join(src, name)
        size = bk.image_size(path)
        if not size:
            continue
        items.append({"name": name, "path": path, "w": size[0], "h": size[1], "ext": e,
                      "kb": "%.0f kB" % (os.path.getsize(path) / 1024.0)})
    return items


def build_png(items, cols, title, subtitle, out_path):
    from PIL import Image
    rows, width, height = geometry(len(items), cols)
    shapes = chrome(title, subtitle, len(items), width, height) + labels(items, cols)
    for i, it in enumerate(items):
        x, y = cell_origin(i, cols)
        with Image.open(it["path"]) as im:
            im = im.convert("RGB")
            scale = min(CELL / float(im.width), CELL / float(im.height))
            w, h = max(1, int(im.width * scale)), max(1, int(im.height * scale))
            thumb = im.resize((w, h), Image.LANCZOS)
        shapes.append({"kind": "paste", "image": thumb,
                       "x": x + (CELL - w) // 2, "y": y + (CELL - h) // 2})
    return bk.render_png(shapes, width, height, out_path)


def build_svg(items, cols, title, subtitle, out_path):
    rows, width, height = geometry(len(items), cols)
    shapes = chrome(title, subtitle, len(items), width, height) + labels(items, cols)
    for i, it in enumerate(items):
        x, y = cell_origin(i, cols)
        inner, vb = bk.svg_inner(it["path"])
        sw, sh = vb[2], vb[3]
        scale = min(CELL / sw, CELL / sh)
        dx = x + (CELL - sw * scale) / 2.0
        dy = y + (CELL - sh * scale) / 2.0
        shapes.append({"kind": "svgchild", "markup":
                       '<g transform="translate(%s,%s) scale(%s)">%s</g>'
                       % (round(dx, 3), round(dy, 3), round(scale, 5), inner)})
    return bk.render_svg(shapes, width, height, out_path, title=title)


def main(argv=None):
    here = os.path.dirname(os.path.abspath(__file__))
    ap = argparse.ArgumentParser(description="Build a contact sheet from a folder of images.")
    ap.add_argument("--src", default=os.path.join(here, "../out/frames"))
    ap.add_argument("--out", default=os.path.join(here, "../out/contact-sheet"))
    ap.add_argument("--cols", type=int, default=3)
    ap.add_argument("--format", default="both", choices=["both", "png", "svg"])
    ap.add_argument("--title", default="Apex Instruments, social frames")
    a = ap.parse_args(argv)

    formats = ["png", "svg"] if a.format == "both" else [a.format]
    if "png" in formats and not bk.have_pillow():
        formats = [f for f in formats if f != "png"]
        if "svg" not in formats:
            formats.append("svg")
        print("Pillow is not installed, so the sheet is written as SVG.")

    subtitle = "CONTACT SHEET / " + os.path.basename(os.path.abspath(a.src)).upper()
    made = []
    if "png" in formats:
        items = collect(a.src, ".png")
        if items:
            made.append(build_png(items, a.cols, a.title, subtitle, a.out + ".png"))
            print("sheet %s, %d items" % (os.path.basename(a.out) + ".png", len(items)))
    if "svg" in formats:
        items = collect(a.src, ".svg")
        if items:
            made.append(build_svg(items, a.cols, a.title, subtitle, a.out + ".svg"))
            print("sheet %s, %d items" % (os.path.basename(a.out) + ".svg", len(items)))
    if not made:
        print("nothing to sheet in %s" % a.src)
        return 1
    for f in made:
        print("  %s  %.0f kB" % (os.path.relpath(f, os.path.dirname(a.out)),
                                 os.path.getsize(f) / 1024.0))
    return 0


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