#!/usr/bin/env python3
"""Reproduce the article's QR examples with an existing public blog URL.

Python 3.12.14, qrcode 8.2, Pillow 12.3.0, zxing-cpp 3.1.1 were used.
This is a PNG encode/decode experiment, not a phone/print robustness test.
Usage: python qr-density-lab.py [output-directory]
The script does not fetch URLs or make network requests.
"""

import importlib.metadata
import json
from pathlib import Path
import platform
import sys

from PIL import Image
import qrcode
from qrcode.exceptions import DataOverflowError
from qrcode.util import MODE_8BIT_BYTE, QRData
import zxingcpp

PAYLOAD = "https://gumi.ink/posts/2026-10-08-how-to-read-meteor-shower-rates/"
RAW = PAYLOAD.encode("utf-8")
LEVELS = {
    "L": qrcode.constants.ERROR_CORRECT_L,
    "M": qrcode.constants.ERROR_CORRECT_M,
    "Q": qrcode.constants.ERROR_CORRECT_Q,
    "H": qrcode.constants.ERROR_CORRECT_H,
}


def make_qr(level, *, mask=None, border=4, version=None, fit=True):
    qr = qrcode.QRCode(
        version=version, error_correction=LEVELS[level],
        box_size=6, border=border, mask_pattern=mask,
    )
    qr.add_data(QRData(RAW, mode=MODE_8BIT_BYTE))
    qr.make(fit=fit)
    return qr


def save_and_read(qr, path, level, mask=None, border=4):
    qr.make_image(fill_color="black", back_color="white").save(path)
    with Image.open(path) as image:
        pixels = image.size
        result = zxingcpp.read_barcode(image)
    decoded = result.text if result else None
    return {
        "file": path.name,
        "ecc": level,
        "mode": "8-bit byte; one segment",
        "mask_setting": "automatic" if mask is None else mask,
        "version": qr.version,
        "symbol_modules_per_side": qr.modules_count,
        "quiet_zone_modules_per_side": border,
        "pixels_per_module": 6,
        "image_pixels": list(pixels),
        "decoded_text": decoded,
        "decoded_text_matches_input": decoded == PAYLOAD,
    }


def main():
    output = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("qr-density-output")
    output.mkdir(parents=True, exist_ok=True)
    records = []
    for level in LEVELS:
        row = save_and_read(make_qr(level), output / f"ecc-{level}.png", level)
        assert row["decoded_text_matches_input"], row
        records.append(row)

    masks = [make_qr("M", mask=mask) for mask in (0, 1)]
    for mask, qr in zip((0, 1), masks):
        row = save_and_read(qr, output / f"mask-{mask}.png", "M", mask=mask)
        assert row["decoded_text_matches_input"], row
        records.append(row)
    assert masks[0].version == masks[1].version
    different_matrices = masks[0].modules != masks[1].modules
    assert different_matrices

    # Record what this decoder does; do not assume all readers behave alike.
    no_margin = save_and_read(
        make_qr("M", border=0), output / "m-no-quiet-zone.png", "M", border=0,
    )
    records.append(no_margin)

    try:
        make_qr("M", version=1, fit=False)
    except DataOverflowError as error:
        overflow = {"rejected": True, "error": type(error).__name__, "detail": str(error)}
    else:
        raise AssertionError("Version 1 unexpectedly accepted the complete payload")

    report = {
        "kind": "generated_pngs_read_by_an_independent_software_decoder",
        "environment": {
            "python": platform.python_version(),
            "macos": platform.mac_ver()[0],
            "machine": platform.machine(),
            "packages": {name: importlib.metadata.version(name)
                         for name in ("qrcode", "Pillow", "zxing-cpp")},
        },
        "payload": PAYLOAD,
        "payload_utf8_bytes": len(RAW),
        "records": records,
        "mask_0_and_1_have_different_matrices": different_matrices,
        "forced_version_1_M_fit_false": overflow,
        "scope": "No phone camera, print, logo damage, viewing distance or lighting tests.",
    }
    (output / "results.json").write_text(
        json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8",
    )
    print(json.dumps(report, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
