#!/usr/bin/env python3
"""Scan a folder and write an index of what is in it.

    python3 index_folder.py
    python3 index_folder.py --src ../out --out ../out/index --ext .png,.svg

Walks a folder, reads the pixel size of every image without opening it in an editor, groups files by
content so exact duplicates are visible, and writes two files: index.json for a machine and index.md
for a person. It needs nothing but the standard library, which matters, because the index is the one
thing you want to be able to rebuild on any machine at any time.

Sizes are read from the PNG header and from the SVG viewBox directly, so this runs with or without
Pillow. Nothing here records a modification time, so the same folder always produces the same index
and a change in the file is a change in the content.

Apex Instruments is fictional. Every name and figure in the scanned files 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

KINDS = {".png": "raster", ".svg": "vector", ".json": "data", ".md": "text"}


def scan(src, exts):
    rows = []
    for base, dirs, files in os.walk(src):
        dirs.sort()
        for name in sorted(files):
            ext = os.path.splitext(name)[1].lower()
            if exts and ext not in exts:
                continue
            path = os.path.join(base, name)
            rel = os.path.relpath(path, src)
            size = bk.image_size(path) if ext in (".png", ".svg") else None
            rows.append({
                "path": rel.replace(os.sep, "/"),
                "folder": os.path.dirname(rel).replace(os.sep, "/") or ".",
                "name": name,
                "ext": ext,
                "kind": KINDS.get(ext, "other"),
                "bytes": os.path.getsize(path),
                "width": size[0] if size else None,
                "height": size[1] if size else None,
                "aspect": round(size[0] / float(size[1]), 4) if size and size[1] else None,
                "crc32": bk.short_hash(path)
            })
    return rows


def group_duplicates(rows):
    by_hash = {}
    for r in rows:
        by_hash.setdefault(r["crc32"], []).append(r["path"])
    return {h: paths for h, paths in by_hash.items() if len(paths) > 1}


def markdown(src, rows, dupes, totals):
    lines = ["# Index of %s" % os.path.basename(os.path.abspath(src)), "",
             "Apex Instruments is fictional. Every name and figure in these files is demo data.", "",
             "%d files, %s, across %d folders." %
             (totals["files"], totals["human"], totals["folders"]), ""]
    by_folder = {}
    for r in rows:
        by_folder.setdefault(r["folder"], []).append(r)
    for folder in sorted(by_folder):
        lines.append("## %s" % folder)
        lines.append("")
        lines.append("| file | size | pixels | bytes |")
        lines.append("| --- | --- | --- | --- |")
        for r in by_folder[folder]:
            px = "%d x %d" % (r["width"], r["height"]) if r["width"] else ""
            lines.append("| %s | %s | %s | %s |" % (r["name"], r["kind"], px, r["bytes"]))
        lines.append("")
    if dupes:
        lines.append("## Exact duplicates")
        lines.append("")
        for h, paths in sorted(dupes.items()):
            lines.append("- `%s`: %s" % (h, ", ".join(paths)))
        lines.append("")
    else:
        lines.append("No two files in this folder have identical content.")
        lines.append("")
    return "\n".join(lines)


def main(argv=None):
    here = os.path.dirname(os.path.abspath(__file__))
    ap = argparse.ArgumentParser(description="Scan a folder and write an index.")
    ap.add_argument("--src", default=os.path.join(here, "../out"))
    ap.add_argument("--out", default=os.path.join(here, "../out/index"))
    ap.add_argument("--ext", default=".png,.svg", help="comma separated extensions, blank for all")
    a = ap.parse_args(argv)

    exts = [e.strip().lower() for e in a.ext.split(",") if e.strip()]
    rows = scan(a.src, exts)
    if not rows:
        print("nothing matching %s under %s" % (a.ext, a.src))
        return 1
    dupes = group_duplicates(rows)
    total_bytes = sum(r["bytes"] for r in rows)
    folders = sorted(set(r["folder"] for r in rows))
    by_kind = {}
    for r in rows:
        by_kind[r["kind"]] = by_kind.get(r["kind"], 0) + 1
    totals = {"files": len(rows), "bytes": total_bytes,
              "human": "%.0f kB" % (total_bytes / 1024.0), "folders": len(folders),
              "byKind": by_kind, "duplicateGroups": len(dupes),
              "duplicateFiles": sum(len(v) - 1 for v in dupes.values())}

    bk.save_json(a.out + ".json", {"root": os.path.basename(os.path.abspath(a.src)),
                                   "extensions": exts, "totals": totals,
                                   "duplicates": dupes, "files": rows})
    bk.ensure_dir(a.out + ".md")
    with open(a.out + ".md", "w", encoding="utf-8") as fh:
        fh.write(markdown(a.src, rows, dupes, totals))

    print("%d files, %s, %d folders" % (totals["files"], totals["human"], totals["folders"]))
    print("kinds: %s" % ", ".join("%s %d" % (k, v) for k, v in sorted(by_kind.items())))
    print("duplicate groups: %d, redundant files: %d" %
          (totals["duplicateGroups"], totals["duplicateFiles"]))
    print("wrote %s.json and %s.md" % (os.path.basename(a.out), os.path.basename(a.out)))
    return 0


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