unit uManejadoresDeMonitores;

{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}

interface

uses
  {$IFDEF FPC}
//  FileUtil,
  {$ENDIF}
  uReferenciaMonitor, uSalasDeJuego,
  uCosa, uCosaConNombre,
  ucosaparticipedemercado,
  uMonitores, Classes,
  uEventosOptSim, SysUtils, StrUtils, xMatDefs, uReferenciaMonHistograma,
  uReferenciaMonSimRes, uVarDefs;

const
  nomMonSimRes3PDefecto = 'Monitor SimRes3 Por Defecto';

type
  TTipoResolucionMonitores = (TResolverMonitoresSimulacion,
    TResolverMonitoresOptimizacion,
    TTodas);

  { TManejadoresDeMonitores }

  TManejadoresDeMonitores = class(TCosa)
  private
    listasDeMonitores: array [TEventoOptSim] of TList{of TProcWrapper};

    //Ordena los monitores de forma que si el monitor A monitorea al monitor B,
    //B sea notificado de sus eventos antes que A
    procedure ordenarMonitores;
  public
    (**************************************************************************)
    (*              A T R I B U T O S   P E R S I S T E N T E S               *)
    (**************************************************************************)

      sala: TSalaDeJuego;
      referenciasMonitores: TListaDeCosasConNombre{of TReferenciaMonitor};
    (**************************************************************************)

    constructor Create(capa: integer; sala: TSalaDeJuego);

    function Rec: TCosa_RecLnk; override;
    procedure BeforeRead(version, id_hilo: integer); override;
    procedure AfterRead(f:TArchiTexto); override;

    constructor CargarManejadorDeMonitores(archiMonitores: string;
      abortarEnError: boolean; sala: TSalaDeJuego);

    procedure limpiar;
    procedure Free; override;

    procedure corregirDefVars(versionArchiTexto: integer);
    procedure PrepararseYPubliVars;
    procedure resolverReferenciasMonitores(paraQue: TTipoResolucionMonitores);
    //Quita el monitor
    //Para usar con una referencia a monitor llamarlo con referencia.monitor y referencia.eventos
    //Se hace asi para evitar una referencia circular
    procedure quitarMonitor(referenciaMonitor: TReferenciaMonitor);
    procedure simulacionAbortada;

    //Notifica a los monitores del evento seleccionado
    procedure notificarEvento(evento: TEventoOptSim);
    function notificarCambioCosa(cosaOrig, cosaNueva: TCosaConNombre): boolean;
    function cosasMonitoreables: TListaDeCosasConNombre;

    function crearMonitorSimResPorDefecto: TReferenciaMonSimRes;


    {$IFDEF BOSTA}
    procedure AfterInstantiation; override;
    {$ENDIF}

  end;

procedure notifyIniSim;
procedure notifyIniCron;
procedure notifyIniPaso;
procedure notifyFinPaso;
procedure notifyFinCron;
procedure notifyFinSim;
procedure notify_Opt_IniSim;
//procedure notify_Opt_IniCron;
procedure notify_Opt_InicioCalculosEtapa;
procedure notify_Opt_FinCalculosEtapa;
//procedure notify_Opt_FinCron;
procedure notify_Opt_Fin;

procedure AlInicio;
procedure AlFinal;

implementation

 //--------------------------------
 //Métodos de TManejadorDeMonitores
 //================================
var
  Instancia: TManejadoresDeMonitores;

constructor TManejadoresDeMonitores.Create(capa: integer; sala: TSalaDeJuego);
var
  i: TEventoOptSim;
begin
  inherited Create( capa );
  self.Sala := sala;

  referenciasMonitores := TListaDeCosasConNombre.Create(capa, 'Referencias Monitores');
  for i := low(listasDeMonitores) to high(listasDeMonitores) do
    listasDeMonitores[i] := TList.Create;

  if Instancia = nil then
    Instancia := self;
end;

