unit uSimSEEOnCluster_fdb_;
// Definiciones para mantenimiento de la información de corridas SimSEE
// en el ClusterUY.
// La idea es que los Robots puedan tener acceso a esta información así
// como la aplicación Cliente (posiblemente el Editor  SimSEE) para
// Lanzar Corridas y poder darles seguimiento.

interface

uses
  Classes, SysUtils;

type
  TEstadoCorrida = (
    EC_Ninguno,  // 0
    EC_Lanzada,  // 1
    EC_Optimizando,  // 2
    EC_Simulando,   // 3
    EC_SimRes3,     // 4
    EC_ResultadosDisponibles, //5
    EC_Abortada);    //6

  TRec_Sala = record
    nid: Longint;
    dtCreacion: TDateTime;
    dtUltimoCambio: TDateTIme;
    activa: shortint;
    korden: integer;
    nombre: shortstring;
  end;

  PRec_Sala = ^TRec_Sala;
  TDAOfRec_Sala = array of TRec_Sala;

  TRec_Corrida = record
    nid: Longint;
    dtCreacion: TDateTime;
    dtUltimoCambio: TDateTIme;
    activa: shortint;
    korden: integer;
    sala: Longint;
    modoRun: integer; // 1 = Opt; 2 = Opt+Sim, 3 = Opt+Sim+SR3
    caso: shortstring;
    plantilla: shortstring;
    JobId: integer;
    estado: TEstadoCorrida;
  end;
  PRec_Corrida = ^TRec_Corrida;
  TDAOfRec_Corrida = array of TRec_Corrida;


var
  HomeDir: string;
  SimSEE_bin: string;  // Carpeta con los binarios de SimSEE
  SimSEE_fdb: string; // carpeta con archivos de generación de números únicos
  // y base de datos de corridas
  SimSEE_slurm: string;  // carpeta con los script de lanzada
  SimSEE_salidas: string; // carpeta con archivos de texto captura de salidas
  SimSEE_salas: string; // carpeta con las Salas ejecutables
  SimSEE_resultados: string; // carpeta con archivos de resultados bajables.


function getNextNID( secuencia: string; reserva: integer = 1): Longint;

function getNidForSala( nombreSala: string; var activa: shortint): Longint;

function getNidForCorrida(nidSala: Longint; ModoRun: integer;
  caso: string; var activa: shortint): integer;


function SetEstadoSala(nidSala: integer; activa: shortint): integer;


function SetEstadoCorrida(nidCorrida: integer; NuevoEstado: TEstadoCorrida;
  JobId: integer // si el estado es EC_Lanzada, el JobId se guarda
  ): integer;


// elimina el Rec de la sala, el archivo, las corridas asociadas.
function EliminarSala( nidSala: integer ): integer;

// elimina la Corrida y todos los archivos de resultados asociados
function EliminarCorrida( nidCorrida: integer ): integer;



function getSalas: TDAOfRec_Sala;
function getCorridas: TDAOfRec_Corrida;

procedure TestearCrearDirectorios;


implementation



function EliminarArchivos( carpeta: string; archiComodin: string): integer;
var
  res: integer;
  Info: TSearchRec;
begin
  res := 0;
  if FindFirst( Carpeta + DirectorySeparator+ archiComodin, faArchive, Info) = 0 then
  begin
    repeat
      SysUtils.DeleteFile( Carpeta + DirectorySeparator+ info.Name);
      Writeln(info.Name);
      Inc(res);
    until FindNext(info) <> 0;
  end;
  FindClose(Info);
  Result := res;
end;


// Intenta abrir en modo escritura con bloqueo para todos los demás
// si lo logra retorna el TFileStream sino retorna NIL
function GetFLock(archi: string; cntIntentos: integer = 100;
  msSleep: integer = 100; flgRaiseException: boolean = True): TFileStream;
var
  res: TFileStream;
begin
  res:= nil;
  while ( res = nil ) and ( cntIntentos > 0 ) do
  begin
    try
      // Intentamos abrir el archivo en modo Exclusivo
      if FileExists(archi) then
        res := TFileStream.Create(archi, fmOpenReadWrite or fmShareExclusive)
      else
        res := TFileStream.Create(archi, fmCreate or fmShareExclusive);

    except
      on E: Exception do
      begin
        res.Free;
        res := nil;
      end;
    end;

    if res = nil then
    begin
      Dec(cntIntentos);
      if cntIntentos > 0 then
        sleep(msSleep);
    end;
  end;

  if (res = nil) and flgRaiseException then
    raise Exception.Create('Fallo GetFLock sobre arcihvo: ' + archi);
  Result := res;

