package Siru::Preparador;

# El preparador, la parte que esta en siru: prepara el modelo de un paso
# EXACTAMENTE antes de ejecutarlo. Ver siruviejo/LLAVES.md 12 y 13.
#
# No arma ecuaciones. Las arma libdin (slex/din/din.c), que es el unico
# armador: aca se le pide, por d2datdinc, el sistema de una configuracion
# --el estado de las llaves y el metodo de Runge-Kutta-- y se guarda lo ya
# pedido. Nunca se prepara de antemano: con n llaves hay 2^n configuraciones
# y la corrida pasa por unas pocas.
#
# Es el mismo para todos los casos. Un sistema sin circuito o un circuito
# comun terminan con una sola configuracion; uno con llaves, con las que
# hayan aparecido.
#
# De una configuracion salen hasta tres sistemas:
#
#   instante     el circuito con los estados como datos, mas la condicion de
#                cada llave y las de parada: de ahi sale la configuracion
#                del paso, y si la corrida sigue
#   integracion  el sistema de un paso del metodo
#   equilibrio   el trasvase de carga o de flujo, solo si la configuracion
#                tiene lazos de capacitores o cortes de bobinas
#
# Como los nombra d2datdinc (decision del autor, r1676): un caso de mdin
# --llaves, lazos, cortes o RK vectorial-- deja NOMBRE[-<bits>][-rk<N>].mdin,
# NOMBRE-[<bits>]I.mdin y NOMBRE-[<bits>]E.mdin, y esos son los nombres
# finales, asi que quedan al lado del archivo. Un sistema que no es caso de
# mdin deja NOMBRE-de-d2datdinc.din y -ED<N>.din, que viven en el temporal y
# no dejan rastro.

use Siru::Registro;
use strict;
use warnings;
use Carp qw(croak);
use File::Basename qw(dirname);
use File::Spec;
use File::Temp qw(tempdir);
use Siru::Donde;

# d2datdinc, su tabla y su biblioteca: desde el arbol de fuentes los de
# siru/slex/d2datdinc; instalado, el d2datdinc del PATH (Siru::Donde)
sub _d2datdinc { Siru::Donde::d2datdinc() }

# Desde donde se corre d2datdinc: el directorio del archivo, porque sus
# include son relativos a el; y los que incluyen examples/..., desde el de
# arriba, como corria Siru.
sub _directorio {
    my ($archivo) = @_;
    my $dir = dirname($archivo);
    if (open my $f, '<', $archivo) {
        my $fuente = do { local $/; <$f> };
        $dir = dirname($dir) if $fuente =~ /^\s*#?\s*(?:include|input)\s+examples\//mi;
    }
    return $dir;
}

