{+doc
+NOMBRE: NucleoT
+CREACION:  10.9.92
+AUTORES:  rch
+REGISTRO:
+TIPO: Unidad Pascal.
+PROPOSITO: Proporcionar los objetos despachadores de interrupciones software
  con los nombres:
    CadaSegundo
    CadaMinuto
    CadaHora
    CadaDia
    CadaSemana
    CadaMes
    CadaAnio
    CadaEstacion
    CombioHorario

+PROYECTO: Simenerg

+REVISION:
+AUTOR:
+DESCRIPCION:
-doc}



unit NucleoT;

interface

uses
  SysUtils, Task2;


{   Los Tipos
  ---------
}
type

  T_Anio = longint;
  T_DiaDelAnio = 1 .. 367;
  T_Mes = (Enero, Febrero, Marzo,
    Abril, Mayo, Junio,
    Julio, Agosto, Setiembre,
    Octubre, Noviembre, Diciembre);

  T_SemanaDelMes = 1..4;
  T_DiaDeLaSemana = (Domingo, Lunes, Martes, Miercoles,
    Jueves, Viernes, Sabado);

  T_Estacion = (Otonio, Invierno, Primavera, Verano);
  T_Hora = 0..23;
  T_Minuto = 0..59;
  T_DiaDelMes = 1..31;
  T_Horario = (Nocturno, Diurno);

  TArrMensual = array[T_Mes] of double;

{
 Estado del Nucleo de Tiempo
 ---------------------------
  El siguietne conjunto de variables definen el instante de tiempo
actual donde se encuentra el Nucleo de tiempos
}
var
  Anio: T_Anio;
  Estacion: T_Estacion;
  Mes: T_Mes;
  DiaDelAnio: T_DiaDelAnio;
  DiaDelMes: T_DiaDelMes;
  DiaDeLaSemana: T_DiaDeLaSemana;
  SemanaDelMes: T_SemanaDelMes;
  Hora: T_Hora;
  Minuto: T_Minuto;
  Segundo: integer;
  Horario: T_Horario;

{ Los Despachadores.
  ------------------
  Los siguientes despachadores son £tiles para inscribir tareas
en los mismos para que se ejecuten con los diferentes latidos del
Nucleo de Tiempos
}

  CadaSegundo: TDespachador_CadaSegundo;
  CadaMinuto: TDespachador_CadaMinuto;
  CadaHora: TDespachador_CadaHora;
  CadaDia: TDespachador_CadaDia;
  CadaSemana: TDespachador_CadaSemana;
  CadaMes: TDespachador_CadaMes;
  CambioEstacion: TDespachador_CambioEstacion;
  CadaAnio: TDespachador_CadaAnio;
  CambioHorario: TDespachador_CambioHorario;


{ Inicializa el Nucleo de Tiempo al instante indicado }
procedure FijarInstante(xAnio: T_Anio; xMes: T_Mes; xDiaDelMes: T_DiaDelMes;
  xHora: T_Hora; xMinuto: T_Minuto);


{   Los TICs del Nucleo de Tiempos.
  ------------------------------
Tics del NDT. Llamando a cualquiera de estos procedimientos
se le da un Tic al NDT al nivel que sugiere el nombre del procedimiento
por ejemplo, si se llama a (Tic_Minuto) se avanzar  en unminuto el tiempo
actual del NDT, si en cambio se llama a (Tic_Dia) si avnazar  un dĦa el
tiempo }
procedure Tic_Segundo;
procedure Tic_Minuto;
procedure Tic_Hora;
procedure Tic_Dia;
procedure Tic_Mes;
procedure Tic_Anio;


{ DiasDelMesActual, se mantiene actualizada cada cambio de mes,
reflejando la cantidad de dĦas del mes en curso }
var
  DiasDelMesActual: T_DiaDelMes;


{  Definici˘n de la duraci˘n de cada uno de los meses.
  La duraci˘n del mes de febrero, se actuliza cada Cambio de A¤o
teniendo en cuenta los que son bisiestos y los que no}
var
  DiasDelMes: array[T_Mes] of T_DiaDelMes = (31, 28, 31, 30, 31,
    30, 31, 31, 30, 31, 30, 31);

const
  {  Consideramos las estaciones comenzando junto con algunos meses }
  MesComienzoDeEstacion: array[T_Estacion] of T_Mes =
    (Marzo, Junio, Setiembre, Diciembre);

  { Cambios de horarios }
  HoraEntradaHorarioDiurno = 6;
  HoraEntradaHorarioNocturno = 18;


{ Retorna TRUE si el a¤o pasado como par metro es bisiesto }
function Bisiesto(Anio: T_Anio): boolean;