function TManejadoresDeMonitores.Rec: TCosa_RecLnk;
begin
  Result:=inherited Rec;
  Result.addCampoDef_ref('sala', TCosa(sala), Self);
  Result.addCampoDef('referenciasMonitores', TCosa(referenciasMonitores));
end;

procedure TManejadoresDeMonitores.BeforeRead(version, id_hilo: integer);
begin
  inherited BeforeRead(version, id_hilo);
end;

procedure TManejadoresDeMonitores.AfterRead(f:TArchiTexto);
var
  i: TEventoOptSim;
begin
  inherited AfterRead(f);
  for i := low(listasDeMonitores) to high(listasDeMonitores) do
      listasDeMonitores[i] := TList.Create;

  if Instancia = nil then
     Instancia := self;
end;



constructor TManejadoresDeMonitores.CargarManejadorDeMonitores(
  archiMonitores: string;
  abortarEnError: boolean; sala: TSalaDeJuego);
var
  f:   TArchiTexto;
  aux: TListaDeCosasConNombre;
  Catalogo: TCatalogoReferencias;
begin
  if FileExists(archiMonitores) { *Converted from FileExists*  } then
  begin
    Catalogo:= TCatalogoReferencias.Create;
    f   := TArchiTexto.CreateForRead( sala.globs.idHilo, Catalogo, archiMonitores, abortarEnError);
    aux := TListaDeCosasConNombre.Create(0, 'aux');
    try
      f.rd('manejadorMonitores', TCosa(self));

      //Si lo llaman del editor
      if not abortarEnError then
        corregirDefVars(f.Version);

      aux.Add(sala);
      aux.Add(sala.globs);
      Catalogo.resolver_referencias(aux);
      Catalogo.resolver_referencias(sala.listaActores);
      Catalogo.resolver_referencias(sala.listaFuentes_);
      Catalogo.resolver_referencias(self.referenciasMonitores);
      Catalogo.resolver_referenciasDeArch(sala.archs);

      aux.FreeSinElemenentos;
      aux := nil;
      f.Free;
      f := nil;
      Catalogo.Free;
    except
      on E: Exception do
      begin
        if aux <> nil then
          aux.Free;
        if f <> nil then
          f.Free;
        raise;
      end;
    end;
  end
  else
    raise Exception.Create(
      'TManejadoresDeMonitores.CargarManejadorDeMonitores: no se encuentra el archivo ' +
      archiMonitores);
end;

 
procedure TManejadoresDeMonitores.limpiar;
var
  i: TEventoOptSim;
  j: integer;
begin
  for i := Low(listasDeMonitores) to high(listasDeMonitores) do
  begin
    for j := 0 to listasDeMonitores[i].Count - 1 do
      TProcWrapper(listasDeMonitores[i][j]).Free;
    listasDeMonitores[i].Count := 0;
  end;
  for j := 0 to referenciasMonitores.Count - 1 do
    if TReferenciaMonitor(referenciasMonitores[j]).monitor <> nil then
    begin
      TReferenciaMonitor(referenciasMonitores[j]).monitor.Free;
      TReferenciaMonitor(referenciasMonitores[j]).monitor := nil;
    end;
end;

procedure TManejadoresDeMonitores.Free;
var
  i: TEventoOptSim;
  j: integer;
begin
  Instancia := nil;
  for i := Low(listasDeMonitores) to high(listasDeMonitores) do
  begin
    if listasDeMonitores[i] <> nil then
    begin
      for j := 0 to listasDeMonitores[i].Count - 1 do
        if listasDeMonitores[i][j] <> nil then
          TProcWrapper(listasDeMonitores[i][j]).Free;
      listasDeMonitores[i].Free;
    end;
  end;
  referenciasMonitores.Free;
  inherited Free;
end;

procedure TManejadoresDeMonitores.resolverReferenciasMonitores(
  paraQue: TTipoResolucionMonitores);
