#!/usr/bin/env python3
# probar-simul1: corre Simul 1 sobre ejemplos del corpus y anota el resultado
# al lado de cada uno, en NOMBRE.simul1.
#
# Uso: probar-simul1 [--tiempo S] ARCHIVO.d|.ds|DIRECTORIO ...
#
# Corre Simul (siru/siruviejo/perl/scripts/execu.pl) en un directorio propio, no
# en siruviejo/corrida, para no pisar otras corridas. El dato es el .dat
# congelado del ejemplo (el de un .d que convive con un .ds del mismo nombre
# es NOMBRE-desde-d.dat); si no hay, lo genera d2datdinc en un temporal.
# Cada corrida tiene un tiempo maximo (180 s por omision).
#
# NOMBRE.simul1 dice:
#   resultado   llega al final | corta | no arranca | tiempo agotado
#   hasta       el ultimo T que escribio, y el TIEMPOFINAL del dato
#   pasos       filas del .simul
#   error       el primer error de Simul que no es ruido conocido, o el
#               ultimo renglon de su salida de errores
#   dato        congelado o generado

import glob, json, os, re, shutil, subprocess, sys, tempfile, time

CORPUS = os.path.dirname(os.path.abspath(__file__))
SIRU = os.path.dirname(os.path.dirname(CORPUS))
SIMUL = os.path.join(SIRU, 'siruviejo')
D2 = os.path.join(SIRU, 'slex', 'd2datdinc')
RUTAS = ['perl/lib', 'varios/Calcula/perl/lib', 'varios/Circuito/perl/lib',
         'varios/ExprTk/perl/lib', 'varios/GSL/perl/lib', 'varios/Siru/perl/lib',
         'varios/PackProj/perl/lib', 'construido/lib']
# errores que Simul escribe en toda corrida, aun las que andan bien
RUIDO = re.compile(r'ERROR (ecuark|No definidos en prepost|Pruebapasos no definido|tipdifvar .* es free|Safe)')


def fuentes(args):
    for a in args:
        if os.path.isdir(a):
            yield from sorted(f for f in glob.glob(os.path.join(a, '*')) if f.endswith(('.d', '.ds')))
        else:
            yield a


def dato(fuente, tmp):
    base, ext = os.path.splitext(fuente)
    otro = base + ('.ds' if ext == '.d' else '.d')
    congelado = base + ('-desde-d' if ext == '.d' and os.path.exists(otro) else '') + '.dat'
    if os.path.exists(congelado):
        return congelado, 'congelado'
    salida = os.path.join(tmp, os.path.basename(base) + '.dat')
    desde = os.path.dirname(fuente)
    texto = open(fuente, encoding='utf-8', errors='replace').read()
    if re.search(r'^\s*#?\s*(include|input)\s+examples/', texto, re.M | re.I):
        desde = os.path.dirname(desde)
    r = subprocess.run([os.path.join(D2, 'd2datdinc'), '-c', os.path.join(D2, 'd2datdinc.slex'),
                        '-b', os.path.join(D2, 'biblioteca'), os.path.abspath(fuente), '-o', salida],
                       cwd=desde, capture_output=True, text=True)
    if r.returncode != 0 or not os.path.exists(salida):
        return None, 'd2datdinc no lo traduce: ' + (r.stderr.strip().splitlines() or [''])[-1]
    return salida, 'generado'


def correr(fuente, limite):
    res = {'archivo': os.path.relpath(fuente, CORPUS)}
    tmp = tempfile.mkdtemp(prefix='simul1-')
    try:
        dat, como = dato(fuente, tmp)
        res['dato'] = como
        if dat is None:
            res['resultado'] = 'no arranca'
            return res
        try:
            final = float(json.load(open(dat))['GLOBAL'].get('defpar', {}).get('TIEMPOFINAL', {}).get('value', 'nan'))
        except Exception:
            final = float('nan')
        orden = ['perl'] + [a for r in RUTAS for a in ('-I', os.path.join(SIMUL, r))] + \
                [os.path.join(SIMUL, 'perl', 'scripts', 'execu.pl'), os.path.abspath(dat)]
        entorno = dict(os.environ, PERL_HASH_SEED='0', PERL_PERTURB_KEYS='0')
        t0 = time.time()
        with open(os.path.join(tmp, 'salida.txt'), 'w') as so, open(os.path.join(tmp, 'errores.txt'), 'w') as se:
            try:
                subprocess.run(orden, cwd=tmp, stdout=so, stderr=se, timeout=limite, env=entorno)
                agotado = False
            except subprocess.TimeoutExpired:
                agotado = True
        res['segundos'] = '%.0f' % (time.time() - t0)
        filas, ultimo = 0, None
        simul = sorted(glob.glob(os.path.join(tmp, 'results', '*.simul')), key=os.path.getmtime)
        if simul:
            for l in open(simul[-1], errors='replace'):
                if l.strip() and not l.startswith('#'):
                    c = l.split()
                    if len(c) > 1:
                        filas += 1
                        try: ultimo = float(c[1])
                        except ValueError: pass
        res['pasos'] = str(filas)
        res['hasta'] = ('T=%g' % ultimo if ultimo is not None else 'sin filas') + \
            (', TIEMPOFINAL=%g' % final if final == final else '')
        error = None
        for l in open(os.path.join(tmp, 'salida.txt'), errors='replace'):
            if l.startswith('ERROR') and not RUIDO.match(l):
                error = l.strip()[:200]
                break
        if not error:
            ult = [l.strip() for l in open(os.path.join(tmp, 'errores.txt'), errors='replace')
                   if l.strip() and 'Use of uninitialized' not in l]
            if ult: error = ult[-1][:200]
        if error: res['error'] = error
        if agotado:
            res['resultado'] = 'tiempo agotado (%d s)' % limite
        elif ultimo is None:
            res['resultado'] = 'no arranca'
        elif final == final and ultimo >= final - 1e-6 * max(1, abs(final)) - 0.2:
            res['resultado'] = 'llega al final'
        else:
            res['resultado'] = 'corta'
        return res
    finally:
        shutil.rmtree(tmp, ignore_errors=True)


def main():
    args, limite = sys.argv[1:], 180
    if args[:1] == ['--tiempo']:
        limite, args = int(args[1]), args[2:]
    for f in fuentes(args):
        r = correr(os.path.abspath(f), limite)
        base, ext = os.path.splitext(f)
        otro = base + ('.ds' if ext == '.d' else '.d')
        salida = base + ('-ds' if ext == '.ds' and os.path.exists(otro) else '') + '.simul1'
        with open(salida, 'w') as o:
            o.write('# Simul 1 sobre este ejemplo, escrito por probar-simul1 (no editar a mano)\n')
            for k in ('archivo', 'resultado', 'hasta', 'pasos', 'error', 'dato', 'segundos'):
                if k in r: o.write('%s: %s\n' % (k, r[k]))
        print('%-60s %s%s' % (r['archivo'], r['resultado'], ('  [' + r['error'][:70] + ']') if 'error' in r else ''))
        sys.stdout.flush()


if __name__ == '__main__':
    main()