{ Retorna los dĦas del a¤o pasado como par metro. Los valores posibles
son 365 si el a¤o es bisiesto y 366 si no lo es }
function DarDiasDelAnio(Anio: T_Anio): integer;

{ Retorna el dĦa del a¤o correspondiente la fecha pasada como par metro
  Por ej: DarDiaDelAnio(1992, Enero, 1) devuelve 1 pues es el primer
dĦa del a¤o. }
function DarDiaDelAnio(Anio: T_Anio; Mes: T_Mes; DiaDelMes: T_DiaDelMes): T_DiaDelAnio;

{ Retorna el dĦa de la semana que corresponde a una fecha dada }
function DarDiaDeLaSemana(Anio: T_Anio; Mes: T_Mes;
  DiaDelMes: T_DiaDelMes): T_DiaDeLaSemana;


{ retorna TRUE si la fecha A es anterior ( o igual ) a la fecha B }
function EnOrdenCronologico(AnioA: T_Anio; MesA: T_Mes; DiaA: T_DiaDelMes;
  AnioB: T_Anio; MesB: T_Mes; DiaB: T_DiaDelMes): boolean;


{ Retorna la distancia en dĦas (con signo) entre ambas fechas }
function DistanciaEnDias(AnioA: T_Anio; MesA: T_Mes; DiaA: T_DiaDelMes;
  AnioB: T_Anio; MesB: T_Mes; DiaB: T_DiaDelMes): longint;


procedure LimpiarTodo;



procedure AlInicio;
procedure AlFinal;

function NombreMes(kMes: T_Mes): string;

function promedio(v: TArrMensual): double; overload;
function minimo(v: TArrMensual): double; overload;
function maximo(v: TArrMensual): double; overload;

implementation

function minimo(v: TArrMensual): double;
var
  a: double;
  k: T_Mes;
begin
  a := v[Enero];
  for k := Febrero to Diciembre do
    if v[k] < a then
      a := v[k];
  Result := a;
end;

function maximo(v: TArrMensual): double;
var
  a: double;
  k: T_Mes;
begin
  a := v[Enero];
  for k := Febrero to Diciembre do
    if v[k] > a then
      a := v[k];
  Result := a;
end;


function promedio(v: TArrMensual): double;
var
  kmes: T_Mes;
  a: double;
begin
  a := 0;
  for kmes := Enero to Diciembre do
    a := a + v[kmes];
  Result := a / 12.0;
end;


function NombreMes(kMes: T_Mes): string;
begin
  case kMes of
    Enero: Result := 'Enero';
    Febrero: Result := 'Febrero';
    Marzo: Result := 'Marzo';
    Abril: Result := 'Abril';
    Mayo: Result := 'Mayo';
    Junio: Result := 'Junio';
    Julio: Result := 'Julio';
    Agosto: Result := 'Agosto';
    Setiembre: Result := 'Setiembre';
    Octubre: Result := 'Octubre';
    Noviembre: Result := 'Noviembre';
    Diciembre: Result := 'Diciembre';
  end;
end;


procedure LimpiarTodo;
begin
  Anio := 1991;
  Estacion := Verano;
  Mes := Diciembre;
  DiaDelAnio := 365;
  DiaDelMes := 31;
  DiaDeLaSemana := Martes;
  SemanaDelMes := 4;
  Hora := 23;
  Minuto := 0;
  Segundo := 0;

  if bisiesto(Anio) then
    DiasDelMes[Febrero] := 29
  else
    DiasDelMes[Febrero] := 28;

  DiasDelMesActual := DiasDelMes[Mes];

  CadaSegundo.Clear;
  CadaMinuto.Clear;
  CadaHora.Clear;
  CadaDia.Clear;
  CadaSemana.Clear;
  CadaMes.Clear;
  CambioEstacion.Clear;
  CadaAnio.Clear;
  CambioHorario.Clear;

end;

{ SwV, ( Swaps Variables) , conmuta (n) bytes de la posici˘n de memoria
de la varible (x) a la de la variable (y). }
procedure SwV(var x; var y; n: integer);
var
  k: integer;
  z: byte; { buffer para intercambio }
type
  w = record
    l, h: byte;
  end;
var
  px, py: ^w;

begin
  px := @w(x).l;
  py := @w(y).l;

  for k := 1 to n do
  begin
    z := px^.l;
    px^.l := py^.l;
    py^.l := z;
    px := @w(x).h;
    py := @w(y).h;
  end;
end;



function DarDiaDeLaSemana(Anio: T_Anio; Mes: T_Mes;
  DiaDelMes: T_DiaDelMes): T_DiaDeLaSemana;

var
  dd: longint;
  DDS: T_DiaDeLaSemana;

