unit table_Light;

  interface
    procedure ProcessCurrentLine_Light;

  implementation
    uses
      BoardManager           (* stPhotocell *),
      Controls               (* mrOK *),
      Dashboard              (* fmDashboard, llblParenData *),
      DateUtils              (* HourSpan, IncSecond *),
      Dialogs                (* ShowMessage *),
      GlobalDump             (* fmGlobalDump *),
      GraphManager2          (* fmGraphManager *),
      HC3DM                  (* dmHC3, Global, TinyRealValue *),
      HC3Utilities           (* INILoadFloat, LastUIDOf *),
      IndoorInstruments      (* fmIndoorSensors *),
      ObservedAndWM          (* GetObservedName, obLR, obOutside, tObserved,
                                  wm2Resistance *),
      OutdoorInstruments     (* fmOutdoorInstruments *),
      RRF_StringAndTimeUtils (* Local to UTC *),
      SensorDevice           (* tSensorDevice *),
      SysUtils               (* Format *),
      TableManager           (* TableInfo, tuLight *);

    const
      MaxDevices = 3;

    type
      tLightDevice = class(tSensorDevice)
        private
          LocalEdge: tDateTime;
          UIDAtEdge: integer;
          LightAtEdge: real;
          LightBeforeEdge: real;
        public
          constructor Create(const ObservedUID: tObserved);
          procedure   NewData;
      end (* tLightDevice *);

    var
      NDevices: integer;
      LightDevice: array[1 .. MaxDevices] of tLightDevice;
      LightThreshhold, MinLightHourSpan: real;

    procedure Cleanup;
      (* reclaim storage *)

      var
        I: integer;

      begin (* Cleanup *)
        for I := 1 to NDevices do
          LightDevice[I].Free
      end   (* Cleanup *);

    procedure ProcessCurrentLine_Light;
      (* device does all the work *)
    
      var
        DeviceIndex: integer;

      function GetDeviceIndex(const ObservedUID: tObserved): integer;
        (* create if no match *)

        function FindDeviceIndex: integer;
          (* if not found, return 0 *)

          var
            I: integer;

          begin (* FindDeviceIndex *)
            Result := 0;
            for I := 1 to NDevices do
              with LightDevice[I] do
                if TheObservedUID = ObservedUID then
                  begin (* found it *)
                    Result := I;
                    break
                  end   (* found it *)
          end   (* FindDeviceIndex *);

        begin (* GetDeviceIndex *)
          Result := FindDeviceIndex;
          if Result = 0 then
            begin (* create new device *)
              inc(NDevices);
              LightDevice[NDevices] := tLightDevice.Create(ObservedUID);
              Result := NDevices
            end   (* create new device *)
        end   (* GetDeviceIndex *);

      begin (* ProcessCurrentLine_Light *)
        DeviceIndex := GetDeviceIndex(Global.LatestData.ObservedUID);
        LightDevice[DeviceIndex].NewData
      end   (* ProcessCurrentLine_Light *);

  { tLightDevice }

    constructor tLightDevice.Create(const ObservedUID: tObserved);
      (* constructor *)

      begin (* tLightDevice.Create *)
        inherited Create(tuLight, ObservedUID, Global.LatestData.UTCStamp, stPhotocell);
        TheObservedUID := ObservedUID;
        TheMeasurements := [wm2Resistance];

        with fmDashboard do
          ExtendChat(Format('li_102 created device for %s light',
                            [GetObservedName(TheObservedUID)]),
                     llblCreated);
        with qGetRecentData do
          begin (* qGetRecentData *)
            Open;
            LocalEdge       := FieldByName('WhenRecorded').AsDateTime;
            LightAtEdge     := FieldByName('Light'       ).AsFloat;
            UIDAtEdge       := FieldByName('UID'         ).AsInteger;
            Next;
            LightBeforeEdge := FieldByName('Light'       ).AsFloat;
            Close
          end   (* qGetRecentData *)
      end   (* tLightDevice.Create *);

    procedure tLightDevice.NewData;
      (* all in Global.LatestData *)

      var
        NewLight: real;

      procedure Changed;
        (* debulk NewData *)

        var
          GapLongEnough: boolean;
          HoursSinceLastEntry: real;

        begin (* Changed *)
          HoursSinceLastEntry := HourSpan(Global.LatestData.LocalStamp, LocalEdge);
          GapLongEnough       := (HoursSinceLastEntry >= MinLightHourSpan);

          if GapLongEnough or
               (abs(NewLight - LightAtEdge) > LightThreshhold) then
            begin (* new edge *)
              RunStoreDataQuery;
              LightBeforeEdge := LightAtEdge;
              UIDAtEdge := LastUIDOf('Light');
              LightAtEdge := NewLight;
              LocalEdge := Global.LatestData.LocalStamp
            end   (* new edge *)

          else
            fmDashboard.LogBoring(TableInfo[tuLight].TheBoringChar)
        end   (* Changed *);

      procedure HorizontalAtZero;
        (* debulk NewData *)

        var
          JustBefore: tDateTime;
          NewUID: integer;

        begin (* HorizontalAtZero *)
          with Global.LatestData do
            begin (* Global.LatestData *)
              NewUID     := LastUIDOf('Light') + 1;
              JustBefore := IncSecond(LocalStamp, -1);
              with dmHC3.qNewLightPoint do
                begin (* dmHC3.qNewLightPoint *)
                  ParamByName('Light'          ).AsFloat    := 0;
                  ParamByName('ObservedUID'    ).AsInteger  := ord(TheObservedUID);
                  ParamByName('UID'            ).AsInteger  := NewUID;
                  ParamByName('WhenRecorded'   ).AsDateTime := JustBefore;
                  ParamByName('WhenRecordedUTC').AsDateTime := LocalToUTC(JustBefore);
                  ExecSQL;
              
                  UIDAtEdge := NewUID + 1;
                  ParamByName('Light'          ).AsFloat    := NewLight;
                  ParamByName('UID'            ).AsInteger  := UIDAtEdge;
                  ParamByName('WhenRecorded'   ).AsDateTime := LocalStamp;
                  ParamByName('WhenRecordedUTC').AsDateTime := UTCStamp;
                  ExecSQL
                end   (* dmHC3.qNewLightPoint *);
              LightBeforeEdge := 0;
              LightAtEdge := NewLight;
              LocalEdge := LocalStamp
            end   (* Global.LatestData *)
        end   (* HorizontalAtZero *);

      begin (* tLightDevice.NewData *)
        // fmGlobalDump.GlobalDump(qStoreData);          // debugging
        // if not fmGlobalDump.ShowModal = mrOK then     // debugging
        //   ShowMessage('Bad data skipped')
        // else
          begin (* proceed *)
            with Global.LatestData do
              begin (* Global.LatestData *)
                NewLight := RealArg[1];  // sqrt to reshape response
                if NewLight < TinyRealValue then
                  NewLight := 0;

                if (NewLight = LightAtEdge) and (LightAtEdge = LightBeforeEdge) then
                  with dmHC3.qMoveLightEdge do
                    begin (* dmHC3.qMoveLightEdge *)
                      ParamByName('WhenRecorded'   ).AsDateTime := LocalStamp;
                      ParamByName('WhenRecordedUTC').AsDateTime := UTCStamp;
                      ParamByName('UID'            ).AsInteger  := UIDAtEdge;
                      ExecSQL
                    end   (* dmHC3.qMoveLightEdge *)

                else if LightAtEdge <= 0 then
                  HorizontalAtZero

                else
                  Changed
              end   (* Global.LatestData *);

            case TheObservedUID of
                   obLR: fmIndoorSensors.NewDataLRLight(NewLight);
              obOutside: fmOutdoorInstruments.NewDataGeneric(tuLight)
              end (* case *);

            if Global.Status <> gsReceivingDump then
              fmGraphManager.UpdateGraphs(TheSensorUID)
          end   (* proceed *)
      end   (* tLightDevice.NewData *);

  initialization
    NDevices         := 0;
    LightThreshhold  := INILoadFloat('Light', 'LightThreshhold');
    MinLightHourSpan := INILoadFloat(         'MinLightHourSpan');

  finalization
    Cleanup;
end.