end;

function GetNRecs(f: TFileStream; RecSize: integer): integer;
begin
  Result := f.size div RecSize;
end;

function ReadNRecs(f: TFileStream; var Buff; kDesde, nRecs, RecSize: integer): integer;
var
  ntr, nr: integer;
begin
  f.seek( kDesde * RecSize, soFromBeginning);
  ntr := nRecs * RecSize;
  nr := f.Read(Buff, ntr);
  Result := nr div RecSize;
end;

function WriteNRecs(f: TFileStream; var Buff; kDesde, nRecs, RecSize: integer): integer;
var
  ntr, nr: integer;
begin
  f.seek( kDesde * RecSize, soFromBeginning);
  if nRecs > 0 then
  begin
    ntr := nRecs * RecSize;
    nr := f.Write(Buff, ntr);
    Result := nr div RecSize;
  end
  else
    result:= 0
end;


function AppendNRecs(f: TFileStream; var Buff; nRecs, RecSize: integer): integer;
var
  ntr, nr: integer;
begin
  f.seek(0, soFromEnd);
  ntr := nRecs * RecSize;
  nr := f.Write(Buff, ntr);
  Result := nr div RecSize;
end;


function ReadTablaSalas(var f: TFileStream): TDAOfRec_Sala;
var
  res: TDAOfRec_Sala;
  ntr, nr: integer;
begin
  ntr := GetNRecs(f, SizeOf(TRec_Sala));
  setlength(res, ntr);
  nr := ReadNRecs(f, res[0], 0, ntr, SizeOf(TRec_Sala));
  if nr <> ntr then
    setlength(res, nr);
  Result := res;
end;

function ReadTablaCorridas(var f: TFileStream): TDAOfRec_Corrida;
var
  res: TDAOfRec_Corrida;
  ntr, nr: integer;
begin
  ntr := GetNRecs(f, SizeOf(TRec_Corrida));
  setlength(res, ntr);
  nr := ReadNRecs(f, res[0], 0, ntr, SizeOf(TRec_Corrida));
  if nr <> ntr then
    setlength(res, nr);
  Result := res;
end;


function WriteTablaSalas(var f: TFileStream; Data: TDAOfRec_Sala): integer;
begin
  Result := WriteNRecs(f, Data[0], 0, length(Data), SizeOf(TRec_Sala));
  f.Size:= f.Position; // truncate
end;


function WriteTablaCorridas(var f: TFileStream; Data: TDAOfRec_Corrida): integer;
begin
  Result := WriteNRecs(f, Data[0], 0, length(Data), SizeOf(TRec_Corrida));
  f.Size:= f.Position; // truncate
end;


function AppendRecSala(var f: TFileStream; rec: TRec_Sala): integer;
begin
  Result := AppendNRecs(f, rec, 1, SizeOf(rec));
end;

function AppendRecCorrida(var f: TFileStream; rec: TRec_Corrida): integer;
begin
  Result := AppendNRecs(f, rec, 1, SizeOf(rec));
end;

procedure CalcularDirs(NewHomeDir: string);
begin
  HomeDir := NewHomeDir;
  SimSEE_bin := HomeDir + DirectorySeparator + 'SimSEE' + DirectorySeparator + 'bin';
  SimSEE_fdb := HomeDir + DirectorySeparator + 'SimSEE' + DirectorySeparator + 'fdb';
  SimSEE_slurm := HomeDir + DirectorySeparator + 'SimSEE' + DirectorySeparator + 'slurm';
  SimSEE_salidas := HomeDir + DirectorySeparator + 'SimSEE' +
    DirectorySeparator + 'salidas';
  SimSEE_salas := HomeDir + DirectorySeparator + 'SimSEE' + DirectorySeparator + 'salas';
  SimSEE_resultados := HomeDir + DirectorySeparator + 'SimSEE' +
    DirectorySeparator + 'resultados';
end;

procedure TestearCrearDir(dir: string);
begin
  if not DirectoryExists(dir) then
    if not ForceDirectories(dir) then
      raise Exception.Create('No fue posible crear el directorio: ' + dir);
end;

procedure TestearCrearDirectorios;
begin
  TestearCrearDir(HomeDir);
  TestearCrearDir(SimSEE_slurm);
  TestearCrearDir(SimSEE_salidas);
  TestearCrearDir(SimSEE_salas);
  TestearCrearDir(SimSEE_bin);
  TestearCrearDir(SimSEE_fdb);
  TestearCrearDir(SimSEE_resultados);
end;


