#!/usr/bin/env python3
"""Deterministic Pillow resolution study.

Draws one vector-like design independently at 640x360, 1280x720 and 3840x2160
(all coordinates, font pixel sizes and stroke widths scaled linearly from a
1280x720 logical canvas; no image is derived by resampling another),
normalizes each native to 1280x720 with Pillow Lanczos, builds 2x
nearest-neighbor enlargements of an identical crop box (small text, fine
diagonals, circle arc), 320x180 Lanczos previews, and a labeled three-row
comparison figure. Writes results.json.

Deterministic and rerunnable: no clock, no randomness, no network; PNG
encoding at fixed compress_level; all writes confined to ./output next to
this script.
"""

import hashlib
import json
import os
import platform
import sys

from PIL import Image, ImageDraw, ImageFont, features

HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, "output")

FONT_REGULAR = "/System/Library/Fonts/Supplemental/Arial.ttf"
FONT_BOLD = "/System/Library/Fonts/Supplemental/Arial Bold.ttf"

LOGICAL_W, LOGICAL_H = 1280, 720
NATIVES = [(640, 360), (1280, 720), (3840, 2160)]

# Design palette (RGB).
BG = (18, 48, 38)          # dark green
CREAM = (242, 233, 210)    # primary foreground
MUTED_CREAM = (214, 206, 184)  # secondary text
LINE_GREEN = (108, 140, 120)   # fine diagonal lines

# Layout in logical 1280x720 coordinates.
TITLE_TEXT = "RESOLUTION STUDY"
TITLE_Y, TITLE_PX = 116, 64          # Arial Bold
SUB_TEXT = "One vector-like design, drawn natively at each size"
SUB_Y, SMALL_PX = 176, 16            # Arial regular
BAND_BOX = (150, 320, 560, 650)      # diagonal band region
BAND_SPACING = 16                    # logical px between diagonals
BAND_WIDTH = 1                       # logical stroke width
CIRCLE_C = (760, 380)                # center
CIRCLE_R, CIRCLE_STROKE = 130, 2     # logical
SMALL_LINES = [                      # small text block, left x, top y
    ("16 px logical small text", 600, 520),
    ("diagonal spacing 16 px", 600, 544),
    ("circle stroke 2 px", 600, 568),
]
LABEL_X, LABEL_Y, LABEL_PX = 24, 656, 20  # native-size label, Arial regular
CROP_BOX = (480, 340, 800, 600)      # logical == normalized 1280x720 coords

NORM_SIZE = (1280, 720)
PREVIEW_SIZE = (320, 180)
CROP2X_SIZE = (640, 520)

COMPARISON_HEADER = "RESOLUTION STUDY · one design at three native sizes — detail crop, 2× nearest"
COMPARISON_CAPTIONS = {
    640: "A · source native 640×360 → normalized 1280×720 (Lanczos) → 2× nearest",
    1280: "B · source native 1280×720 → identity normalization (unchanged pixels) → 2× nearest",
    3840: "C · source native 3840×2160 → normalized 1280×720 (Lanczos) → 2× nearest",
}


def sha256_file(path):
    with open(path, "rb") as f:
        return hashlib.sha256(f.read()).hexdigest()


