#!/bin/bash
# contra-ds2dat: d2datdinc contra Simulparser sobre muchos archivos.
#
#   pruebas/contra-ds2dat [-v] ARCHIVO|DIRECTORIO...
#
# Cada .ds se pasa por ds2dat y por d2datdinc; cada .d, por d2ds + ds2dat
# y por d2datdinc. Los dos datos se comparan como JSON, sin OTHERINFO (tiene
# rutas) y sin lo que agrega d2datdinc a proposito: GLOBAL.perlfunctions
# (DATABLOB a ExprTk) y GLOBAL.tablas; las referencias TABLE X (ARG) del
# .d se comparan ya traducidas a Table('X.txt',...) y las rutas de Table()
# por el nombre del archivo. Se corre desde el directorio del archivo, o
# desde el de arriba si incluye examples/... (como corria Siru). Sin
# referencia (d2ds o ds2dat fallan) no se compara. -v muestra cada
# diferencia. Estado 0 si no hay diferencias.
#
# Copyright (C) Diego Saravia <dsa@unsa.edu.ar>
# GPL version 3 o posterior.

AQUI=$(cd "$(dirname "$0")/.." && pwd)
SP=$(cd "$AQUI/../../Simulparser" && pwd)
E="${D2DATDINC:-$AQUI/d2datdinc} -c $AQUI/d2datdinc.slex -b $AQUI/biblioteca"
detalle=0
[ "$1" = -v ] && { detalle=1; shift; }
[ $# -gt 0 ] || { echo "uso: contra-ds2dat [-v] ARCHIVO|DIRECTORIO..." >&2; exit 2; }
T=$(mktemp -d)
trap 'rm -rf "$T"' EXIT

cat > "$T/cmp.py" <<'PY'
import json, sys, re, os
a, b = (json.load(open(f)) for f in sys.argv[1:3])
for d in (a, b): d.pop('OTHERINFO', None)
a.get('GLOBAL', {}).pop('perlfunctions', None)
a.get('GLOBAL', {}).pop('tablas', None)
def tabd(m):
    arg = m.group(2).strip()
    return "Table('%s.txt','X','Y',%s,1)" % (m.group(1), 'T' if arg.upper() == 'TIME' else arg)
def norm(v, ref):
    if isinstance(v, dict): return {k: norm(x, ref) for k, x in v.items()}
    if isinstance(v, list): return [norm(x, ref) for x in v]
    # Los numeros, a 15 cifras significativas. El dato de aca los escribe con
    # 17, que es lo que hace falta para volver a leer un doble; Perl convierte
    # un doble a texto con 15 y sus codificadores JSON lo hacen asi, de modo
    # que no puede dar mas. La diferencia es de un ulp y no cambia ningun
    # resultado: se compara hasta donde los dos pueden decir lo mismo.
    if isinstance(v, float): return float('%.15g' % v)
    if isinstance(v, str):
        if ref: v = re.sub(r"(?i)(?<![A-Za-z0-9])TABLE\s*([A-Za-z][A-Za-z0-9]*)\s*\(([^()]*)\)", tabd, v)
        v = re.sub(r"Table\(\s*'([^']*)'", lambda m: "Table('" + os.path.basename(m.group(1)) + "'", v)
    return v
a, b = norm(a, False), norm(b, True)
def diff(x, y, p):
    if type(x) != type(y) and not (isinstance(x, (int, float)) and isinstance(y, (int, float))):
        print(p, 'tipo:', repr(x)[:150], '|', repr(y)[:150]); return
    if isinstance(x, dict):
        for k in sorted(set(x) | set(y)):
            if k not in x: print(p + '/' + k, 'falta:', repr(y[k])[:150])
            elif k not in y: print(p + '/' + k, 'sobra:', repr(x[k])[:150])
            else: diff(x[k], y[k], p + '/' + k)
    elif isinstance(x, list):
        if len(x) != len(y): print(p, 'largo', len(x), len(y)); return
        for i, (u, v) in enumerate(zip(x, y)): diff(u, v, p + '[%d]' % i)
    elif x != y: print(p, repr(x)[:200], '|', repr(y)[:200])
diff(a, b, '')
PY

iguales=0; distintos=0; sin_ref=0; solo_e10=0
while IFS= read -r f; do
  dir=$(dirname "$f")
  grep -qiE '^[[:space:]]*#?[[:space:]]*(include|input)[[:space:]]+examples/' "$f" && dir=$(dirname "$dir")
  if [ "${f##*.}" = d ]; then
    ( cd "$dir" && "$SP/bin/d2ds" "$f" -o "$T/r.ds2" >/dev/null 2>&1 && "$SP/bin/ds2dat" "$T/r.ds2" -o "$T/r.dat" >/dev/null 2>&1 )
  else
    ( cd "$dir" && "$SP/bin/ds2dat" "$f" -o "$T/r.dat" >/dev/null 2>&1 )
  fi
  hay_ref=$?
  ( cd "$dir" && $E "$f" -o "$T/e.dat" 2>"$T/e.err" ); r=$?
  if [ $hay_ref -ne 0 ]; then
    sin_ref=$((sin_ref+1))
    [ $r -eq 0 ] && { solo_e10=$((solo_e10+1)); echo "sin referencia, d2datdinc si: $f"; }
    rm -f "$T/r.dat"; continue
  fi
  if [ $r -ne 0 ]; then distintos=$((distintos+1)); echo "d2datdinc falla: $f: $(head -c 200 "$T/e.err")"; continue; fi
  python3 "$T/cmp.py" "$T/e.dat" "$T/r.dat" > "$T/d"
  if [ -s "$T/d" ]; then
    distintos=$((distintos+1)); echo "distinto: $f ($(wc -l < "$T/d") diferencias)"
    [ $detalle -eq 1 ] && sed 's/^/    /' "$T/d"
  else iguales=$((iguales+1)); fi
  rm -f "$T/r.dat" "$T/e.dat"
done < <(for a in "$@"; do
  if [ -d "$a" ]; then find "$(cd "$a" && pwd)" \( -name '*.d' -o -name '*.ds' \) -type f -not -path '*/.svn/*'
  else echo "$(cd "$(dirname "$a")" && pwd)/$(basename "$a")"; fi
done | sort -u)

echo "iguales $iguales, distintos $distintos, sin referencia $sin_ref"
exit $((distintos > 0))