var
  i, j:     integer;
  ref:      TReferenciaMonitor;
  errores:  string;
  cosasMon: TListaDeCosasConNombre;
begin
  errores := '';
  PrepararseYPubliVars;
  ordenarMonitores;
  cosasMon := self.cosasMonitoreables;
  for i := 0 to referenciasMonitores.Count - 1 do
  begin
    ref := TReferenciaMonitor(referenciasMonitores[i]);
    if ref.Enabled then
    begin
      try
        begin
          if (paraQue = TResolverMonitoresSimulacion) and ref.esDeSimulacion then
            ref.crearMonitorDeVariables(cosasMon)
          else if (paraQue = TResolverMonitoresOptimizacion) and ref.esDeOptimizacion then
            ref.crearMonitorDeVariables(cosasMon)
          else if (paraQue = TTodas) then
            ref.crearMonitorDeVariables(cosasMon);

          if ref.monitor <> nil then
          begin
            for j := 0 to High(ref.eventos) do
              listasDeMonitores[ref.eventos[j].evento].Add(
                ref.monitor.ResolverRefProc(ref.eventos[j].refProc));
          end;
        end
      except
        on E: EMonitorException do
        begin
          errores := errores + E.Message;
          if e.cantVarsResueltas > 0 then
            for j := 0 to High(ref.eventos) do
              listasDeMonitores[ref.eventos[j].evento].Add(
                ref.monitor.ResolverRefProc(ref.eventos[j].refProc));
        end
      end;
    end;
  end;
  if errores <> '' then
    raise EMonitorException.Create(errores, 0);
end;

procedure TManejadoresDeMonitores.quitarMonitor(referenciaMonitor: TReferenciaMonitor);
var
  i, j: integer;
  procW, procIt: TProcWrapper;
begin
  if referenciaMonitor.monitor <> nil then
  begin
    for i := 0 to high(referenciaMonitor.eventos) do
    begin
      procW := referenciaMonitor.monitor.resolverRefProc(
        referenciaMonitor.eventos[i].refProc);
      if listasDeMonitores[referenciaMonitor.eventos[i].evento] <> nil then
      begin
        for j := 0 to listasDeMonitores[referenciaMonitor.eventos[i].evento].Count - 1 do
        begin
          procIt := TProcWrapper(listasDeMonitores[referenciaMonitor.eventos
            [i].evento].Items[j]);
          if procW.EsIgualA(procIt) then
          begin
            listasDeMonitores[referenciaMonitor.eventos[i].evento].Delete(j);
            if listasDeMonitores[referenciaMonitor.eventos[i].evento].Count = 0 then
            begin
              listasDeMonitores[referenciaMonitor.eventos[i].evento].Free;
              listasDeMonitores[referenciaMonitor.eventos[i].evento] := nil;
            end;
            break;
          end;
        end;
        procW.Free;
      end;
    end;
    uMonitores.FreeAndNil(referenciaMonitor.monitor);
  end;
end;

procedure TManejadoresDeMonitores.simulacionAbortada;
var
  i: integer;
begin
  for i := 0 to referenciasMonitores.Count - 1 do
    quitarMonitor(TReferenciaMonitor(referenciasMonitores[i]));
end;

procedure TManejadoresDeMonitores.notificarEvento(evento: TEventoOptSim);
var
  i: integer;
begin
  for i := 0 to listasDeMonitores[evento].Count - 1 do
    TProcWrapper(listasDeMonitores[evento][i]).proc;
end;

function TManejadoresDeMonitores.notificarCambioCosa(cosaOrig, cosaNueva:
  TCosaConNombre): boolean;
var
  i:   integer;
  res: boolean;
begin
  res := False;
  if cosaOrig.nombre <> cosaNueva.nombre then
    for i := 0 to referenciasMonitores.Count - 1 do
    begin
      if TReferenciaMonitor(referenciasMonitores[i]).notificarCambioCosa(
        cosaOrig, cosaNueva) then
        res := True;
    end;
  Result := res;
