#!/usr/bin/env python3
"""Audit optical blackboard options for both cv/ssty shaping orders."""
from __future__ import annotations
import argparse, json
import sys
from fontTools.ttLib import TTFont
import uni_map as U

CV = {
    'cv03': ('bbd', 'bbd_s', 'bbd_ss'),
    'cv04': ('bbi', 'bbi_s', 'bbi_ss'),
    'cv05': ('hrb', 'hrb_s', 'hrb_ss'),
    'cv06': ('hrbd', 'hrbd_s', 'hrbd_ss'),
    'cv07': ('hbi', 'hbi_s', 'hbi_ss'),
}
BASE = ('bb', 'bb_s', 'bb_ss')


def feature_lookups(font, tag):
    t = font['GSUB'].table
    out = []
    if not t.FeatureList or not t.LookupList:
        return out
    for fr in t.FeatureList.FeatureRecord:
        if fr.FeatureTag == tag:
            out.extend(t.LookupList.Lookup[i] for i in fr.Feature.LookupListIndex)
    return out


def single_mapping(font, tag):
    out = {}
    for lk in feature_lookups(font, tag):
        for st in lk.SubTable:
            if lk.LookupType == 1:
                out.update(getattr(st, 'mapping', {}) or {})
            elif lk.LookupType == 7 and getattr(st, 'ExtensionLookupType', None) == 1:
                out.update(getattr(st.ExtSubTable, 'mapping', {}) or {})
    return out


def alternate_mapping(font, tag):
    out = {}
    for lk in feature_lookups(font, tag):
        for st in lk.SubTable:
            if lk.LookupType == 3:
                out.update(getattr(st, 'alternates', {}) or {})
            elif lk.LookupType == 7 and getattr(st, 'ExtensionLookupType', None) == 3:
                out.update(getattr(st.ExtSubTable, 'alternates', {}) or {})
    return out


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument('font')
    ap.add_argument('--contract', required=True)
    ns = ap.parse_args()
    f = TTFont(ns.font)
    con = json.load(open(ns.contract, encoding='utf-8'))
    by = {}
    for r in con['records']:
        by[(r['tag'], int(r['slot']))] = r['output_glyph']

    slots = sorted(U.alpha_map('bb'))
    errors = []
    ssty = alternate_mapping(f, 'ssty')
    checked_cv = checked_ssty = 0
    for cv, tags in CV.items():
        cmap = single_mapping(f, cv)
        for level, (bt, at) in enumerate(zip(BASE, tags)):
            for slot in slots:
                b = by.get((bt, slot))
                a = by.get((at, slot))
                if not b or not a:
                    errors.append(
                        f'{cv}: missing source record level{level} slot {slot}: {bt}->{at}')
                    continue
                got = cmap.get(b)
                if got != a:
                    errors.append(f'{cv}: {b} -> {got!r}, expected {a} (level{level} slot {slot})')
                else:
                    checked_cv += 1
        # Also require cv->ssty path for the option text glyph itself.
        text, script, ss = tags
        for slot in slots:
            b = by.get((text, slot))
            a1 = by.get((script, slot))
            a2 = by.get((ss, slot))
            if not b or not a1 or not a2:
                continue
            got = ssty.get(b)
            if got is None or list(got)[:2] != [a1, a2]:
                errors.append(f'ssty: {b} -> {got!r}, expected [{a1}, {a2}] ({cv} slot {slot})')
            else:
                checked_ssty += 1

    if errors:
        for e in errors[:80]:
            print('BB OPTION SSTY:', e, file=sys.stderr)
        raise SystemExit(f'BB OPTION SSTY FAIL: {len(errors)} errors')
    expected = len(slots) * 3 * len(CV)
    expected_ssty = len(slots) * len(CV)
    if checked_cv != expected or checked_ssty != expected_ssty:
        raise SystemExit(
            f'BB OPTION SSTY FAIL: checked cv={checked_cv}/{expected}, ssty={checked_ssty}/{expected_ssty}')


if __name__ == '__main__':
    main()
