"""Decode every QR matrix produced by qr.js with OpenCV (two independent decoders, several render scales) and compare with the source text.

    node tests/js/qr-matrix.mjs /tmp/qr_cases.json && python3 tests/js/verify_qr.py /tmp/qr_cases.json
    node tests/js/qr-sweep.mjs  /tmp/qr_sweep.json && python3 tests/js/verify_qr.py /tmp/qr_sweep.json

A case passes when at least one decoder returns the exact original text (OpenCV's classic detector is known to be
finicky on some larger symbols; the Aruco-based decoder is the cross-check).
Needs: pip install opencv-python numpy
"""
import json, sys, collections
import numpy as np
import cv2

cases = json.load(open(sys.argv[1] if len(sys.argv) > 1 else '/tmp/qr_cases.json'))
classic = cv2.QRCodeDetector()
aruco = cv2.QRCodeDetectorAruco() if hasattr(cv2, 'QRCodeDetectorAruco') else None


def render(c, scale, quiet):
    n = c['size']
    img = np.full(((n + 2 * quiet) * scale,) * 2, 255, np.uint8)
    for y, row in enumerate(c['rows']):
        for x, ch in enumerate(row):
            if ch == '1':
                img[(y + quiet) * scale:(y + quiet + 1) * scale, (x + quiet) * scale:(x + quiet + 1) * scale] = 0
    return img


def decodes(c):
    for scale, quiet in ((10, 4), (8, 5), (12, 6), (6, 4), (14, 4)):
        img = render(c, scale, quiet)
        if classic.detectAndDecode(img)[0] == c['text']:
            return 'classic'
        if aruco is not None and aruco.detectAndDecode(img)[0] == c['text']:
            return 'aruco'
    return None


fails, per = 0, collections.Counter()
for c in cases:
    if 'error' in c:
        print(f"SKIP  ecl={c['ecl']} len={len(c['text'])}: {c['error']}")
        continue
    how = decodes(c)
    per[(c['ecl'], bool(how))] += 1
    if how is None:
        fails += 1
        print(f"FAIL  ecl={c['ecl']} v{c['version']} mask={c['mask']} len={len(c['text'].encode())}")
print(f"decoded OK: {sum(v for (e, ok), v in per.items() if ok)}  failed: {fails}  (of {sum(per.values())})")
sys.exit(1 if fails else 0)