function getNextNID(secuencia: string; reserva: integer = 1): Longint;
var
  archi: string;
  f: TFileStream;
  res, resx: Longint;
  nr: integer;

begin
  archi := SimSEE_fdb + DirectorySeparator + 'nextval_' + secuencia;
  f := GetFLock(archi);
  if f = nil then
    raise Exception.Create('Imposible acceder al archivo: ' + Archi);

  nr := f.Read(res, sizeOf(res));
  if nr < SizeOf(res) then // asumo archivo vacio o corrupto reinicio en 1
    res := 1;

  resx := res + reserva;
  f.Seek(0, soFromBeginning);
  f.Write(resx, SizeOf(resx));
  f.Free;
  Result := res;
end;


function getNidForSala(nombreSala: string; var activa: shortint): Longint;
var
  f: TFileStream;
  r: TRec_Sala;
  k: integer;
  recs: TDAOfRec_Sala;
  res: integer;

begin
  f := GetFLock(SimSEE_fdb + DirectorySeparator + 'salas.fdb');

  res := 0;
  (*
  recs := ReadTablaSalas(f);
  if length(recs) > 0 then
  begin
    for k := 0 to high(recs) do
      if recs[k].nombre = nombreSala then
      begin
        activa := recs[k].activa;
        res := -recs[k].nid;
        result:= res;
        break;
      end;
  end;
    *)

  if res = 0 then
  begin
    r.nid := getNextNID('salas');
    r.dtCreacion := now;
    r.dtUltimoCambio := now;
    r.nombre := nombreSala;
    r.activa := -1;

    AppendRecSala(f, r);

    activa := r.activa;
    Result := r.nid;
  end;

  setlength(recs, 0);
  f.Free;
end;

function getNidForCorrida(nidSala: Longint; ModoRun: integer; caso: string;
  var activa: shortint): integer;
var
  f: TFileStream;
  r: TRec_Corrida;
  k: integer;
  recs: TDAOfRec_Corrida;
  res: integer;

begin
  f := GetFLock(SimSEE_fdb + DirectorySeparator + 'corridas.fdb');
  res := 0;

  (*
  recs := ReadTablaCorridas(f);
  if length(recs) > 0 then
  begin
    for k := 0 to high(recs) do
      if (recs[k].Sala = nidSala) and (recs[k].caso = caso) and
        (recs[k].modoRun = ModoRun) then
      begin
        activa := recs[k].activa;
        res := -recs[k].nid;
        break;
      end;
  end;
    *)

  if res = 0 then
  begin
    r.nid := getNextNID('corridas');
    r.dtCreacion := now;
    r.dtUltimoCambio := now;
    r.sala := nidSala;
    r.modoRun := modoRun;
    r.caso := caso;
    r.plantilla := '';
    r.activa := -1;

    AppendRecCorrida(f, r);

    activa := r.activa;
    Result := r.nid;
  end;
  setlength(recs, 0);
  f.Free;
end;

function SetEstadoSala(nidSala: integer; activa: shortint): integer;
var
  f: TFileStream;
  k: integer;
  recs: TDAOfRec_Sala;
  kRec: integer;

begin
  f := GetFLock(SimSEE_fdb + DirectorySeparator + 'salas.fdb');
  recs := ReadTablaSalas(f);
  kRec := -1;
  for k := 0 to high(recs) do
    if recs[k].nid = nidSala then
    begin
      kRec := k;
      break;
    end;

  if kRec >= 0 then
  begin
    recs[kRec].activa := activa;

    if WriteNRecs(f, recs[kRec], kRec, 1, SizeOf(TRec_Sala)) = 1 then
      Result := kRec
    else
      Result := -2;

  end
  else
    Result := -1;
  f.Free;
  setlength(recs, 0);

end;

function SetEstadoCorrida(nidCorrida: integer; NuevoEstado: TEstadoCorrida;
  JobId: integer // si el estado es EC_Lanzada, el JobId se guarda
  ): integer;
var
  f: TFileStream;
  k: integer;
  recs: TDAOfRec_Corrida;
  kRec: integer;

begin
  f := GetFLock(SimSEE_fdb + DirectorySeparator + 'corridas.fdb');
  recs := ReadTablaCorridas(f);
  kRec := -1;
  for k := 0 to high(recs) do
    if recs[k].nid = nidCorrida then
    begin
      kRec := k;
      break;
    end;

  if kRec >= 0 then
  begin

    recs[kRec].estado := NuevoEstado;
    if ( NuevoEstado = EC_Lanzada ) and ( JobId > 0 ) then
      recs[kRec].JobId := JobId;
    recs[kRec].dtUltimoCambio := now;

    if WriteNRecs(f, recs[kRec], kRec, 1, SizeOf(TRec_Corrida)) = 1 then
      Result := kRec
    else
      Result := -2;

  end
  else
    Result := -1;
  f.Free;
  setlength(recs, 0);
