#!/usr/bin/perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../lib";
use File::Basename qw(dirname);
use File::Spec;
use Getopt::Long qw(GetOptions);
use File::Temp qw(tempdir);
use Siru::Calcula;
use Siru::Integrador;

# NOMBRE.informe, al lado del archivo de entrada: como termino la corrida, por
# cualquiera de los caminos, bien o mal. siru/corpus/clasificar lo lleva al .tipo
# (campo siru). Si no converge, ademas un mensaje que lo diga en la pantalla,
# y no solo el volcado del solver.
our ($INFORME, $FILAS, $TIEMPOFINAL, $ULTIMO_ERROR, %ESTADISTICAS_ROS);
our %ESTADISTICAS_INTEGRACION;
$SIG{__DIE__} = sub { $ULTIMO_ERROR = $_[0] unless $^S };
END {
    my $estado = $?;
    if (defined $INFORME) {
        my ($resultado, $hasta, $error);
        if ($estado == 0 && !defined $ULTIMO_ERROR) {
            $resultado = 'llega al final';
            $hasta = defined $FILAS ? "$FILAS filas" : undef;
            $hasta .= ", TIEMPOFINAL=$TIEMPOFINAL" if defined $hasta && defined $TIEMPOFINAL;
        } else {
            ($error = $ULTIMO_ERROR // "estado $estado") =~ s/\s+\z//;
            $error =~ s/\n.*//s;
            if ($error =~ /^paso (\d+), t=([^:]+): (.*)/) {
                my ($paso, $t, $motivo) = ($1, $2 + 0, $3);
                $resultado = $paso > 0 ? 'NO CONVERGE' : 'no arranca';
                # el tope de pasos no es una falla de Newton: la corrida anduvo
                # hasta ahi (Pared.d con la mitad del paso)
                $resultado = 'se corta' if $motivo =~ /se alcanzo max_pasos/;
                $hasta = "paso $paso, t=$t" . (defined $TIEMPOFINAL ? " de TIEMPOFINAL=$TIEMPOFINAL" : '');
                $error = $motivo;
                print STDERR "siru: $resultado en t=$t (paso $paso)\n";
            } else {
                $resultado = 'no arranca';
                print STDERR "siru: no arranca\n";
            }
        }
        Siru::Registro::cerrar();
        Siru::Registro::escribe('siru', "resultado: $resultado" . (defined $hasta ? ", $hasta" : '')
                                  . (defined $error ? "; $error" : '') . "\n");
        if (open my $o, '>', $INFORME) {
            print $o "# siru sobre este archivo, escrito por bin/siru al terminar\n";
            print $o "resultado: $resultado\n";
            print $o "hasta: $hasta\n" if defined $hasta;
            print $o "error: $error\n" if defined $error;
            if (%ESTADISTICAS_ROS) {
                require JSON::PP;
                print $o "estadisticas ROS: ",JSON::PP->new->canonical->encode(\%ESTADISTICAS_ROS),"\n";
            }
            if (%ESTADISTICAS_INTEGRACION) {
                require JSON::PP;
                print $o "estadisticas ultima integracion: ",
                    JSON::PP->new->canonical->encode(\%ESTADISTICAS_INTEGRACION),"\n";
            }
            # el estado al terminar, para seguir con --continuar
            no warnings 'once';     # se escribe en Circuito y se lee solo aca
            if (my $ef = $Siru::Circuito::ESTADO_FINAL) {
                print $o "estado final: t=$ef->{t}", (map { " $_=$ef->{estados}{$_}" } sort keys %{ $ef->{estados} }), "\n";
                print $o "parametros impuestos:", (map { " $_=$ef->{impuestos}{$_}" } sort keys %{ $ef->{impuestos} }), "\n"
                    if %{ $ef->{impuestos} // {} };
            }
            close $o;
        }
    }
    $? = $estado;
}

sub ruta {
    my ($base, $valor) = @_;
    return File::Spec->rel2abs($valor, $base);
}

my ($salida, $calcula, $bibliotecas, $plazo, $ayuda, $continuar, $hasta, $factor, $nosimplot, $documento);
my $uso = "Uso: $0 [--salida ARCHIVO] [--calcula EJECUTABLE] [--bibliotecas DIR] [--plazo SEGUNDOS] [--paso-factor F] CIRCUITO.d|.ds|.dat|SISTEMA.din\n"
        . "     $0 --continuar [--hasta T] CIRCUITO.d|.ds|.dat   sigue la corrida terminada (NOMBRE.informe) hasta T, otro bloque en NOMBRE.simul\n"
        . "     -nosimplot: no llamar a Simplot al terminar (por defecto grafica la salida)\n"
        . "     --documento: emitir por stdout el .d/.ds con TABLE NOMBRE (del caso); diagnosticos por stderr\n";
GetOptions('salida=s' => \$salida, 'calcula=s' => \$calcula,
    'bibliotecas=s' => \$bibliotecas, 'plazo=f' => \$plazo, 'ayuda|help' => \$ayuda,
    'continuar' => \$continuar, 'hasta=f' => \$hasta, 'paso-factor=f' => \$factor,
    'nosimplot' => \$nosimplot, 'documento' => \$documento)
    or die $uso;
# --plazo: cuanto se espera cada respuesta de Calcula (un punto, un paso).
# Por omision 30 s en una corrida y 10 s en un punto; un circuito que Newton
# tarda en resolver (a13z.d, pedido del autor 30/09/2026) necesita mas.
die "--plazo debe ser un numero positivo\n" if defined $plazo && !($plazo > 0);
# --paso-factor F: la corrida con el paso por F (adaptivo: las tolerancias),
# para ver si el resultado cambia al achicarlo; lo aplica Siru::Din
# (SIRU_PASO_FACTOR). Lo usa siru/corpus/convergencia.
die "--paso-factor debe ser un numero positivo\n" if defined $factor && !($factor > 0);
$ENV{SIRU_PASO_FACTOR} = $factor if defined $factor;
if ($ayuda) { print $uso; exit 0; }
die $uso unless @ARGV == 1;
my $archivo = File::Spec->rel2abs($ARGV[0]);
die "--documento requiere .d o .ds y no admite --continuar\n"
    if $documento && ($continuar || $archivo !~ /\.(?:d|ds)\z/i);
my $stdout_documento;
if ($documento) {
    open $stdout_documento, '>&', \*STDOUT or die "no se pudo guardar stdout: $!\n";
    open STDOUT, '>&', \*STDERR or die "no se pudo separar diagnosticos: $!\n";
}
my $base = dirname($archivo);
($INFORME = $archivo) =~ s/\.[^.\/]+\z//;
require Siru::Registro;
Siru::Registro::abrir("$INFORME.log");     # NOMBRE.log: el stderr de todos
$INFORME .= '.informe';
# Si la salida ya existe no se pisa: se escribe NOMBRE-1.simul, -2... y el
# informe lleva el mismo numero (Siru::Salida->libre).
require Siru::Salida;
sub salida_libre {
    $salida = Siru::Salida->libre($salida);
    ($INFORME = $salida) =~ s/\.[^.\/]+\z//;
    $INFORME .= '.informe';
}
sub graficar {
    return if $nosimplot;
    require Siru::Graficos;
    Siru::Graficos->hacer($archivo, $salida, $_[0]);
}
sub emitir_documento {
    return unless $documento;
    require Siru::Documento;
    open my $f, '<:raw', $archivo or die "no se pudo leer $archivo: $!\n";
    my $fuente = do { local $/; <$f> };
    close $f;
    open my $t, '<:raw', $salida or die "no se pudo leer $salida: $!\n";
    my $tabla = do { local $/; <$t> };
    close $t;
    print {$stdout_documento} Siru::Documento::componer($fuente, $tabla, Siru::Documento::nombre_tabla($archivo))
        or die "no se pudo escribir el documento en stdout: $!\n";
}
if ($continuar) {
    # --continuar: sigue la corrida que termino, desde el estado final que
    # bin/siru dejo en NOMBRE.informe, hasta --hasta T (o lo que diga la
    # seccion CONTINUE del archivo); otro bloque en el mismo NOMBRE.simul
    die "--continuar es para un .d, .ds o .dat\n" unless $archivo =~ /\.(?:d|ds|ds2|dat)\z/i;
    ($salida = $archivo) =~ s/\.[^.]+\z/.simul/ unless defined $salida;
    ($INFORME = $salida) =~ s/\.[^.\/]+\z/.informe/;
    open my $i, '<', $INFORME or die "no hay $INFORME: hay que correr $archivo antes de continuarlo\n";
    my @lineas = <$i>;
    close $i;
    my ($estado) = grep { /^estado final: / } @lineas;
    my ($impuestos) = grep { /^parametros impuestos: / } @lineas;
    die "$INFORME no trae el estado final (la corrida no termino bien)\n" unless defined $estado;
    my %e = map { split /=/, $_, 2 } split ' ', ($estado =~ s/^estado final: //r);
    my %imp = defined $impuestos ? (map { split /=/, $_, 2 } split ' ', ($impuestos =~ s/^parametros impuestos: //r)) : ();
    my $t = delete $e{t};
    die "--hasta $hasta no es posterior a t=$t\n" if defined $hasta && !($hasta > $t);
    require Siru::Circuito;
    my ($caso, $dato, $preparador) = Siru::Circuito->caso($archivo);
    require Siru::Dibujo;
    Siru::Dibujo->hacer($archivo, $dato);
    die "$archivo es un solo punto, no hay corrida que continuar\n" if $caso == 4;
    my %donde = (calcula => (defined($calcula) ? File::Spec->rel2abs($calcula) : 'calcula'),
        bibliotecas => (defined($bibliotecas) ? File::Spec->rel2abs($bibliotecas) : undef));
    $FILAS = Siru::Circuito->continuar(archivo => $archivo, salida => $salida, dato => $dato, preparador => $preparador,
        estado => { t => $t, estados => \%e, impuestos => \%imp }, hasta => $hasta, cambios => $dato->{GLOBAL}{continuar},
        (defined $plazo ? (plazo => $plazo) : ()), %donde);
    $TIEMPOFINAL = $hasta;
    graficar($dato);
    exit 0;
}
if ($archivo =~ /\.(?:d|ds|ds2|dat)\z/i) {
    # La salida se nombra por el archivo fuente, no por el .din que se arme
    # despues: ese vive en un directorio temporal que se borra. Asi el .simul
    # queda al lado de la fuente y con su nombre por cualquiera de los caminos.
    ($salida = $archivo) =~ s/\.[^.]+\z/.simul/ unless defined $salida;
    salida_libre();
    # El selector (siruviejo/LLAVES.md 12) dice que caso es, sin armar nada:
    #
    #   1. sin circuito, con derivadas
    #   2. con llaves, lazos de C, cortes de L o varios metodos de RK
    #   3. circuito sin nada de eso
    #   4. un solo punto: no hay estados
    #
    # El 1, el 2 y el 3 se integran, y los corre lo mismo: antes de cada paso
    # se prepara el sistema de la configuracion de ese paso, si no estaba. Que
    # terminen usando un sistema o varios sale de la corrida. El 4 no se
    # integra: Calcula lo resuelve una vez.
    require Siru::Circuito;
    my ($caso, $dato, $preparador) = Siru::Circuito->caso($archivo);
    require Siru::Dibujo;
    Siru::Dibujo->hacer($archivo, $dato);
    $TIEMPOFINAL = eval { $dato->{GLOBAL}{defpar}{TIEMPOFINAL}{value} };
    my %donde = (calcula => (defined($calcula) ? File::Spec->rel2abs($calcula) : 'calcula'),
        bibliotecas => (defined($bibliotecas) ? File::Spec->rel2abs($bibliotecas) : undef));
    if ($caso == 4) {
        # No hay tiempo, ni paso, ni Runge-Kutta: entran los datos, sale la
        # solucion, y ahi termina.
        my $dir = tempdir('.siru-din-XXXXXX', DIR => $base, CLEANUP => 1);
        my ($ruta, $datos) = Siru::Circuito->din_punto($archivo, $dir);
        require Siru::UnPunto;
        Siru::UnPunto->correr(din => $ruta, salida => $salida, datos => $datos, dato => $dato, %donde,
            (defined $plazo ? (plazo => $plazo) : ()),
            directorio => Siru::Preparador::_directorio($archivo));
        graficar($dato);
        emitir_documento();
        exit 0;
    }
    $FILAS = Siru::Circuito->correr_con_reruns(archivo => $archivo, salida => $salida,
        estadisticas => Siru::ROS->lista($dato) ? \%ESTADISTICAS_ROS : \%ESTADISTICAS_INTEGRACION,
        (defined $plazo ? (plazo => $plazo) : ()),
        dato => $dato, preparador => $preparador, %donde);
    graficar($dato);
    emitir_documento();
    exit 0;
}
my $config;
if ($archivo =~ /\.din\z/i) {
    # La corrida esta en el .din, en sus propias secciones de texto llano:
    #
    #   # corrida       desde, hasta, paso, tiempo
    #   # adaptacion    metodo, orden_error, absoluto, relativo, paso_min, paso_max
    #   # parametros    nombre valor
    #   # estados       actual siguiente inicial estimacion    (- si no hay)
    #
    # Van COMENTADAS porque el .din lo lee Calcula, que compila cada linea con
    # ExprTk: lo que no es comentario tiene que ser una ecuacion. Asi la
    # corrida viaja en el mismo archivo sin que Calcula tenga que saber de
    # ella.
    #
    # Sin JSON y sin ninguna ruta: el .din es el archivo que nos dieron, y
    # donde se escribe lo decide quien simula.
    require Siru::Din;
    my $s = Siru::Din->leer($archivo);
    require Siru::SPLIT;
    if (Siru::SPLIT->de_din($s)) {
        ($salida=$archivo) =~ s/\.din\z/.simul/i unless defined $salida;
        salida_libre();
        $TIEMPOFINAL=$s->{corrida}{hasta};
        $FILAS=Siru::SPLIT->correr_din(din=>$archivo,salida=>$salida,
            estadisticas=>\%ESTADISTICAS_INTEGRACION,
            calcula=>defined($calcula)?File::Spec->rel2abs($calcula):'calcula',
            (defined($bibliotecas)?(bibliotecas=>File::Spec->rel2abs($bibliotecas)):()),
            (defined($plazo)?(plazo=>$plazo):()));
        print "Escritas $FILAS filas en $salida\n";
        graficar();
        exit 0;
    }
    require Siru::ROS;
    if (Siru::ROS->de_din($s)) {
        ($salida=$archivo) =~ s/\.din\z/.simul/i unless defined $salida;
        salida_libre();
        $TIEMPOFINAL=$s->{corrida}{hasta};
        $FILAS=Siru::ROS->correr_din(din=>$archivo,salida=>$salida,
            estadisticas=>\%ESTADISTICAS_ROS,
            calcula=>defined($calcula)?File::Spec->rel2abs($calcula):'calcula',
            (defined($bibliotecas)?(bibliotecas=>File::Spec->rel2abs($bibliotecas)):()),
            (defined($plazo)?(plazo=>$plazo):()));
        print "Escritas $FILAS filas en $salida\n";
        graficar();
        exit 0;
    }
    # un .din con elementos K no se puede correr solo: la historia de la
    # convolucion (e_k1, r_k1) la da Circuito en cada paso
    die "El .din tiene elementos K (convolucion): corra el .d o el .ds, que guarda la historia; un .din solo no alcanza\n"
        if @{ $s->{convoluciones} || [] };
    my %corrida = %{$s->{corrida}};
    my %adaptacion = %{$s->{adaptacion}};
    my %parametros = %{$s->{parametros}};
    my @estados = Siru::Din->estados_de_integracion($s);
    die "El .din no tiene seccion estados; regenere el ampliado desde el .d\n"
        unless @estados;
    $config = { %corrida, parametros => \%parametros, estados => \@estados };
    $config->{glm} = Siru::Din->glm_de_din($s);
    $config->{descriptor_glm} = $s->{descriptor_glm};
    require Siru::PGLM;
    $config->{pglm} = Siru::PGLM->de_din($s);
    $config->{descriptor_pglm} = $s->{descriptor_pglm};
    # 'tiempo' nombra la variable del sistema que es el tiempo: no es un
    # parametro fijo, el integrador la pone en cada paso.
    $config->{nombre_tiempo} = delete $config->{tiempo}
        if defined $config->{tiempo};
    # 'pasos' nombra la que recibe la cantidad de pasos dados (TICK)
    $config->{nombre_pasos} = delete $config->{pasos}
        if defined $config->{pasos};
    $config->{adaptacion} = \%adaptacion if %adaptacion;
    $config->{dinamico} = $archivo;
    # La salida es siempre un .simul, por cualquiera de los caminos: el
    # formato no depende de si el sistema pasa por din, por mdin o por un
    # punto solo. Lo elige la extension (ver Integrador::correr).
    ($salida = $archivo) =~ s/\.din\z/.simul/i unless defined $salida;
    salida_libre();
    $config->{salida} = $salida;
    $TIEMPOFINAL //= $config->{hasta};
} else { die $uso; }

# El calcula-maquina: --calcula si lo dieron, y si no el del PATH. La
# configuracion no lo dice, a proposito: un archivo generado no lleva adentro
# la ruta de instalacion de quien lo genero.
$config->{ejecutable} = defined($calcula) ? File::Spec->rel2abs($calcula) : 'calcula';
$config->{bibliotecas} = File::Spec->rel2abs($bibliotecas) if defined $bibliotecas;

die "Falta dinamico en la configuracion\n" unless defined($config->{dinamico});
$config->{dinamico} = ruta($base, $config->{dinamico});
$config->{salida} = defined($salida) ? File::Spec->rel2abs($salida)
    : ruta($base, $config->{salida} // die "Falta salida en la configuracion\n");
my %entorno;
if (defined $config->{bibliotecas}) {
    my $ruta = ruta($base, $config->{bibliotecas});
    $entorno{LD_LIBRARY_PATH} = $ruta . ($ENV{LD_LIBRARY_PATH} ? ":$ENV{LD_LIBRARY_PATH}" : '');
}
my $sesion;
eval {
    $sesion = Siru::Calcula->new(
        ejecutable => $config->{ejecutable}, dinamico => $config->{dinamico},
        entorno => \%entorno, timeout => $plazo // $config->{timeout} // 10,
    );
    require Siru::IntegradorGLM if $config->{glm};
    my $motor = $config->{pglm} ? 'Siru::PGLM' : $config->{glm} ? 'Siru::IntegradorGLM' : 'Siru::Integrador';
    my $filas = $motor->integrar(
        sesion => $sesion,
        parametros => $config->{parametros}, estados => $config->{estados},
        ($config->{glm} ? (glm => $config->{glm}, descriptor => $config->{descriptor_glm}) : ()),
        ($config->{pglm} ? (pglm => $config->{pglm}, descriptor => $config->{descriptor_pglm}) : ()),
        desde => $config->{desde}, hasta => $config->{hasta}, paso => $config->{paso},
        nombre_paso => $config->{nombre_paso}, nombre_tiempo => $config->{nombre_tiempo},
        nombre_pasos => $config->{nombre_pasos},
        max_pasos => $config->{max_pasos}, adaptacion => $config->{adaptacion},
        estadisticas => \%ESTADISTICAS_INTEGRACION,
        salida => $config->{salida},
    );
    $sesion->cerrar();
    $FILAS = $filas;
    print "Escritas $filas filas en $config->{salida}\n";
    1;
} or do {
    my $error = $@ || 'error desconocido';
    $sesion->cerrar() if $sesion;
    die $error;
};
graficar();
