"""Add Word same-run prime forms shifted by preceding MATH italic
correction.

Use padding and measurements from the local build and generated font.
"""
from __future__ import annotations

from fractions import Fraction
import unicodedata

from fontTools.ttLib.tables import otTables as ot
from fontTools.pens.recordingPen import RecordingPen
from fontTools.pens.t2CharStringPen import T2CharStringPen
from fontTools.pens.transformPen import TransformPen

from finalize import update_average_width


# Context classes are a compact Word-compatibility approximation,
# expressed as fractions of the output em rather than
# copied MTPro2 measurements.
_CLASS_STEP = Fraction(3, 100)
_CLASS_CAP = Fraction(27, 100)


def _scaled_policy(upm: int) -> tuple[int, int]:
    step = max(1, round(_CLASS_STEP * upm))
    cap = max(step, round(_CLASS_CAP * upm))
    return step, cap


def edit(f, *, base_shift: int) -> bool:
    """Edit caller-owned font, returning False for a byte-preserving no-op."""
    if 'MATH' not in f or 'GSUB' not in f:
        return False
    if 'CFF ' not in f:
        raise ValueError(
            'prime contextualization expects the generated CFF OTF'
        )

    cmap = f.getBestCmap()
    gi = f['MATH'].table.MathGlyphInfo
    icinfo = gi.MathItalicsCorrectionInfo
    ics = dict(
        zip(
            icinfo.Coverage.glyphs, [v.Value for v in icinfo.ItalicsCorrection]
        )
    )
    step, cap = _scaled_policy(f['head'].unitsPerEm)

    # Only encoded mathematical letters participate.  Operators and
    # punctuation follow their own OpenType MATH placement paths.
    classes: dict[int, list[str]] = {}
    for u, g in cmap.items():
        if u < 0x370:
            continue
        try:
            category = unicodedata.category(chr(u))
        except ValueError:
            continue
        if not category.startswith('L'):
            continue
        ic = int(ics.get(g, 0) or 0)
        klass = max(0, min(cap, int(round(ic / step)) * step))
        if abs(klass - base_shift) < step / 2:
            continue
        classes.setdefault(klass, []).append(g)
    if not classes:
        return False

    primes = [
        g
        for g in (
            cmap.get(0x2032), cmap.get(0x2033), cmap.get(0x2034),
            cmap.get(0x2057)
        )
        if g
    ]
    if not primes:
        return False

    # Create translated copies.  Width and left sidebearing move by the
    # same delta, so the outline-to-advance
    # relationship is preserved exactly.
    cff = f['CFF '].cff[f['CFF '].cff.fontNames[0]]
    cs = cff.CharStrings
    hmtx = f['hmtx']
    order = f.getGlyphOrder()
    glyphset = f.getGlyphSet()
    made: dict[tuple[str, int], str] = {}
    for klass in sorted(classes):
        dx = klass - int(base_shift)
        for src in primes:
            new = f'{src}.ic{klass}'
            rp = RecordingPen()
            glyphset[src].draw(rp)
            width = hmtx[src][0] + dx
            pen = T2CharStringPen(width - cs[src].private.nominalWidthX, None)
            rp.replay(TransformPen(pen, (1, 0, 0, 1, dx, 0)))
            chstr = pen.getCharString(
                private=cs[src].private, globalSubrs=cs.globalSubrs
            )
            cs.charStringsIndex.append(chstr)
            cs.charStrings[new] = len(cs.charStringsIndex) - 1
            hmtx[new] = (width, hmtx[src][1] + dx)
            order.append(new)
            made[(src, klass)] = new
    f.setGlyphOrder(order)
    cff.charset = order
    f['maxp'].numGlyphs = len(order)

    gsub = f['GSUB'].table
    lookups = gsub.LookupList.Lookup

    def single_lookup(mapping):
        st = ot.SingleSubst()
        st.mapping = dict(mapping)
        lk = ot.Lookup()
        lk.LookupType = 1
        lk.LookupFlag = 0
        lk.SubTable = [st]
        lk.SubTableCount = 1
        lookups.append(lk)
        return len(lookups) - 1

    def coverage(glyphs):
        c = ot.Coverage()
        c.glyphs = sorted(set(glyphs), key=f.getGlyphID)
        return c

    ctx_indices = []
    for klass in sorted(classes):
        sub_idx = single_lookup({src: made[(src, klass)] for src in primes})
        chain = ot.ChainContextSubst()
        chain.Format = 3
        chain.BacktrackGlyphCount = 1
        chain.BacktrackCoverage = [coverage(classes[klass])]
        chain.InputGlyphCount = 1
        chain.InputCoverage = [coverage(primes)]
        chain.LookAheadGlyphCount = 0
        chain.LookAheadCoverage = []
        rec = ot.SubstLookupRecord()
        rec.SequenceIndex = 0
        rec.LookupListIndex = sub_idx
        chain.SubstLookupRecord = [rec]
        chain.SubstCount = 1
        lk = ot.Lookup()
        lk.LookupType = 6
        lk.LookupFlag = 0
        lk.SubTable = [chain]
        lk.SubTableCount = 1
        lookups.append(lk)
        ctx_indices.append(len(lookups) - 1)
    gsub.LookupList.LookupCount = len(lookups)

    for fr in gsub.FeatureList.FeatureRecord:
        if fr.FeatureTag == 'ccmp':
            fr.Feature.LookupListIndex.extend(ctx_indices)
            fr.Feature.LookupCount = len(fr.Feature.LookupListIndex)
    update_average_width(f)
    return True