end;




procedure  LimpiarArhcivosCorrida( nidCorrida, slurm_JobId: Longint );
begin
  EliminarArchivos( SimSEE_salidas, 'slurm-'+IntToStr( slurm_JobId )+'*' );
  EliminarArchivos( SimSEE_resultados, IntToStr( nidCorrida )+'_*' );
  EliminarArchivos( SimSEE_slurm, 'rse'+IntToStr( nidCorrida )+'.slurm' );
end;


function EliminarSala(nidSala: integer): integer;
var
  fSalas, fCorridas: TFileStream;
  k, j: integer;
  Salas: TDAOfRec_Sala;
  Corridas: TDAOfRec_Corrida;

  kRecSala: integer;
  NCorridas: integer;
  NombreSala: string;

begin
  fSalas := GetFLock(SimSEE_fdb + DirectorySeparator + 'salas.fdb');
  Salas := ReadTablaSalas( fSalas );
  kRecSala := -1;
  for k := 0 to high( Salas ) do
    if Salas[k].nid = nidSala then
    begin
      kRecSala := k;
      break;
    end;

  if kRecSala >= 0 then
  begin
    NombreSala:= ExtractFileName( Salas[kRecSala].nombre );
    EliminarArchivos( SimSEE_salas, NombreSala+'.zip' );
    for j:= kRecSala to high( Salas ) -1 do
      Salas[j]:= Salas[j+1];
    setlength( Salas, Length( Salas ) - 1 );
    WriteTablaSalas( fSalas, Salas );
  end
  else
  begin
    Result := -1;
  end;

  fSalas.Free;
  setlength( Salas, 0);


  fCorridas:= GetFLock( SimSEE_fdb + DirectorySeparator +'corridas.fdb' );
  Corridas:= ReadTablaCorridas( fCorridas );
  k:= 0;
  NCorridas:= length( Corridas );

  while k < NCorridas do
  begin
    if Corridas[k].sala = nidSala then
    begin
      LimpiarArhcivosCorrida(  Corridas[k].nid, Corridas[k].JobId );
      dec( NCorridas );
      for j:= k to NCorridas-1 do
        Corridas[j]:= Corridas[j+1];
    end
    else
      inc( k );
  end;
  setlength( Corridas, NCorridas );
  WriteTablaCorridas( fCorridas, Corridas );
  fCorridas.Free;

end;

function EliminarCorrida(nidCorrida: integer): integer;
var
  fCorridas: TFileStream;
  Corridas: TDAOfRec_Corrida;
  kRec: integer;
  k, j: integer;
begin
  fCorridas:= GetFLock( SimSEE_fdb + DirectorySeparator +'corridas.fdb' );
  Corridas:= ReadTablaCorridas( fCorridas );

  kRec:= -1;
  for k:= 0 to high( Corridas ) do
    if Corridas[k ].nid = nidCorrida then
    begin
      kRec:= k;
      break;
    end;

  if kRec >= 0 then
  begin
    LimpiarArhcivosCorrida(  Corridas[k].nid, Corridas[k].JobId );
    for j:= kRec to high( Corridas ) - 1 do
      Corridas[j]:= Corridas[j+1];
    setlength( Corridas, length( Corridas ) -1 );
    WriteTablaCorridas( fCorridas, Corridas );
  end;
  result:= kRec;
  fCorridas.Free;
  setlength( Corridas, 0 );
end;

function getSalas: TDAOfRec_Sala;
var
  fSalas: TFileStream;
  Salas: TDAOfRec_Sala;

begin
  fSalas := GetFLock(SimSEE_fdb + DirectorySeparator + 'salas.fdb');
  Salas := ReadTablaSalas( fSalas );
  fSalas.Free;
  result:= Salas;
end;

function getCorridas: TDAOfRec_Corrida;
var
  fCorridas: TFileStream;
  Corridas: TDAOfRec_Corrida;
begin
  fCorridas:= GetFLock(SimSEE_fdb + DirectorySeparator + 'corridas.fdb');
  Corridas := ReadTablaCorridas( fCorridas );
  fCorridas.Free;
  result:= Corridas;

end;



begin
  HomeDir := GetEnvironmentVariable('HOME');
  CalcularDirs(HomeDir);
end.