end;

procedure TManejadoresDeMonitores.ordenarMonitores;
 //La idea es que el queda en la posición i no este violando ninguna condición.
 //Entonces el problema se reduce y se puede trabajar desde la posición i + 1
var
  aux: TReferenciaMonitor;
  i, j, pos, aux2: integer;
begin
  i := 0;
  while i < referenciasMonitores.Count - 1 do
  begin
    pos := i;
    aux := TReferenciaMonitor(referenciasMonitores[pos]);
    j   := i + 1;
    while j < referenciasMonitores.Count do
    begin
      if aux.monitoreaA(TReferenciaMonitor(referenciasMonitores[j])) then
      begin
        referenciasMonitores.Exchange(pos, j);
        aux2 := pos;
        pos  := j;
        j    := aux2 + 1;
      end;
      j := j + 1;
    end;
    if pos = i then //No cambie nada => aux no viola ninguna condición
      i := i + 1;    //Si cambie algo tengo que revisar que el cambio no viole otra
    //condición => no avanzo
  end;
  for i := 0 to referenciasMonitores.Count - 1 do
    TReferenciaMonitor(referenciasMonitores[i]).notificarPosEnManejador(i);
end;

procedure TManejadoresDeMonitores.corregirDefVars(versionArchiTexto: integer);
var
  i, j:    integer;
  varDefsDeMonitor: TDAOfTReferenciaDefVar;
  varDefj: TReferenciaDefVar;
  poste:   string;
  posCorcheteAperturaEnNomVar, posCorcheteCierreEnNomVar: integer;
begin
  if versionArchiTexto < uCosa.VERSION_ArchiTexto then
  begin
    for i := 0 to referenciasMonitores.Count - 1 do
    begin
      varDefsDeMonitor := TReferenciaMonitor(referenciasMonitores[i]).getReferenciasDefVars;
      for j := 0 to high(varDefsDeMonitor) do
      begin
        varDefj := varDefsDeMonitor[j];

        //Antes de la versión 19 las variables se publicaban con sus unidades en
        //el nombre, hay que sacar las unidades de los nombres de las variables
        if versionArchiTexto < 19 then
        begin
          //Saco la unidad del nombre de la variable
          posCorcheteAperturaEnNomVar := Pos('[', varDefj.nombreVar);
          if posCorcheteAperturaEnNomVar <> 0 then
          begin
            posCorcheteCierreEnNomVar := Pos(']', varDefj.nombreVar);
            try
              //Si es un indice todo ok
              StrToInt(copy(varDefj.nombreVar, posCorcheteAperturaEnNomVar +
                1, posCorcheteCierreEnNomVar - posCorcheteAperturaEnNomVar - 1))
            except
              //Sino lo borro
              on EConvertError do
                Delete(varDefj.nombreVar, posCorcheteAperturaEnNomVar,
                  posCorcheteCierreEnNomVar - posCorcheteAperturaEnNomVar + 1);
            end;
          end;
        end;

        //Antes de la versión 20 los aportes propios de los generadores hidráulicos
        //se publicaban como 'QAportesPropios', ahora cambio a 'QAportesP'
        //y todos los vectores excepto el del monitor de histograma cambiaron
        //su notacion de vector[i] a vector_Pi para escribir el número de poste
        //en el monitorSimRes
        if versionArchiTexto < 20 then
        begin
          if varDefj.nombreVar = 'QAportesPropios' then
            varDefj.nombreVar := 'QAportesP'
          else if varDefj.nombreVar = 'Congestión' then
            varDefj.nombreVar := 'CostoCongestion'
          else if varDefj.nombreVar = 'NMaquinasDespachadas_EstePaso' then
            varDefj.nombreVar := 'NMaqsDespachadas'
          else if varDefj.nombreVar = 'cv_agua_Dec' then
            varDefj.nombreVar := 'CV_aguaDec'
          else if varDefj.nombreVar = 'cv_agua_Inc' then
            varDefj.nombreVar := 'CV_aguaInc';

          if not (varDefj.claseCosa = TReferenciaMonHistograma.ClassName) then
          begin
            posCorcheteAperturaEnNomVar := Pos('[', varDefj.nombreVar);
            if posCorcheteAperturaEnNomVar <> 0 then
            begin
              posCorcheteCierreEnNomVar := Pos(']', varDefj.nombreVar);
              poste := MidStr(varDefj.nombreVar, posCorcheteAperturaEnNomVar +
                1, posCorcheteCierreEnNomVar - posCorcheteAperturaEnNomVar - 1);
              varDefj.nombreVar :=
                copy(varDefj.nombreVar, 1, posCorcheteAperturaEnNomVar - 1) +
                '_P' + poste;
            end;
          end;
        end;
      end;
    end;
  end;
