unit uclusteradmin;

{$mode delphi}

interface

uses
  LCLType,
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics,
  Dialogs, StdCtrls, ExtCtrls, ComCtrls,
  uvisordetabla,
  ufechas,
  uclusterconfig,
  uauxiliares,
  uInicioYFinal,
  ucosa,
  ucosaconnombre,
  usalasdejuego,
  uempaquetar,
  uConstantesSimSEE;


type
  { TTablaSalas }

  TTablaSalas = class( TTabla )
      SelectDirectoryDialog1: TSelectDirectoryDialog;

  public
     nids: array of integer;
     nombres: array of string;
    constructor Create( scrollBox: TScrollBox );
    procedure Borrar_OnClick( nidRec: string; kFila: integer ); override;
    procedure Bajar_OnClick( nidRec: string; kFila: integer ); override;
    procedure Populate; virtual;
    destructor destroy; override;
    function getNombreSala( nidSala: integer ): string;
  end;

    { TTablaCorridas }
  TRecCorrida = record
    nid: integer;
    sala: integer;
    caso: string;
    modorun: integer;
    estado: integer;
    dtCreacion: TDateTime;
    dtUltimoCambio: TDateTime;
    activa: shortint;
  end;


  TDAOfRecCorridas = array of TRecCorrida;

  TTablaCorridas = class( TTabla )
    SelectDirectoryDialog1: TSelectDirectoryDialog;
   public
    recs: TDAOfRecCorridas;
    TablaSalas: TTablaSalas;

    constructor Create( scrollBox: TScrollBox; TablaSalas: TTablaSalas );
    procedure Borrar_OnClick( nidRec: string; kFila: integer ); override;
    procedure Bajar_OnClick( nidRec: string; kFila: integer ); override;
    procedure Populate; virtual;
    destructor destroy; override;
  end;

var
  TablaSalas: TTablaSalas;
  TablaCorridas: TTablaCorridas;


// comandos sobre el despachador del cluster
function getJobStatus( jobId: string ): string;
function getUserJobsLst: string;
function JobCancel( jobId: string ): string;

// comandos sobre simsee_sfdb
function simsee_sfdb( parametros: string ): TStringList;
function simsee_sfdbs( parametros: string ): string;

// comandos de subida y bajada de archivos vía pscp
procedure scp_write( archi_local, archi_remoto: string );
procedure scp_read( archi_remoto, archi_local: string );

// Sube una sala al directorio del cluster y retorna el NID asignado
// si ya existe ese archivo retonra -Nid de la sala existente.
function SubirSala( archi: string ): integer;

// Despachar una corrida para una sala y escenarios dados.
// Retorna el JobId = NID asignado a la corrida.
// Si el resultado es negativo significa que falló porque ya existia esa combinación
// elimine la corrida y lancela de nuevo si es lo que quiere hacer.
function despacharCorrida(
  nidSala: integer;
  sala, Escenario : string;
  modo_run: integer ): integer;

// Procedimiento sobre la visualización de las tablas.
procedure inicializarTablas( ScrollBox_Salas, ScrollBox_Corridas: TScrollBox );
procedure refrescarTablas;
procedure freeTablas;


implementation
var
 sshPerfil: TClusterConfigPerfil;


procedure getconfig;
begin
  if sshPerfil = nil then
    sshPerfil:= uclusterconfig.get_Cluster_cfg.PerfilActivo;
end;

function sshRemoteRun( comandos: string ): TStringList;
var
  res: TStringList;
  paramsLst: TStringList;
  pal: String;