begin
  { Tomaremos como fecha base el:
     Lunes, 18 de Junio de 1962 }

  dd := DistanciaEnDias(1962, Junio, 18, Anio, Mes, DiaDelMes);
  dd := (Ord(Lunes) + dd) mod 7;
  DDS := Domingo;
  if dd >= 0 then
    Inc(DDS, dd)
  else
    Inc(DDS, 7 + dd);
  DarDiaDeLaSemana := DDS;
end;




{ Retorna TRUE si el a¤o pasado como par metro es bisiestoaaq }
function Bisiesto(Anio: T_Anio): boolean;
begin
  Bisiesto := SysUtils.IsLeapYear(Anio);

  //(( Anio mod 4 ) = 0)and(( Anio mod 100)<>0)

end;

{ retorna TRUE si la fecha B es posterior ( o igual ) a la fecha A }
function EnOrdenCronologico(AnioA: T_Anio; MesA: T_Mes; DiaA: T_DiaDelMes;
  AnioB: T_Anio; MesB: T_Mes; DiaB: T_DiaDelMes): boolean;
begin
  if AnioA > AnioB then
    EnOrdenCronologico := False
  else if MesA > MesB then
    EnOrdenCronologico := False
  else if DiaA > DiaB then
    EnOrdenCronologico := False
  else
    EnOrdenCronologico := True;
end;




function DistanciaEnDias(AnioA: T_Anio; MesA: T_Mes; DiaA: T_DiaDelMes;
  AnioB: T_Anio; MesB: T_Mes; DiaB: T_DiaDelMes): longint;
var
  DA: longint;
  signo: integer;
  kAnio: T_Anio;

begin

  { Obligo a los datos a estar en orden cronologico }
  if not EnOrdenCronologico(AnioA, MesA, DiaA, AnioB, MesB, DiaB) then
  begin
    SWV(AnioA, AnioB, SizeOf(T_Anio));
    SWV(MesA, MesB, SizeOf(T_Mes));
    SWV(DiaA, DiaB, SizeOf(T_DiaDelMes));
    Signo := -1;
  end
  else
    Signo := 1;

  DA := 0;

  kAnio := AnioA;
  while kAnio < AnioB do
  begin
    DA := DA + DarDiasDelAnio(kAnio);
    Inc(kAnio);
  end;
  DA := DA + DarDiaDelAnio(AnioB, MesB, DiaB);
  DA := DA - DarDiaDelAnio(AnioA, MesA, DiaA);

  DistanciaEnDias := DA * signo;
end;


{ --------- Definici˘n de los TICs ----------------}

procedure Tic_Semana;
begin
  if SemanaDelMes = 4 then
    SemanaDelMes := 1
  else
    Inc(SemanaDelMes);

  CadaSemana.run;
end;


procedure Tic_CambioEstacion;
begin
  Estacion := T_Estacion((integer(Estacion) + 1) mod 4);
  CambioEstacion.run;
end;


procedure Tic_Segundo;
begin
  if Segundo = 59 then
  begin
    Segundo := 0;
    Tic_Minuto;
  end
  else
    Inc(Segundo);

  CadaSegundo.run;
end;



procedure Tic_Minuto;
begin
  if Minuto = 59 then
  begin
    Minuto := 0;
    Tic_Hora;
  end
  else
    Inc(Minuto);

  CadaMinuto.run;
end;

procedure Tic_Hora;
begin
  if Hora = HoraEntradaHorarioDiurno then
  begin
    Horario := Diurno;
    CambioHorario.run;
  end
  else
  if Hora = HoraEntradaHorarioNocturno then
  begin
    Horario := Nocturno;
    CambioHorario.run;
  end;

  if Hora = 23 then
  begin
    Hora := 0;
    Tic_Dia;
  end
  else
    Inc(Hora);

  CadaHora.run;
end;

procedure Tic_Dia;
begin

  Inc(DiaDelAnio);

  if DiaDeLaSemana = Sabado then
  begin
    DiaDeLaSemana := Domingo;
    Tic_Semana;
  end
  else
    Inc(DiaDeLaSemana);

  if DiaDelMes = DiasDelMesActual then
  begin
    DiaDelMes := 1;
    Tic_Mes;
  end
  else
    Inc(DiaDelMes);

  CadaDia.run;
end;


procedure Tic_Mes;
begin
  if Mes = Diciembre then
  begin
    Mes := Enero;
    Tic_Anio;
  end
  else
    Inc(Mes);

  DiasDelMesActual := DiasDelMes[Mes];

  if MesComienzoDeEstacion[T_Estacion((integer(Estacion) + 1) mod 4)] = mes then

    Tic_CambioEstacion;

  CadaMes.run;
end;


procedure Tic_Anio;
begin
  Inc(Anio);
  DiaDelAnio := 1;

  if bisiesto(Anio) then
    DiasDelMes[Febrero] := 29
  else
    DiasDelMes[Febrero] := 28;

  CadaAnio.run;