end;

procedure TManejadoresDeMonitores.PrepararseYPubliVars;
var
  i: integer;
begin
  for i := 0 to referenciasMonitores.Count - 1 do
  begin
    quitarMonitor(TReferenciaMonitor(TReferenciaMonitor(referenciasMonitores[i])));
    TReferenciaMonitor(referenciasMonitores[i]).PubliVars;
  end;
end;

function TManejadoresDeMonitores.cosasMonitoreables: TListaDeCosasConNombre;
var
  i:   integer;
  resultado: TListaDeCosasConNombre;
  aux: TCosaConNombre;
begin

//////  //Ojo, la sala debe estar preparada de antes
//////// ---begin_probando OJO pruebo esto
//////  if  sala.pubvarlst = nil then
//////    sala.Prepararse_;
//////// ---end_probando


  resultado := TListaDeCosasConNombre.Create(capa, 'Cosas Monitoreables');

  if (sala.pubvarlst <> nil) and (sala.pubvarlst.Count <> 0) then
    resultado.Add(sala);

  if (sala.globs.pubvarlst <> nil) and (sala.globs.pubvarlst.Count <> 0) then
    resultado.Add(sala.globs);

  for i := 0 to sala.listaActores.Count - 1 do
  begin
    aux := TCosaConNombre(sala.listaActores[i]);
    if (aux.pubvarlst <> nil) and (aux.pubvarlst.Count <> 0) then
      resultado.Add(aux);
  end;

  for i := 0 to sala.listaFuentes_.Count - 1 do
  begin
    aux := TCosaConNombre(sala.listaFuentes_[i]);
    if (aux.pubvarlst <> nil) and (aux.pubvarlst.Count <> 0) then
      resultado.Add(aux);
  end;
  if sala.listaFuentesReemplazadas <> nil then
  begin
    for i := 0 to sala.listaFuentesReemplazadas.Count - 1 do
    begin
      aux := TCosaConNombre(sala.listaFuentesReemplazadas[i]);
      if (aux.pubvarlst <> nil) and (aux.pubvarlst.Count <> 0) then
        resultado.Add(aux);
    end;
  end;

  for i := 0 to referenciasMonitores.Count - 1 do
  begin
    aux := TCosaConNombre(referenciasMonitores[i]);
    if (aux.pubvarlst <> nil) and (aux.pubvarlst.Count <> 0) then
      resultado.Add(aux);
  end;

  Result := resultado;
end;

function TManejadoresDeMonitores.crearMonitorSimResPorDefecto: TReferenciaMonSimRes;
var
  i, j:    integer;
  cosaParticipeDeMercado: TCosaParticipeDeMercado;
  defVars: TListaDeCosas;
  cosasEnLaSala: TListaDeCosasConNombre;
