unit table_generic_helper;
  interface
    uses
      ObservedAndWM (* tMeasurements2, tWhatMeasured2, wm2PressureInches, wm2RH, wm2TempF*);

    var
      DewPointReady: boolean;
      DewPointF, HumidexF: real;

    function  GetDewPointF(const TempF, RH: real; var DewPointF: real): boolean;
    function  GetHumidexF(const TempC, DewpointC: real): real;
    procedure TRHPSetup;
    function  TRHPWorthSaving(var OldPressureInches, OldRH, OldTempF: real;
                              const LocalEdge: tDateTime;
                              Measurements: tMeasurements2): boolean;

  implementation
    uses
      DateUtils     (* HourSpan *),
      HC3DM         (* Global, SillyRealValue *),
      HC3Utilities  (* CtoF, FtoC, INILoadFloat *),
      MyConsts      (* nullstring *),
      SysUtils      (* eInvalidOp, eMathError *),
      TableManager  (* tuTRHP *);

    var
      MinTRHPHourSpan, PressureThreshhold, RHThreshhold, TempThreshhold: real;

    function GetDewPointF(const TempF, RH: real; var DewPointF: real): boolean;
      (* service for bogus sensor and here
         From the Sensiron datasheet for the SHT15, page 15 *)

      const
        TnCold = 272.62;
        TnWarm = 243.12;
        MCold  = 22.46;
        MWarm  = 17.62;

      var
        DewPointC, LnRH, M, TempC, TFraction, Tn: real;

      begin (* GetDewPointF *)
        Result := false;
        TempC := FtoC(TempF);

        if TempC >= 0 then begin M := MWarm; Tn := TnWarm end
        else               begin M := MCold; Tn := TnCold end;

        LnRH := Ln(1.0 * RH/100.0);
        TFraction := (M * TempC)/(Tn + TempC);

        try    (* /0 happened once *)
          DewPointC := Tn * (LnRH + TFraction) / (M - LnRH - TFraction);
          DewPointF := CtoF(DewPointC);
          Result := true
        except
          on eInvalidOp do
            (* Result already false *);
          on eMathError do
            (* Result already false *)
        end    (* /0 happened once *)
      end   (* GetDewPointF *);

    function GetHumidexF(const TempC, DewpointC: real): real;
      (* service for bogus sensor and here
         see https://en.wikipedia.org/wiki/Humidex
      *)

      var
        Bracket, ExpDiff, HumidexC, TheExponent: real;

      begin (* GetHumidexF *)
        ExpDiff     := 1.0/273.16 - 1.0/(273.15 + DewPointC);
        TheExponent := 5417.753 * ExpDiff;
        Bracket     := 6.11 * exp(TheExponent) - 10.0;
        HumidexC    := TempC + 5.0 * Bracket / 9.0;
        Result      := CtoF(HumidexC)
      end   (* GetHumidexF *);

    procedure TRHPSetup;
      (* debulking *)

      const
        DefaultMinHourSpan        = 0.2;
        DefaultPressureThreshhold = 0.005;
        DefaultRHThreshhold       = 0.5;
        DefaultTempThreshhold     = 0.3;

      begin (* TRHPSetup *)
        MinTRHPHourSpan    := INILoadFloat('TRHP', 'MinTRHPHourSpan',    DefaultMinHourSpan);
        PressureThreshhold := INILoadFloat(        'PressureThreshhold', DefaultPressureThreshhold);
        RHThreshhold       := INILoadFloat(        'RHThreshholdKey',    DefaultRHThreshhold);
        TempThreshhold     := INILoadFloat(        'TempThreshhold',     DefaultTempThreshhold)
      end   (* TRHPSetup *);

    function  TRHPWorthSaving(var OldPressureInches, OldRH, OldTempF: real;
                              const LocalEdge: tDateTime;
                              Measurements: tMeasurements2): boolean;
      (* debulk *)

      const
        SillyTemp = -100.0;

      var
        GapLongEnough: boolean;
        HoursSinceLastEntry, PressureInches, RH, TempF: real;

      procedure GetData;
        (* debulk TRHPWorthSaving *)

        var
          I: integer;

        begin (* GetData *)
          PressureInches := SillyRealValue;
          RH             := SillyRealValue;
          TempF          := SillyRealValue;

          with Global.LatestData do
            for I := 1 to NArgs do
              case WMArg[I] of
                 wm2PressureInches: PressureInches := RealArg[I];
                             wm2RH: RH             := RealArg[I];
                          wm2TempF: TempF          := RealArg[I]
                 end (* case *);
          if (TempF > SillyTemp) and (RH >= 0) then
            begin (* not silly *)
              DewPointReady := GetDewPointF(TempF, RH, DewPointF);
              HumidexF := GetHumidexF(FToC(TempF), FtoC(DewPointF));
            end   (* not silly *)

          else
            DewPointReady := false      // wanted for UI even if not saved
        end   (* GetData *);

    function Qualified(Measurement: tWhatMeasured2;
                       const Datum: real;
                       var OldDatum: real;
                       const Threshhold: real): integer;
      (* if Measurement is pertinent, see if Datum is different enough *)

      begin (* Qualified *)
        if ((Measurement in Measurements) and (Datum <> SillyRealValue) and
           (GapLongEnough or (abs(Datum-OldDatum) >= Threshhold))) then
          begin (* non-boring *)
            OldDatum := Datum;
            Result := 1
          end   (* non-boring *)
        else
          Result := 0
        end   (* Qualified *);

      begin (* TRHPWorthSaving *)
        GetData;
        HoursSinceLastEntry := HourSpan(Global.LatestData.LocalStamp, LocalEdge);
        GapLongEnough       := (HoursSinceLastEntry >= MinTRHPHourSpan);

        Result := ((Qualified(wm2PressureInches, PressureInches, OldPressureInches, PressureThreshhold  ) +
                   Qualified(wm2RH,              RH,             OldRH,             RHThreshhold  ) +
                   Qualified(wm2TempF,           TempF,          OldTempF,          TempThreshhold)) > 0)
      end   (* TRHPWorthSaving *);
end.