end;

{ --------------Fin de los TICs -------------------}


function DarDiasDelAnio(Anio: T_Anio): integer;
begin
  if Bisiesto(Anio) then
    DarDiasDelAnio := 366
  else
    DarDiasDelAnio := 365;
end;

function DarDiaDelAnio(Anio: T_Anio; Mes: T_Mes; DiaDelMes: T_DiaDelMes): T_DiaDelAnio;
var
  kMes: T_Mes;
  DiasDeFebrero: T_DiaDelMes;
  A: T_DiaDelAnio;

begin

  DiasDeFebrero := DiasDelMes[Febrero];

  if Bisiesto(Anio) then
    DiasDelMes[Febrero] := 29
  else
    DiasDelMes[Febrero] := 28;


  A := DiaDelMes;
  kMes := Enero;
  while kMes < Mes do
  begin
    A := A + DiasDelMes[kMes];
    Inc(kMes);
  end;

  DiasDelMes[Febrero] := DiasDeFebrero;
  DarDiaDelAnio := A;
end;

procedure FijarInstante(xAnio: T_Anio; xMes: T_Mes; xDiaDelMes: T_DiaDelMes;
  xHora: T_Hora; xMinuto: T_Minuto);

var
  kMes: T_Mes;
begin

  if (xHora < HoraEntradaHorarioNocturno) and (xHora > HoraEntradaHorarioDiurno) then
    Horario := Diurno
  else
    Horario := NOcturno;

  { Fijamos el a¤o }
  Anio := xAnio;

  { Acomodamos los dĦas del mes de febrero }
  if bisiesto(Anio) then
    DiasDelMes[Febrero] := 29
  else
    DiasDelMes[Febrero] := 28;

  { Fijamos el mes }
  Mes := xMes;

  { Fijamos la Estaci˘n }
  if Mes < MesComienzoDeEstacion[Otonio] then
    Estacion := Verano
  else if Mes < MesComienzoDeEstacion[Invierno] then
    Estacion := Otonio
  else if Mes < MesComienzoDeEstacion[Primavera] then
    Estacion := Invierno
  else
    Estacion := Primavera;

  { Fijamos l dĦa del Mes }
  DiaDelMes := xDiaDelMes;
  if DiaDelMes > DiasDelMes[Mes] then
    RunError(201);

  { Inicializamos DiasDelMesActual }
  DiasDelMesActual := DiasDelMes[Mes];

  { Fijamos dĦa del A¤o }
  DiaDelAnio := DiaDelMes;
  kMes := Enero;
  while kMes < Mes do
  begin
    DiaDelAnio := DiaDelAnio + DiasDelMes[kMes];
    Inc(kMes);  // ???.rch.marzo.2004 BUG faltaba incrementar
  end;


  { Fijamos el dĦa de la semana }
  DiaDeLaSemana := DarDiaDeLaSemana(Anio, Mes, DiaDelMes);


  { Fijamos la semana del mes }
  SemanaDelMes := trunc((DiaDelMes - 1) / DiasDelMesActual * 4) + 1;

  { Fijamos la hora }
  Hora := xHora;

  { Fijamos el Minuto }
  Minuto := xMinuto;
end;

procedure AlInicio;
begin

  Anio := 1992;
  Estacion := Verano;
  Mes := Enero;
  DiaDelAnio := 1; { 1§ de octubre }
  DiaDelMes := 1;
  DiaDeLaSemana := Miercoles;
  SemanaDelMes := 1;
  Hora := 0;
  Minuto := 0;
  Segundo := 0;

  if bisiesto(Anio) then
    DiasDelMes[Febrero] := 29
  else
    DiasDelMes[Febrero] := 28;

  DiasDelMesActual := DiasDelMes[Mes];

  CadaSegundo := TDespachador_CadaSegundo.Create;
  CadaMinuto := TDespachador_CadaMinuto.Create;
  CadaHora := TDespachador_CadaHora.Create;
  CadaDia := TDespachador_CadaDia.Create;
  CadaSemana := TDespachador_CadaSemana.Create;
  CadaMes := TDespachador_CadaMes.Create;
  CambioEstacion := TDespachador_CambioEstacion.Create;
  CadaAnio := TDespachador_CadaAnio.Create;
  CambioHorario := TDespachador_CambioHorario.Create;
end;

procedure AlFinal;
begin
  CadaSegundo.Free;
  CadaMinuto.Free;
  CadaHora.Free;
  CadaDia.Free;
  CadaSemana.Free;
  CadaMes.Free;
  CambioEstacion.Free;
  CadaAnio.Free;
  CambioHorario.Free;
end;

end.