begin
  cosasEnLaSala := TListaDeCosasConNombre.Create(0, 'aux');
  cosasEnLaSala.Add(sala.globs);
  for i := 0 to sala.listaActores.Count - 1 do
    cosasEnLaSala.Add(TCosaConNombre(sala.listaActores[i]));
  for i := 0 to sala.listaFuentes_.Count - 1 do
    cosasEnLaSala.Add(TCosaConNombre(sala.listaFuentes_[i]));
  cosasEnLaSala.Add(sala);

  defVars := TListaDeCosas.Create(0, 'defVars');
  for i := 0 to cosasEnLaSala.Count - 1 do
  begin
    if cosasEnLaSala[i] is TCosaParticipeDeMercado then
    begin
      cosaParticipeDeMercado := TCosaParticipeDeMercado(cosasEnLaSala[i]);
      try
        for j := 0 to cosaParticipeDeMercado.variablesParaSimRes.Count - 1 do
          defVars.Add(TReferenciaDefVar.Create(0, cosaParticipeDeMercado.nombre,
            cosaParticipeDeMercado.ClassName,
            TVarDef(cosaParticipeDeMercado.variablesParaSimRes[j]).nombreVar));
      except
        On e: Exception do
        begin
          e.Message := e.Message +
            ' TManejadoresDeMonitores.crearMonitorSimResPorDefecto, actor: ' +
            cosaParticipeDeMercado.DescClase;
          //Salteo el actor pero permito a la aplicación continuar
          //Muestro la excepcion para debug
          ApplicationShowException(e);
        end;
      end;
    end;
  end;

  cosasEnLaSala.FreeSinElemenentos;

  Result := TReferenciaMonSimRes.Create(0, nomMonSimRes3PDefecto,
    sala.dirResultadosCorrida +
    'simres_XXx' + IntToStr(sala.globs.NCronicasSim) + '.xlt',
    defVars, sala);
end;



{$IFDEF BOSTA}
procedure TManejadoresDeMonitores.AfterInstantiation;
var
  i: TEventoOptSim;
begin
  inherited AfterInstantiation;
  if Instancia = nil then
    Instancia := self;
  for i := low(listasDeMonitores) to high(listasDeMonitores) do
    listasDeMonitores[i] := TList.Create;
end;
{$ENDIF}

procedure notifyIniSim;
begin
  Instancia.notificarEvento(E_Sim_Inicio);
end;

procedure notifyIniCron;
begin
  Instancia.notificarEvento(E_Sim_InicioCronica);
end;

procedure notifyIniPaso;
begin
  Instancia.notificarEvento(E_Sim_InicioPaso);
end;

procedure notifyFinPaso;
begin
  Instancia.notificarEvento(E_Sim_FinPaso);
end;

procedure notifyFinCron;
begin
  Instancia.notificarEvento(E_Sim_FinCronica);
end;

procedure notifyFinSim;
begin
  Instancia.notificarEvento(E_Sim_Fin);
end;

procedure notify_Opt_IniSim;
begin
  Instancia.notificarEvento(E_Opt_Inicio);
end;

(*
procedure notify_Opt_IniCron;
begin
  Instancia.notificarEvento( E_Opt_IniCron );
end;
*)

procedure notify_Opt_InicioCalculosEtapa;
begin
  Instancia.notificarEvento(E_Opt_InicioCalculosEtapa);
end;

procedure notify_Opt_FinCalculosEtapa;
begin
  Instancia.notificarEvento(E_Opt_FinCalculosEtapa);
end;

(*
procedure notify_Opt_FinCron;
begin
  Instancia.notificarEvento(E_Opt_FinCron);
end;
  *)

procedure notify_Opt_Fin;
begin
  Instancia.notificarEvento(E_Opt_Fin);
end;

procedure AlInicio;
begin
  uCosa.registrarClaseDeCosa(TManejadoresDeMonitores.ClassName, TManejadoresDeMonitores);
  Instancia := nil;
end;

procedure AlFinal;
begin
  if Instancia <> nil then
    instancia.Free;
end;

end.

