#!/usr/bin/env python3
"""Compare serialized MATH values with the local build snapshot."""
from __future__ import annotations

import argparse
import json
from pathlib import Path

import sys
from fontTools.ttLib import TTFont


def _scalar(value):
    return int(getattr(value, 'Value', value))


def load_expected(path: str | Path) -> dict[str, int]:
    data = json.loads(Path(path).read_text(encoding='utf-8'))
    constants = data.get('constants')
    if not isinstance(constants, dict):
        raise ValueError('local MATH snapshot has no constants object')
    expected = {str(k): int(v) for k, v in constants.items()}
    if 'MinConnectorOverlap' not in data:
        raise ValueError('local MATH snapshot has no MinConnectorOverlap')
    expected['MinConnectorOverlap'] = int(data['MinConnectorOverlap'])
    return expected


def audit(font_path: str | Path, values_path: str | Path) -> dict:
    expected = load_expected(values_path)
    font = TTFont(str(font_path))
    if 'MATH' not in font:
        raise ValueError(f'{font_path}: no MATH table')
    table = font['MATH'].table
    constants = table.MathConstants
    actual = {}
    missing = []
    for public_name in expected:
        if public_name == 'MinConnectorOverlap':
            actual[public_name] = int(table.MathVariants.MinConnectorOverlap)
            continue
        ot_name = public_name
        if not hasattr(constants, ot_name):
            missing.append((public_name, ot_name))
            continue
        actual[public_name] = _scalar(getattr(constants, ot_name))

    mismatches = {
        name: {'expected': expected[name], 'actual': actual.get(name)}
        for name in expected
        if name not in actual or actual[name] != expected[name]
    }
    if missing or mismatches:
        if missing:
            for public_name, ot_name in missing:
                print(f'MATH VALUE MISSING: {public_name} ({ot_name})', file=sys.stderr)
        for name, vals in sorted(mismatches.items()):
            print('MATH VALUE MISMATCH: %s expected=%s actual=%s' %
                  (name, vals['expected'], vals['actual']), file=sys.stderr)
        raise SystemExit(1)

    result = {
        'font': Path(font_path).name,
        'values_file': Path(values_path).name,
        'checked_names': sorted(expected),
        'actual': {k: actual[k] for k in sorted(actual)},
    }
    return result


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument('font')
    ap.add_argument('--values', required=True)
    ap.add_argument('--json-output')
    ns = ap.parse_args()
    result = audit(ns.font, ns.values)
    if ns.json_output:
        Path(ns.json_output).write_text(
            json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True) + '\n',
            encoding='utf-8')


if __name__ == '__main__':
    main()