sub nuevo {
    my ($clase, %op) = @_;
    my $archivo = File::Spec->rel2abs($op{archivo} // croak 'falta el archivo');
    (my $nombre = $archivo) =~ s{\A.*/}{};
    $nombre =~ s/\.[^.]+\z//;
    # directorio: desde donde corren d2datdinc y Calcula (los include y las
    # tablas son relativos a el); por omision el del archivo. Un rerun es un
    # .dat en un temporal y corre desde el directorio del .d original.
    return bless { archivo => $archivo, nombre => $nombre, directorio => $op{directorio} // _directorio($archivo),
                   al_lado => $op{al_lado} // dirname($archivo),
                   tmp => tempdir(CLEANUP => 1), configuraciones => {}, n => 0 }, $clase;
}

# corre d2datdinc con esos argumentos; devuelve (bien, lo que escribio)
sub _d2 {
    my ($self, @args) = @_;
    my @cmd = (_d2datdinc(), @args);
    my $pid = open(my $p, '-|') // croak "no se pudo correr d2datdinc: $!";
    if (!$pid) {
        chdir $self->{directorio} or exit 126;
        open STDERR, '>&', \*STDOUT;
        exec { $cmd[0] } @cmd or exit 127;
    }
    my $salida = do { local $/; <$p> } // '';
    my $bien = close $p;
    Siru::Registro::escribe('d2datdinc', $salida);
    return ($bien, $salida);
}

# "S1=1,S2=0" de los digitos de una configuracion, en el orden de las llaves
sub _llaves_de {
    my ($self, $bits) = @_;
    my @ll = $self->llaves;
    croak "configuracion '$bits' con otro numero de llaves" unless length($bits) == @ll;
    return join ',', map { "$ll[$_]=" . substr($bits, $_, 1) } 0 .. $#ll;
}

# Que hay en el circuito: las llaves, y en una configuracion los lazos de
# capacitores y los cortes de bobinas. Lo dice libdin; aca no se mira ninguna
# rama. Sin $bits y con llaves, los lazos y cortes son los de todas abiertas
# y 'configuracion' dice 'incompleta'.
sub topologia {
    my ($self, $bits) = @_;
    my $clave = $bits // '?';
    return $self->{topologias}{$clave} if $self->{topologias}{$clave};
    my @args = ($self->{archivo}, '--topologia');
    push @args, '--llaves', $self->_llaves_de($bits) if defined($bits) && length($bits);
    my ($bien, $salida) = $self->_d2(@args);
    croak "$self->{nombre}: $salida" unless $bien;
    my %t;
    for my $l (split /\n/, $salida) {
        my ($k, @v) = split ' ', $l;
        next unless defined $k;
        $t{$k} = \@v;
    }
    croak "$self->{nombre}: d2datdinc no dijo la topologia: $salida" unless $t{ramas};
    $t{ramas} = $t{ramas}[0] // 0;
    $t{configuracion} = $t{configuracion}[0] // '';
    $t{paradas} = ($t{paradas} || [])->[0] // 0;
    $t{$_} //= [] for qw(llaves lazos cortes estados islas);
    return $self->{topologias}{$clave} = \%t;
}

sub llaves { my ($self) = @_; return @{ $self->topologia->{llaves} } }

# Los .din de una configuracion: $bits, un digito por llave (1 cerrada) en el
# orden de llaves(), y $rk, el numero del metodo. Se preparan la primera vez
# que se piden.
sub configuracion {
    my ($self, $bits, $rk, $bdf2, $glm, $pglm) = @_;
    $glm = 20 if $bdf2;
    my $clave = "$bits|$rk|" . (defined($pglm) ? "PGLM$pglm" : defined($glm) ? "GLM$glm" : 'RK');
    return $self->{configuraciones}{$clave} if $self->{configuraciones}{$clave};
    my $dir = File::Spec->catdir($self->{tmp}, 'c' . ++$self->{n});
    mkdir $dir or croak "no se pudo crear $dir: $!";
    my @args = ('--directorio', $dir, $self->{archivo},
                (defined($pglm) ? ('--din-pglm', $pglm) : defined($glm) ? ('--din-glm', $glm) : ('--din', $rk)));
    push @args, '--llaves', $self->_llaves_de($bits) if length $bits;
    my ($bien, $salida) = $self->_d2(@args);
    $salida =~ s/\s+\z//;
    croak "$self->{nombre}: no se pudo preparar la configuracion"
        . (length($bits) ? " $bits" : '') . ": $salida" unless $bien;
    my %c = (bits => $bits, rk => $rk, aviso => $salida);
    opendir my $dh, $dir or croak "no se pudo leer $dir: $!";
    my @hay = sort grep { /\.(?:din|mdin)\z/ } readdir $dh;
    closedir $dh;
    for my $f (@hay) {
        my $ruta = File::Spec->catfile($dir, $f);
        if ($f =~ /\.mdin\z/) {
            # un caso de mdin: los nombres son los finales, van al lado
            $c{mdin} = 1;
            my $final = File::Spec->catfile($self->{al_lado}, $f);
            rename($ruta, $final) or croak "no se pudo dejar $final: $!";
            $ruta = $final;
            # NOMBRE-[bits]I.mdin y NOMBRE-[bits]E.mdin, con el guion: circuitoORI.mdin
            # (la integracion) termina en I.mdin y se tomaba por el instante
            if    ($f =~ /-[01]*I\.mdin\z/) { $c{instante} = $ruta }
            elsif ($f =~ /-[01]*E\.mdin\z/) { $c{equilibrio} = $ruta }
            # el punto de operacion DC de una configuracion con lazos o cortes
            elsif ($f =~ /-[01]*D\.mdin\z/) { $c{puntodc} = $ruta }
            else                        { $c{integracion} = $ruta }
        } else {
            if    ($f =~ /-ED[0-9]+\.din\z/ || (defined($pglm) && $f =~ /-PGLM[0-9]+\.din\z/) || (defined($glm) && $f =~ /-(?:BDF2(?:-DIRECTO)?|GLM[0-9]+)\.din\z/))
                { $c{integracion} = $ruta }
            elsif ($f =~ /-de-d2datdinc\.din\z/) { $c{instante} = $ruta }
        }
    }
    croak "$self->{nombre}: d2datdinc no escribio el sistema del metodo $rk"
        . (length($salida) ? ": $salida" : '') unless $c{integracion} && -s $c{integracion};
    return $self->{configuraciones}{$clave} = \%c;
}

sub configuracion_glm {
    my ($self, $bits, $id) = @_;
    return $self->configuracion($bits, 0, 0, $id);
}

sub configuracion_pglm {
    my ($self,$bits,$id)=@_;
    return $self->configuracion($bits,0,0,undef,$id);
}

sub configuracion_ros {
    my ($self,$id)=@_;
    return $self->_configuracion_campo('ROS',$id,'--din-ros',$id);
}

sub configuracion_split {
    my ($self,$id)=@_;
    return $self->_configuracion_campo('SPLIT',$id,'--din');
}

sub _configuracion_campo {
    my ($self,$familia,$id,@opciones)=@_;
    return $self->{configuraciones}{"$familia$id"} if $self->{configuraciones}{"$familia$id"};
    my $dir=File::Spec->catdir($self->{tmp},'c'.++$self->{n});
    mkdir $dir or croak "no se pudo crear $dir: $!";
    my ($bien,$salida)=$self->_d2(@opciones,'--directorio',$dir,$self->{archivo});
    croak "$self->{nombre}: no se pudo preparar $familia: $salida" unless $bien;
    my $ruta=File::Spec->catfile($dir,"$self->{nombre}-de-d2datdinc-$familia$id.din");
    croak "d2datdinc no escribio $ruta" unless -s $ruta;
    return $self->{configuraciones}{"$familia$id"}={integracion=>$ruta};
}

1;