begin
  getconfig;

  paramsLst:= TStringList.Create;
  paramsLst.add( '-i' );
  paramsLst.add( sshPerfil.archiPrivateKey );
  paramsLst.add( sshPerfil.Usuario+'@'+sshPerfil.host );
  repeat
    pal:= getNextPalSep( comandos, ' ');
    paramsLst.add( pal );
  until comandos = '';
  {$IFDEF LINUX}
  res:= RunPipe( 'plink', paramsLst, nil, #10 );
  {$ELSE}
  res:= RunPipe( 'plink', paramsLst, nil, #13 );
  {$ENDIF}
  result:= res;
end;


function simsee_sfdb( parametros: string ): TStringList;
var
  res: TStringList;
  k: integer;
begin
  res:= sshRemoteRun( '~/SimSEE/bin/simsee_sfdb '+Parametros);
  for k:= 0 to res.count-1 do
    writeln( k, ': ', res[k] );
  result:= res;
end;


function simsee_sfdbs( parametros: string ): string;
var
  res: TStringList;
  k: integer;
begin
  res:= sshRemoteRun( '~/SimSEE/bin/simsee_sfdb '+Parametros);
  for k:= 0 to res.count-1 do
    writeln( k, ': ', res[k] );
  result:= trim( res.text );
  res.Free;
end;


procedure scp_write( archi_local, archi_remoto: string );
var
  res: TStringList;
  paramsLst: TStringList;
begin
  getconfig;
  paramsLst:= TStringList.Create;
  paramsLst.add( '-i' );
  paramsLst.add( sshPerfil.archiPrivateKey );
  paramsLst.add( archi_local );
  paramsLst.add( sshPerfil.Usuario+'@'+sshPerfil.host+':'+archi_remoto );
  res:= RunPipe( 'pscp', paramsLst, nil, #13 );
  writeln( res.text );
end;

procedure scp_read( archi_remoto, archi_local: string );
var
  res: TStringList;
  paramsLst: TStringList;
begin
  getconfig;
  paramsLst:= TStringList.Create;
  paramsLst.add( '-i' );
  paramsLst.add( sshPerfil.archiPrivateKey );
  paramsLst.add( sshPerfil.Usuario+'@'+sshPerfil.host+':'+archi_remoto );
  paramsLst.add( archi_local );
  res:= RunPipe( 'pscp', paramsLst, nil, #13 );
  writeln( res.text );
end;



function EmpaquetarSala( archi: string ): string;
var
  carpeta: string;
  nid_carpeta: integer;
  zipname: string;
  res: integer;
  ext_str: string;
  lstArchis: TStringList;
  salaDummy:TSalaDeJuego;
  f:TarchiTexto;
  Catalogo:TCatalogoReferencias;
  b:boolean;


begin
  if not FileExists( archi ) then
    raise Exception.Create('No se encuentra el archivo de sala: ' + archi );

  ext_str:= ExtractFileExt( archi );
    if ext_str = '.ese' then
    begin
      b :=False;

      uInicioYFinal.AlInicio;
      Catalogo:= TCatalogoReferencias.Create;
      f := TArchiTexto.CreateForRead(0, Catalogo,  archi, False);
      try
        f.rd('Sala', TCosa(salaDummy));
      finally
        f.Free;
      end;
      salaDummy.setDirCorrida( archi );
      zipname := uempaquetar.EmpaquetarSalaEnZip(salaDummy, nil, b);
      salaDummy.Free;
      uInicioYFinal.AlFinal;
    end;
    result:= zipname;
end;

function SubirSala( archi: string ): integer;
var
  archi_remoto, ArchiSala, NombreSala: string;
  nidSala: integer;
  archi_zip: string;

begin
  archi_zip := EmpaquetarSala( archi );
  ArchiSala:= ExtractFileName(archi_zip);
  NombreSala:=ChangeFileExt( Archi_zip, '' );
//  NombreSala:= copy( ArchiSala,1, length( ArchiSala) - length('.zip' ) );

  nidSala:= StrToInt( simsee_sfdbs( 'getnidforsala '+NombreSala ) );

  archi_remoto := 'SimSEE/salas/' + ArchiSala;
  if nidSala >= 0 then
  begin
    scp_write(  archi_zip, archi_remoto );
    simsee_sfdb( 'setestadosala '+IntToStr( nidSala ) +' 1' );
  end
  else
    showmessage( 'Ya hay una sala con ese nombre. (Nid: '+IntToStr( nidSala ) +').' );
  result:= nidSala;
end;



(*
Asumimos que el archivo de la sala ya fue subido y se encuentra
en el directorio ~/SimSEE/corridas del cluster
Sala, es el nombre del archivo zip sin la extensión.
Escenario, es el nombre del Escenario a simular
Plantilla, es el nombre da la Plantilla.sr3 a ejecutar si es que se ejecuta.
modo_run es 1: solo optimización , 2: optimizar y simular y 3: optimizar, simular y simres3
*)

function despacharCorrida( nidSala: integer; Sala, Escenario: string; modo_run: integer
  ): integer;
var
  apl: string;
  archi_slurm: string;
  nombre_slurm: string;
  idEjecutor: string;
  f: textfile;
  res: string;

begin
  getconfig;
  idEjecutor:= trim( simsee_sfdb( 'getnidforcorrida '+IntToStr( nidSala )
               +' '+IntToStr( modo_run )
               +' '+Escenario ).Text );

  nombre_slurm:=  'rse'+idEjecutor+'.slurm';
  archi_slurm := getDir_Tmp+ nombre_slurm ;

  assignfile( f, archi_slurm );
  rewrite(  f );
  system.write( f, '#!/bin/bash'+#10 );
  system.write( f, '#SBATCH --job-name=rse'+idEjecutor+#10 );
  system.write( f, '#SBATCH --ntasks=1'+#10 );
  system.write( f, '#SBATCH --cpus-per-task=39'+#10 );
  system.write( f, '#SBATCH --mem=118436'+#10 );
  system.write( f, '#SBATCH --tmp=20G'+#10 );
  system.write( f, '#SBATCH --time=12:00:00'+#10 );
  system.write( f, '#SBATCH --partition='+sshPerfil.particion+#10 );
  system.write( f, '#SBATCH --mail-type=ALL'+#10 );
 // system.write( f, '#SBATCH --mail-type=FAIL'+#10 );
  system.write( f, '#SBATCH --mail-user='+ sshPerfil.email +#10 );
  //system.write( f, '#SBATCH --output=/dev/null'+#10 );
  //system.write( f, '#SBATCH --error=/dev/null'+#10 );
  system.write( f, '#SBATCH --open-mode=truncate'+#10 );
  system.write( f, 'source /etc/profile.d/modules.sh'+#10 );

  case modo_run of
  1: apl:= '${HOME}/SimSEE/bin/lanzar_opt.sh '+Sala+' '+idEjecutor+' '+Escenario;
  2: apl:= '${HOME}/SimSEE/bin/lanzar_optsim.sh '+Sala+' '+idEjecutor+' '+Escenario;
  3: apl:= '${HOME}/SimSEE/bin/lanzar_optsimsr3.sh '+Sala+' '+idEjecutor+' '+Escenario;
  end;
  system.write( f, apl+#10 );
  closefile( f );

  scp_write( archi_slurm, 'SimSEE/slurm/'+nombre_slurm );

  res:= sshRemoteRun( 'sbatch ./SimSEE/slurm/'+nombre_slurm ).text;

  if pos( 'sbatch: error', res ) <> 0 then
    raise Exception.Create( res );
  if pos( 'Submitted batch job ', res ) <> 1 then
    raise Exception.Create( res );
  delete( res, 1, length( 'Submitted batch job ' ) );
  res:= trim( res );
  simsee_sfdb( 'setestadocorrida '+idEjecutor+' 1 '+ res );

  result:= StrToInt( res );
end;

procedure inicializarTablas( ScrollBox_Salas, ScrollBox_Corridas: TScrollBox );
begin
  if TablaSalas = nil then
   TablaSalas:= TTablaSalas.Create( scrollBox_Salas );
  if TablaCorridas = nil then
   TablaCorridas:= TTablaCorridas.Create( scrollBox_Corridas, TablaSalas );
end;

procedure refrescarTablas;
begin
  TablaSalas.Populate;
  TablaCorridas.Populate;
end;

procedure freeTablas;
begin
  if tablaSalas <> nil then
  begin
    TablaSalas.Free;
    TablaSalas:= nil;
  end;

  if TablaCorridas <> nil then
  begin
    TablaCorridas.Free;
    TablaCorridas:= nil;
  end;
end;

function CanclearCorrida(
  idJob: integer ): string;
begin
  result:= sshRemoteRun( '~/SimSEE/bin/getnextkey ejecutores 1' ).text;
end;

{ TTablaCorridas }

constructor TTablaCorridas.Create(scrollBox: TScrollBox; TablaSalas: TTablaSalas );
begin
  inherited Create( scrollbox, 'TablaCorridas', 4, 0, 0);
//  populate;
  Parent:= scrollbox;
  SelectDirectoryDialog1:= TSelectDirectoryDialog.Create( Self );
  Self.TablaSalas:= TablaSalas;
end;

procedure TTablaCorridas.Borrar_OnClick(nidRec: string; kFila: integer);
begin
   if MessageDlg('Eliminación de Corrida.', 'Desea eliminar esta Corrida y sus resultados?', mtConfirmation,
   [mbYes, mbNo],0) = mrYes then
   begin
     simsee_sfdbs('eliminarcorrida '+nidRec);
     dropFila( filas[kFila] );
   end;
end;


procedure TTablaCorridas.Bajar_OnClick(nidRec: string; kFila: integer);
var
  archi_remoto, archi_local, nombreArchivo: string;
  flgBajar: boolean;
  NombreSala: string;
begin
  if nidRec <>  IntToStr( recs[kFila-1].nid ) then
    raise Exception.Create( 'Error, no coincide el nid guardado con la fila!!' );

  case recs[ kFila-1 ].modorun of
  1: archi_remoto := '_otp_';
  2: archi_remoto := '_sim_';
  3: archi_remoto := '_sr3_';
  end;

  NombreSala:= TablaSalas.getNombreSala( recs[kFila-1].sala );
  NombreArchivo:= IntToStr( recs[kFila-1].nid )+archi_remoto
     + NombreSala  +'_'+ recs[kFila-1].caso +'.zip';
  archi_remoto := 'SimSEE/resultados/' +  NombreArchivo;
//  cp ${ejecutor}_resopt.zip ${resultados}/
//  cp ${ejecutor}_resoptsim.zip ${resultados}/
//  cp ${ejecutor}_resultados.zip ${resultados}/

  if SelectDirectoryDialog1.Execute then
  begin
    archi_local:= SelectDirectoryDialog1.FileName+ DirectorySeparator+ NombreArchivo;
    flgBajar:= true;
    if FileExists( archi_local ) then
    begin
      if MessageDlg('Sobreescribir archivos.', 'Ya existe un archivo con el mismo nombre, desea SOBREESCRIBIRLO?', mtConfirmation,
       [mbYes, mbNo],0) = mrYes then
         deleteFile( archi_local )
       else
         flgBajar:= false;
    end;
    if flgBajar then
    begin
      scp_read( archi_remoto,  archi_local );
      ShowMessage( 'Archivo bajado: '+ archi_local );
    end
    else
      showMessage( 'Se canceló la bajada del archivo.' );

  end;

end;

procedure TTablaCorridas.Populate;
var
  ls: TStringList;
  r: string;
  pal: string;
  nRecs: integer;
  encabs:TStringList;
  aRec: string;
  nCampos: integer;
  k, j: integer;

begin
  ls:= simsee_sfdb( 'lst_corridas' );
  r:= ls[0];
  pal:= getNextPalSep( r,  #9 );
  pal:= getNextPalSep( r, #9 );
  nRecs:= StrToInt( pal );

  setlength( recs, nRecs );

  encabs:= TStringList.Create;

  r:= ls[1];
  pal:= getNextPalSep(r, #9 );
  while pal <> '' do
  begin
    encabs.add( pal );
    pal:= getNextPalSep(r, #9 );
  end;

  nCampos:= encabs.Count;

  ClearRedim( nRecs+1, nCampos+1 );

// fila encabezado
  for j:= 0 to nCampos-1 do
  begin
    wrTexto('', 0, j, encabs[j] );
    GetCelda(0, j).Color:= clGray;
    AlinearCelda(0,j, CAH_Centro, CAV_Abajo );
  end;


  for k:= 1 to nRecs do
  begin
    aRec:= ls[k+1];

    pal:= getNextPalSep( aRec, #9 );
    wrTexto('', k, 0,  pal );
    recs[k-1].nid:= StrToInt( pal );

    pal:= getNextPalSep( aRec, #9 );
    wrTexto('', k, 1,  pal );
    recs[k-1].sala:= StrToInt( pal );

    pal:= getNextPalSep( aRec, #9 );
    wrTexto('', k, 2,  pal );
    recs[k-1].caso:= pal;

//    pal:= getNextPalSep( aRec, #9 );
//    wrTexto('', k, 3,  pal );
//    recs[k-1].plantilla:= pal;

    pal:= getNextPalSep( aRec, #9 );
    recs[k-1].modorun:= StrToInt( pal );
    wrTexto('', k, 3,  pal );

    pal:= getNextPalSep( aRec, #9 );
    recs[k-1].estado:= StrToInt( pal );
    case recs[k-1].estado of
    1: pal:= 'Sol';
    2: pal:= 'Opt';
    3: pal:= 'Sim';
    4: pal:= 'SR3';
    5: pal:= 'Fin';
    end;
    wrTexto('', k, 4,  pal );

    pal:= getNextPalSep( aRec, #9 );
    wrTexto('', k, 5,  pal );
    recs[k-1].dtCreacion:= IsoStrToDateTime( pal );

    pal:= getNextPalSep( aRec, #9 );
    wrTexto('', k, 6,  pal );
    recs[k-1].dtUltimoCambio:= IsoStrToDateTime( pal );

    wrBotonera( IntToStr( recs[k-1].nid ), k, nCampos, [0,5], ['Eliminar Sala', 'Bajar resultados.']);
//    wrBotonera( IntToStr(k), k, nCampos, [0,1,2,3,4,5,6,7,8,9,10,11,12], []);
  end;

//  mitabla.AlinearColumna(3, CAH_Centro, CAV_Centro);

  reposicionar;
  ls.Free;
  encabs.Free;
end;


destructor TTablaCorridas.destroy;
begin
  setlength( recs, 0 );
  inherited destroy;
end;

{ TTablaSalas }

constructor TTablaSalas.Create(scrollBox: TScrollBox);
begin
  inherited Create( scrollbox, 'TablaSalas', 4, 0, 0);
//  populate;
  Parent:= scrollbox;
  SelectDirectoryDialog1:= TSelectDirectoryDialog.Create( Self );
end;


procedure TTablaSalas.Borrar_OnClick(nidRec: string; kFila: integer);
begin
   if MessageDlg('Eliminación de Sala.', 'Desea eliminar esta Sala y todas sus corridas?', mtConfirmation,
   [mbYes, mbNo],0) = mrYes then
   begin
     simsee_sfdbs('eliminarsala '+nidRec);
     dropFila( filas[kFila] );
   end;
end;

procedure TTablaSalas.Bajar_OnClick(nidRec: string; kFila: integer);
var
  archi_remoto, archi_local: string;
  flgBajar: boolean;
begin
  if nidRec <>  IntToStr( nids[kFila-1] ) then
    raise Exception.Create( 'Error, no coincide el nid guardado con la fila!!' );

  archi_remoto := 'SimSEE/salas/' + nombres[kFila-1]+'.zip';
  if SelectDirectoryDialog1.Execute then
  begin
    archi_local:= SelectDirectoryDialog1.FileName+ DirectorySeparator+ nombres[kFila-1]+'.zip';

    flgBajar:= true;
    if FileExists( archi_local ) then
    begin
      if MessageDlg('Sobreescribir archivos.', 'Ya existe un archivo con el mismo nombre, desea SOBREESCRIBIRLO?', mtConfirmation,
       [mbYes, mbNo],0) = mrYes then
         deleteFile( archi_local )
       else
         flgBajar:= false;
    end;
    if flgBajar then
    begin
      scp_read( archi_remoto,  archi_local );
      ShowMessage( 'Archivo bajado: '+ archi_local );
    end
    else
      showMessage( 'Se canceló la bajada del archivo.' );

  end;
end;

procedure TTablaSalas.Populate;
var
  ls: TStringList;
  r: string;
  pal: string;
  nRecs: integer;
  encabs:TStringList;
  aRec: string;
  nCampos: integer;
  k, j: integer;

begin
  ls:= simsee_sfdb( 'lst_salas' );
  r:= ls[0];
  pal:= getNextPalSep( r,  #9 );
  pal:= getNextPalSep( r, #9 );
  nRecs:= StrToInt( pal );

  setlength( nids, nRecs );
  setlength( nombres, nRecs );

  encabs:= TStringList.Create;

  r:= ls[1];
  pal:= getNextPalSep(r, #9 );
  while pal <> '' do
  begin
    encabs.add( pal );
    pal:= getNextPalSep(r, #9 );
  end;

  nCampos:= encabs.Count;

  ClearRedim( nRecs+1, nCampos+1 );

// fila encabezado
  for j:= 0 to nCampos-1 do
  begin
    wrTexto('', 0, j, encabs[j] );
    GetCelda(0, j).Color:= clGray;
    AlinearCelda(0,j, CAH_Centro, CAV_Abajo );
  end;


  for k:= 1 to nRecs do
  begin
    aRec:= ls[k+1];

    pal:= getNextPalSep( aRec, #9 );
    wrTexto('', k, 0,  pal );
    nids[k-1]:= StrToInt( pal );

    pal:= getNextPalSep( aRec, #9 );
    pal:= ExtractFileName( pal );
    wrTexto('', k, 1,  pal );
    nombres[k-1]:= pal;

    for j:= 2 to nCampos-1 do
    begin
      pal:= getNextPalSep( aRec, #9 );
      wrTexto('', k, j,  pal );
    end;
    wrBotonera( IntToStr( nids[k-1]), k, nCampos, [0,5], ['Eliminar Sala', 'Bajar archivo de Sala']);
//    wrBotonera( IntToStr(k), k, nCampos, [0,1,2,3,4,5,6,7,8,9,10,11,12], []);
  end;

//  mitabla.AlinearColumna(3, CAH_Centro, CAV_Centro);

  reposicionar;
  ls.Free;
  encabs.Free;
end;

destructor TTablaSalas.destroy;
begin
  setlength( nids, 0 );
  setlength( nombres, 0 );
  inherited destroy;
end;

function TTablaSalas.getNombreSala(nidSala: integer): string;
var
  k: integer;
begin
  for k:= 0 to high( nids ) do
   if nids[k] = nidSala then
   begin
     result:= ExtractFileName( nombres[k] );
     exit;
   end;
  raise Exception.Create( 'No se encuentra la Sala cuyo nid es: '+IntToStr( nidSala ) );
end;

function getJobStatus( jobId: string ): string;
begin
  result:= sshRemoteRun('scontrol show job '+ jobId ).text;
end;

function getUserJobsLst: string;
begin
  getconfig;
  result:= sshRemoteRun( 'squeue -u '+ sshPerfil.Usuario ).text;
end;

function JobCancel(jobId: string): string;
begin
  result:= sshRemoteRun('scancel '+ jobId ).text
end;


end.