def draw_design(width, height):
    """Draw the design natively at width x height; return (image, actual params)."""
    s = width / LOGICAL_W
    S = lambda v: int(round(v * s))
    img = Image.new("RGB", (width, height), BG)
    d = ImageDraw.Draw(img)

    # Fine diagonal lines, drawn on a layer covering the band region only.
    x0, y0, x1, y1 = S(BAND_BOX[0]), S(BAND_BOX[1]), S(BAND_BOX[2]), S(BAND_BOX[3])
    bw, bh = x1 - x0, y1 - y0
    band = Image.new("RGB", (bw, bh), BG)
    bd = ImageDraw.Draw(band)
    spacing = max(1, int(round(BAND_SPACING * s)))
    line_w = max(1, int(round(BAND_WIDTH * s)))
    x = -bh
    while x <= bw + 1:
        bd.line([(x, 0), (x + bh, bh)], fill=LINE_GREEN, width=line_w)
        x += spacing
    img.paste(band, (x0, y0))

    # Circle outline.
    r = S(CIRCLE_R)
    cx, cy = S(CIRCLE_C[0]), S(CIRCLE_C[1])
    stroke = max(1, int(round(CIRCLE_STROKE * s)))
    d.ellipse([cx - r, cy - r, cx + r, cy + r], outline=CREAM, width=stroke)

    # Text. Font pixel sizes scale from the logical canvas like every coordinate.
    f_title = ImageFont.truetype(FONT_BOLD, max(1, int(round(TITLE_PX * s))))
    f_small = ImageFont.truetype(FONT_REGULAR, max(1, int(round(SMALL_PX * s))))
    f_label = ImageFont.truetype(FONT_REGULAR, max(1, int(round(LABEL_PX * s))))

    d.text((S(LOGICAL_W // 2), S(TITLE_Y)), TITLE_TEXT, font=f_title, fill=CREAM, anchor="mm")
    d.text((S(LOGICAL_W // 2), S(SUB_Y)), SUB_TEXT, font=f_small, fill=MUTED_CREAM, anchor="mm")
    for line, lx, ly in SMALL_LINES:
        d.text((S(lx), S(ly)), line, font=f_small, fill=CREAM, anchor="la")
    d.text(
        (S(LABEL_X), S(LABEL_Y)),
        "native %d×%d, drawn natively" % (width, height),
        font=f_label,
        fill=MUTED_CREAM,
        anchor="la",
    )

    params = {
        "scale_factor": s,
        "title_font_px": f_title.size,
        "small_text_font_px": f_small.size,
        "label_font_px": f_label.size,
        "diagonal_spacing_px": spacing,
        "diagonal_stroke_px": line_w,
        "circle_radius_px": r,
        "circle_stroke_px": stroke,
        "diagonal_band_box_px": [x0, y0, x1, y1],
        "circle_bbox_px": [cx - r, cy - r, cx + r, cy + r],
    }
    return img, params


def record_file(path):
    return {
        "path": os.path.relpath(path, HERE),
        "width": None,  # filled by caller when an image is at hand
        "encoded_bytes": os.path.getsize(path),
        "sha256": sha256_file(path),
    }


def main():
    for p in (FONT_REGULAR, FONT_BOLD):
        if not os.path.exists(p):
            sys.exit("required font missing: %s" % p)
    os.makedirs(OUT, exist_ok=True)

    versions = {
        "python": sys.version.split()[0],
        "python_executable": sys.executable,
        "pillow": Image.__version__,
        "freetype": features.version("freetype2"),
        "raqm_layout_engine": bool(features.check("raqm")),
        "platform": platform.platform(),
        "machine": platform.machine(),
    }

    natives = []
    images = {}
    for w, h in NATIVES:
        img, params = draw_design(w, h)
        path = os.path.join(OUT, "native-%dx%d.png" % (w, h))
        img.save(path, format="PNG", compress_level=6)
        rec = record_file(path)
        rec.update(
            {
                "width": img.width,
                "height": img.height,
                "native_pixel_count": img.width * img.height,
                "native_size": "%dx%d" % (w, h),
                "drawn_params": params,
            }
        )
        natives.append(rec)
        images[w] = Image.open(path)

    # Normalize each native to 1280x720 with a single Lanczos pass.
    normalization = {
        "method": "Image.resize((1280, 720), Image.Resampling.LANCZOS), single pass per native",
        "resampling": "LANCZOS",
        "target_size": "1280x720",
        "items": [],
    }
    for rec in natives:
        w = int(rec["native_size"].split("x")[0])
        native = images[w]
        norm = native.resize(NORM_SIZE, Image.Resampling.LANCZOS)
        path = os.path.join(OUT, "normalized-from-%d.png" % w)
        norm.save(path, format="PNG", compress_level=6)
        item = record_file(path)
        item.update(
            {
                "width": norm.width,
                "height": norm.height,
                "source": rec["path"],
                "pixel_data_identical_to_native": norm.tobytes() == native.tobytes(),
            }
        )
        normalization["items"].append(item)

    # 2x nearest enlargements of the identical crop box.
    crop = {
        "box_logical_1280": list(CROP_BOX),
        "box_on_normalized_1280x720": list(CROP_BOX),
        "crop_size": "%dx%d" % (CROP_BOX[2] - CROP_BOX[0], CROP_BOX[3] - CROP_BOX[1]),
        "contents": [
            "small text block (16 px logical)",
            "fine diagonal lines (16 px logical spacing)",
            "circle outline arc",
        ],
        "enlargement": "Image.resize((640, 520), Image.Resampling.NEAREST), factor 2",
        "native_size_label_position_logical": [LABEL_X, LABEL_Y],
        "items": [],
    }
    for rec in normalization["items"]:
        norm = Image.open(os.path.join(HERE, rec["path"]))
        c = norm.crop(CROP_BOX)
        big = c.resize(CROP2X_SIZE, Image.Resampling.NEAREST)
        path = os.path.join(OUT, "crop2x-from-%s" % os.path.basename(rec["path"]).split("-")[2])
        big.save(path, format="PNG", compress_level=6)
        item = record_file(path)
        item.update({"width": big.width, "height": big.height, "source": rec["path"]})
        crop["items"].append(item)

    # 320x180 previews, single Lanczos pass straight from each native.
    previews = {
        "method": "Image.resize((320, 180), Image.Resampling.LANCZOS), single pass from native",
        "resampling": "LANCZOS",
        "size": "320x180",
        "items": [],
    }
    for rec in natives:
        native = images[int(rec["native_size"].split("x")[0])]
        pv = native.resize(PREVIEW_SIZE, Image.Resampling.LANCZOS)
        path = os.path.join(OUT, "preview-320x180-from-%d.png" % int(rec["native_size"].split("x")[0]))
        pv.save(path, format="PNG", compress_level=6)
        item = record_file(path)
        item.update({"width": pv.width, "height": pv.height, "source": rec["path"]})
        previews["items"].append(item)

    # Labeled three-row comparison figure built from the saved 2x crops.
    MARGIN, HEADER_H, CAPTION_H, GAP = 24, 64, 36, 14
    CROP_W, CROP_H = CROP2X_SIZE
    fig_w = 900
    x_col = (fig_w - CROP_W) // 2
    fig_h = HEADER_H + 3 * (CAPTION_H + CROP_H + GAP) - GAP + MARGIN
    paper, ink, rule = (247, 244, 236), (18, 48, 38), (150, 163, 150)
    fig = Image.new("RGB", (fig_w, fig_h), paper)
    fd = ImageDraw.Draw(fig)
    f_head = ImageFont.truetype(FONT_BOLD, 22)
    f_cap = ImageFont.truetype(FONT_REGULAR, 17)
    fd.text(
        (MARGIN, HEADER_H // 2),
        COMPARISON_HEADER,
        font=f_head,
        fill=ink,
        anchor="lm",
    )
    for i, item in enumerate(crop["items"]):
        src_w = int(os.path.basename(item["source"]).rsplit("-", 1)[1].split(".")[0])
        y0 = HEADER_H + i * (CAPTION_H + CROP_H + GAP)
        fd.text((x_col, y0 + CAPTION_H // 2), COMPARISON_CAPTIONS[src_w], font=f_cap, fill=ink, anchor="lm")
        top = y0 + CAPTION_H
        crop_img = Image.open(os.path.join(HERE, item["path"]))
        fig.paste(crop_img, (x_col, top))
        fd.rectangle([x_col - 1, top - 1, x_col + CROP_W, top + CROP_H], outline=rule, width=1)
    comparison_path = os.path.join(OUT, "comparison-rows.png")
    figure_text_fits = fd.textlength(COMPARISON_HEADER, font=f_head) <= fig_w - 2 * MARGIN and all(
        fd.textlength(COMPARISON_CAPTIONS[w], font=f_cap) <= fig_w - x_col - MARGIN for w in COMPARISON_CAPTIONS
    )
    fig.save(comparison_path, format="PNG", compress_level=6)
    comparison = record_file(comparison_path)
    comparison.update(
        {
            "width": fig.width,
            "height": fig.height,
            "layout": "header + three rows, each: caption line above a %dx%d 2x-nearest crop" % CROP2X_SIZE,
            "rows": [COMPARISON_CAPTIONS[w] for w in (640, 1280, 3840)],
            "sources": [it["path"] for it in crop["items"]],
        }
    )

    # Self-checks against files on disk.
    checks = []

    def check(name, passed, detail):
        checks.append({"name": name, "passed": bool(passed), "detail": detail})

    check(
        "native_dimensions",
        all(r["width"] == w and r["height"] == h for r, (w, h) in zip(natives, NATIVES)),
        "expected %s" % ", ".join("%dx%d" % n for n in NATIVES),
    )
    check(
        "native_files_hash_and_size",
        all(
            os.path.getsize(os.path.join(HERE, r["path"])) == r["encoded_bytes"]
            and sha256_file(os.path.join(HERE, r["path"])) == r["sha256"]
            for r in natives
        ),
        "re-read every native PNG and recomputed size + SHA256",
    )
    check(
        "normalized_dimensions",
        all(it["width"] == 1280 and it["height"] == 720 for it in normalization["items"]),
        "all normalized outputs are 1280x720",
    )
    ident = [it for it in normalization["items"] if it["source"].endswith("native-1280x720.png")][0]
    native1280 = Image.open(os.path.join(HERE, "output", "native-1280x720.png"))
    norm1280 = Image.open(os.path.join(HERE, "output", "normalized-from-1280.png"))
    check(
        "normalized_1280_identity_pixel_exact",
        native1280.tobytes() == norm1280.tobytes() and ident["pixel_data_identical_to_native"],
        "1280x720 native and its 1280x720 Lanczos output have identical pixel data",
    )
    crop_ok, crop_detail = True, []
    for item in crop["items"]:
        norm = Image.open(os.path.join(HERE, item["source"]))
        redone = norm.crop(CROP_BOX).resize(CROP2X_SIZE, Image.Resampling.NEAREST)
        saved = Image.open(os.path.join(HERE, item["path"]))
        same = redone.tobytes() == saved.tobytes()
        crop_ok = crop_ok and same and saved.size == CROP2X_SIZE
        crop_detail.append("%s: %s %s" % (os.path.basename(item["path"]), saved.size, "ok" if same else "MISMATCH"))
    check(
        "crop_consistency_same_box_rederived",
        crop_ok,
        "all 2x crops re-derived from saved normalized PNGs at box %s match saved outputs (%s)"
        % (list(CROP_BOX), "; ".join(crop_detail)),
    )
    expected_sources = [os.path.join("output", "normalized-from-%d.png" % w) for w in (640, 1280, 3840)]
    check(
        "crop_same_source_box_all_rows",
        [os.path.normpath(it["source"]) for it in crop["items"]] == expected_sources,
        "each row derives from the normalized image of its own native size, same crop box",
    )
    check(
        "comparison_text_fits_figure",
        figure_text_fits,
        "header and all captions measured with font.getlength-style textlength fit %d px figure width minus margins" % fig_w,
    )
    own = os.path.join(HERE, "output") + os.sep
    all_paths = [r["path"] for r in natives] + [i["path"] for i in normalization["items"]]
    all_paths += [i["path"] for i in crop["items"]] + [i["path"] for i in previews["items"]] + [comparison["path"]]
    check(
        "outputs_confined_to_owned_dir",
        all(os.path.normpath(os.path.join(HERE, p)).startswith(own) for p in all_paths),
        "%d output files, all under output/" % len(all_paths),
    )

    results = {
        "task": "023400-resolution-study",
        "generator": os.path.basename(__file__),
        "determinism_note": "no clock, randomness or network inputs; fixed PNG compress_level=6; safe to rerun",
        "versions": versions,
        "fonts": {
            "regular": FONT_REGULAR,
            "bold": FONT_BOLD,
            "both_present": True,
        },
        "design": {
            "logical_canvas": "1280x720",
            "scaling_rule": "s = native_width / 1280; coordinates int(round(v*s)); font px int(round(px*s)); stroke widths max(1, int(round(w*s))); every native drawn independently, none derived by resampling another",
            "colors": {"background_dark_green": list(BG), "cream": list(CREAM), "muted_cream": list(MUTED_CREAM), "diagonal_line_green": list(LINE_GREEN)},
            "elements_logical": {
                "title": {"text": TITLE_TEXT, "font": "Arial Bold", "px": TITLE_PX, "center": [LOGICAL_W // 2, TITLE_Y]},
                "subtitle": {"text": SUB_TEXT, "font": "Arial", "px": SMALL_PX, "center": [LOGICAL_W // 2, SUB_Y]},
                "diagonal_band": {"box": list(BAND_BOX), "spacing": BAND_SPACING, "stroke": BAND_WIDTH, "angle_deg": 45},
                "circle": {"center": list(CIRCLE_C), "radius": CIRCLE_R, "stroke": CIRCLE_STROKE},
                "small_text_lines": [{"text": t, "at": [x, y]} for t, x, y in SMALL_LINES],
                "native_size_label": {"format": "native {W}×{H}, drawn natively", "at": [LABEL_X, LABEL_Y], "px": LABEL_PX, "outside_crop_box": list(CROP_BOX)},
            },
        },
        "natives": natives,
        "normalization": normalization,
        "crop_2x": crop,
        "previews_320x180": previews,
        "comparison_figure": comparison,
        "self_checks": checks,
    }

    results_path = os.path.join(OUT, "results.json")
    with open(results_path, "w", encoding="utf-8") as f:
        json.dump(results, f, indent=2, ensure_ascii=False, sort_keys=True)
        f.write("\n")

    # Post-write integrity: results.json reparses and every recorded hash/size matches disk.
    with open(results_path, "r", encoding="utf-8") as f:
        reparsed = json.load(f)
    disk_ok = True
    for section in ("natives",):
        for r in reparsed[section]:
            p = os.path.join(HERE, r["path"])
            disk_ok &= os.path.getsize(p) == r["encoded_bytes"] and sha256_file(p) == r["sha256"]
    for section in (normalization, crop, previews):
        for r in section["items"]:
            p = os.path.join(HERE, r["path"])
            disk_ok &= os.path.getsize(p) == r["encoded_bytes"] and sha256_file(p) == r["sha256"]
    p = os.path.join(HERE, comparison["path"])
    disk_ok &= os.path.getsize(p) == comparison["encoded_bytes"] and sha256_file(p) == comparison["sha256"]
    check("results_json_reparses_and_hashes_match_disk", disk_ok, "results.json reparsed; every recorded encoded_bytes and SHA256 recomputed from disk")

    # Rewrite with the final check included, then re-verify once more.
    with open(results_path, "w", encoding="utf-8") as f:
        json.dump(results, f, indent=2, ensure_ascii=False, sort_keys=True)
        f.write("\n")

    failed = [c for c in checks if not c["passed"]]
    print("python %s / pillow %s / freetype %s / raqm %s"
          % (versions["python"], versions["pillow"], versions["freetype"], versions["raqm_layout_engine"]))
    print("fonts: %s" % FONT_REGULAR)
    for r in natives:
        print("native   %-22s %5dx%-5d %8d bytes  %s" % (os.path.basename(r["path"]), r["width"], r["height"], r["encoded_bytes"], r["sha256"][:12]))
    for it in normalization["items"]:
        print("norm     %-22s %5dx%-5d %8d bytes  %s" % (os.path.basename(it["path"]), it["width"], it["height"], it["encoded_bytes"], it["sha256"][:12]))
    for it in crop["items"]:
        print("crop2x   %-22s %5dx%-5d %8d bytes  %s" % (os.path.basename(it["path"]), it["width"], it["height"], it["encoded_bytes"], it["sha256"][:12]))
    for it in previews["items"]:
        print("preview  %-22s %5dx%-5d %8d bytes  %s" % (os.path.basename(it["path"]), it["width"], it["height"], it["encoded_bytes"], it["sha256"][:12]))
    print("figure   %-22s %5dx%-5d %8d bytes  %s" % (os.path.basename(comparison["path"]), comparison["width"], comparison["height"], comparison["encoded_bytes"], comparison["sha256"][:12]))
    print("self-checks: %d/%d passed" % (len(checks) - len(failed), len(checks)))
    if failed:
        for c in failed:
            print("FAILED: %s — %s" % (c["name"], c["detail"]))
        sys.exit(1)


if __name__ == "__main__":
    main()
