#!/usr/bin/env python3
"""Read-only verification for the Preview export samples in this folder.

Compares baseline-100ppi.png, off-300ppi.png and on-300ppi.png against the
expectations recorded in the sibling measurements.json, and prints each file's
name, pixel dimensions, actual byte count, stored DPI, color mode, file
SHA-256 and decoded RGBA SHA-256. Requires Python 3 and Pillow. It never
resizes, resamples, rewrites or creates images.

Usage: place this script, measurements.json and the three PNGs in one folder,
then run `python3 verify.py`.
"""
import hashlib
import json
import sys
from pathlib import Path

try:
    from PIL import Image
except ImportError:
    sys.exit("verify.py: Pillow is required (python3 -m pip install pillow), but it is not installed.")

PNG_NAMES = ("baseline-100ppi.png", "off-300ppi.png", "on-300ppi.png")


def fail(message):
    sys.exit("verify.py: " + message)


def sha256(data):
    return hashlib.sha256(data).hexdigest()


def measure(path):
    """Return measured properties of one PNG without modifying it."""
    try:
        raw = path.read_bytes()
    except OSError as error:
        fail("cannot read " + path.name + ": " + str(error))
    try:
        with Image.open(path) as image:
            image.load()
            dpi = image.info.get("dpi", (0.0, 0.0))
            return {
                "file": path.name,
                "pixels": [image.width, image.height],
                "bytes": len(raw),
                "stored_ppi": [round(value, 4) for value in dpi],
                "color_mode": image.mode,
                "file_sha256": sha256(raw),
                "decoded_rgba_sha256": sha256(image.convert("RGBA").tobytes()),
            }
    except Exception as error:
        fail(path.name + " is not a readable PNG: " + str(error))


def main():
    folder = Path(__file__).resolve().parent
    try:
        records = {
            record["file"]: record
            for record in json.loads((folder / "measurements.json").read_text(encoding="utf-8"))["files"]
        }
    except (OSError, ValueError, KeyError, TypeError) as error:
        fail("cannot load measurements.json: " + str(error))

    measured = {}
    for name in PNG_NAMES:
        path = folder / name
        if not path.is_file():
            fail("missing file: " + name)
        record = records.get(name)
        if record is None:
            fail("measurements.json has no record for " + name)
        actual = measure(path)
        measured[name] = actual

        print(actual["file"] + ": "
              + str(actual["pixels"][0]) + " x " + str(actual["pixels"][1]) + " pixels, "
              + str(actual["bytes"]) + " bytes, "
              + str(actual["stored_ppi"][0]) + " x " + str(actual["stored_ppi"][1]) + " DPI, "
              + "mode " + actual["color_mode"])
        print("  file SHA-256:         " + actual["file_sha256"])
        print("  decoded RGBA SHA-256: " + actual["decoded_rgba_sha256"])

        if actual["file_sha256"] != record["file_sha256"]:
            fail(name + ": file SHA-256 differs from measurements.json")
        if actual["pixels"] != record["pixels"]:
            fail(name + ": pixel dimensions differ from measurements.json")
        if actual["decoded_rgba_sha256"] != record["decoded_rgba_sha256"]:
            fail(name + ": decoded RGBA SHA-256 differs from measurements.json")

    if measured["baseline-100ppi.png"]["decoded_rgba_sha256"] != measured["off-300ppi.png"]["decoded_rgba_sha256"]:
        fail("baseline and Resample-off files do not share identical decoded RGBA pixels")
    on_pixels = measured["on-300ppi.png"]["pixels"]
    if on_pixels != [3840, 2160]:
        fail("Resample-on file is " + str(on_pixels[0]) + " x " + str(on_pixels[1])
             + ", expected 3840 x 2160")

    print("PASS: file hashes, pixel dimensions and decoded RGBA pixels match measurements.json;"
          " baseline and Resample off share pixels; Resample on is 3840 x 2160.")


if __name__ == "__main__":
    main()
