#!/usr/bin/env python3
"""Comprueba, contra git, que en el repo solo ha cambiado lo previsto.

Este barrido ha cazado las tres tandas de destrozos de la auditoria
('seminaaaaa', 'foodstuffsstuffs', 'Śikṣāṣṭakamm', medio parrafo duplicado en
cc-madhya-8.36). Se pasa SIEMPRE despues de aplicar, sin excepcion, porque el
aplicador puede estar convencido de haber hecho lo correcto y haber escrito dos
veces sobre la misma posicion.

Que mira, por fichero modificado:
  1. Palabras NUEVAS que no estaban en HEAD. Con un aplicador de supresiones no
     deberia aparecer casi ninguna: solo las que cambian de mayuscula al quedar
     abriendo frase. Cualquier otra es sospechosa.
  2. Palabras nuevas con una letra repetida tres veces ('towardss', 'upwardsss')
     o de mas de 22 caracteres: la firma de una doble aplicacion.
  3. Palabras DESAPARECIDAS que no figuran en la lista de lo que se iba a
     suprimir. Si el aplicador se ha llevado algo por delante, sale aqui.
  4. Marcado descompensado: asteriscos, parentesis o corchetes impares en un
     parrafo que antes estaba equilibrado.

Uso:
  python3 barrido_integridad.py                      # solo las alarmas
  python3 barrido_integridad.py --esperado supresiones.json
"""
import json, re, sys, subprocess, pathlib, collections

HERE = pathlib.Path(__file__).resolve().parent
REPO = pathlib.Path('/Users/jaganat/git_projects/vedabase-original')
TRIPLE = re.compile(r'(.)\1\1')
PAL = re.compile(r"[\w'’-]+", re.UNICODE)


def git(*args):
    return subprocess.run(['git', *args], capture_output=True, text=True,
                          cwd=str(REPO)).stdout


def modificados():
    out = []
    for l in git('status', '--porcelain').splitlines():
        f = l[3:].strip()
        if f.endswith('.md'): out.append(f)
    return out


def desequilibrio(t):
    """Marcado impar por parrafo. Devuelve los parrafos que se han roto.

    Los asteriscos ESCAPADOS (`\\*`) no son cursiva: son el marcador de nota al
    pie del impreso, que hay que escapar justamente para que markdown no lo lea
    como enfasis. Contarlos daba por roto un parrafo correcto (sb-7.9.34)."""
    t = t.replace('\\*', '')
    malos = []
    for i, par in enumerate(t.split('\n\n')):
        if par.count('*') % 2 or par.count('(') != par.count(')') \
           or par.count('[') != par.count(']'):
            malos.append(i)
    return set(malos)


def main():
    esperado = collections.defaultdict(set)
    if '--esperado' in sys.argv:
        p = sys.argv[sys.argv.index('--esperado') + 1]
        for x in json.load(open(HERE / p, encoding='utf-8')):
            for w in PAL.findall(x.get('borrado', '')):
                esperado[x['file']].add(w.lower())

    fich = modificados()
    print(f'ficheros modificados: {len(fich)}')
    alarmas = collections.Counter(); detalle = []

    for f in fich:
        viejo = git('show', f'HEAD:{f}')
        if not viejo: continue
        nuevo = (REPO / f).read_text(encoding='utf-8')
        pv = collections.Counter(w.lower() for w in PAL.findall(viejo))
        pn = collections.Counter(w.lower() for w in PAL.findall(nuevo))

        aparecen = {w: c for w, c in (pn - pv).items()}
        desaparecen = {w: c for w, c in (pv - pn).items()}

        for w in aparecen:
            # Una palabra larga solo es sospechosa si NO lleva guion: los
            # compuestos sanscritos legitimos pasan de 22 caracteres sin
            # problema ('andantara-stha-paramanu-cayantara-stham').
            if TRIPLE.search(w) or (len(w) > 22 and '-' not in w):
                alarmas['palabra con firma de doble aplicacion'] += 1
                detalle.append((f, 'DOBLE APLICACION', w))
            else:
                alarmas['palabra nueva (revisar)'] += 1
                detalle.append((f, 'nueva', w))
        for w in desaparecen:
            if esperado and w not in esperado.get(f, set()):
                alarmas['palabra desaparecida que no estaba prevista'] += 1
                detalle.append((f, 'DESAPARECIDA NO PREVISTA', w))

        rotos = desequilibrio(nuevo) - desequilibrio(viejo)
        if rotos:
            alarmas['marcado descompensado'] += len(rotos)
            detalle.append((f, 'MARCADO ROTO', f'{len(rotos)} parrafo(s)'))

    if not alarmas:
        print('\nlimpio: no ha cambiado nada que no estuviera previsto')
        return 0
    print()
    for k, v in alarmas.most_common(): print(f'  {v:5}  {k}')
    print()
    graves = [d for d in detalle if d[1].isupper()]
    for f, tipo, w in (graves or detalle)[:60]:
        print(f'  {tipo:26} {w!r}   {f}')
    if len(detalle) > 60: print(f'  ... y {len(detalle) - 60} mas')
    json.dump([{'file': f, 'tipo': t, 'palabra': w} for f, t, w in detalle],
              open(HERE / 'barrido_integridad.json', 'w'), ensure_ascii=False, indent=1)
    return 1 if graves else 0


if __name__ == '__main__':
    sys.exit(main())
