unit SensorDevice;

  interface
    uses
      AdsTable      (* tAdsQuery *),
      LineParser    (* tLineParser *),
      ObservedAndWM (* tFieldType, MeasurementInfo2, tMeasurements2,
                         NoValueMeasurements, obImplicit, tObserved,
                         tRRFFieldType, rrftBoolean, rrftFloat, rrftInteger,
                         tSensorType, stBogus, tTableUsed, tuOnOff, tuRain,
                         tuTRHP, tWhatMeasured2 *),
      TableManager  (* tMeasurements, TableInfo *);

    type
      tSensorDevice = class(tObject)
        protected
          qGetRecentData: tAdsQuery;
          TheLocalEdge: tDateTime;
          TheSensorUID: integer;
          TheMeasurements: tMeasurements2; // set by descendants
          TheObservedUID: tObserved;       // set by descendants
          TheSensorType: tSensorType;
          TheTableName: string;
          TheTableUsed: tTableUsed;
          constructor Create(const TU: tTableUsed;
                             ObservedUID: tObserved = obImplicit;
                             LastReportUTC: tDateTime = 0;
                             SensorType: tSensorType = stBogus);
          procedure   RunStoreDataQuery;
        public
          qStoreData: tAdsQuery;
          destructor Destroy; override;
        end (* tSensorDevice *);

  implementation
    uses
      Dashboard          (* fmDashboard *),
      DB                 (* ftBoolean, ftFloat, ftInteger *),
      Dialogs            (* ShowMessageFmt *),
      HC3DM              (* dmHC3, Global *),
      HC3Utilities       (* LastUIDOf *),
      IndoorInstruments  (* fmIndoorSensors *),
      MyConsts           (* BLANK, nullstring *),
      MyUtils            (* UTCToLocal *),
      OutdoorInstruments (* fmOutdoorInstruments *),
      SysUtils           (* Format *);

{ tSensorDevice }

    constructor tSensorDevice.Create(const TU: tTableUsed;
                                     ObservedUID: tObserved;
                                     LastReportUTC: tDateTime;
                                     SensorType: tSensorType);
      (* base class *)

      procedure MakeRecentDataQuery;
        (*   define qGetRecent to pull the most recent values of this
          line's fields from this line's first table.  That is,
            select top 2          // 2 for OnOff pairs or Rain
              <fieldnames>
            from
              <tablename>
            [where
              ObservedUID = TheObserved]
            [where
              (FieldName is not null)*]
            order by
              WhenRecordedUTC desc
         Fields that are maintained although not explicit in data must be fetched.

         *)

        var
          I: integer;
          TheFieldList, TheSQL, WhereClause: string;

        begin (* MakeRecentDataQuery *)
          with Global.LatestData do
            begin (* Global.LatestData *)
              TheFieldList := 'WhenRecorded, WhenRecordedUTC, UID';

              for I := 1 to NArgs do
                if Table[I] = TheTableUsed then
                  if not (WMArg[I] in NoValueMeasurements) then
                    TheFieldList := TheFieldList + ', ' + FieldName[I];

              case TheTableUsed of  // fields tabulated but computed from data
                tuOnOff: TheFieldList := TheFieldList + ', HoursOn'  ;
                 tuRain: TheFieldList := TheFieldList + ', MMPerHour';
                 tuTRHP: TheFieldList := TheFieldList + ', DewpointF, HumidexF';
                end (* case *);

              if TableInfo[TheTableUsed].UsesObserved then
                WhereClause := Format('where ObservedUID = %d ', [ord(TheObservedUID)])
              else
                WhereClause := nullstring;

              TheSQL := 'select top 2           ' +
                           TheFieldList + BLANK   +
                        'from                   ' +
                           TheTableName + BLANK   +
                         WhereClause              +
                        'order by               ' +
                        '   WhenRecordedUTC desc';

              qGetRecentData := tAdsQuery.Create(nil);
              qGetRecentData.DatabaseName := 'cnHC3Data';
              qGetRecentData.SQL.Text := TheSQL;

              with fmDashboard do
                if ckbxShowGetRecentSQL.Checked then
                  ExtendChat('sd_128   ' +
                             Format('qGetRecentData[%s]: %s',
                                    [TheTableName, TrimInternalWhiteSpace(TheSQL)]))
            end (* Global.LatestData *)
        end   (* MakeRecentDataQuery *);

      procedure MakeStoreDataQuery;
        (* The query will be
             insert into
               Table(UID,
                     WhenRecorded,
                     WhenRecordedUTC,
                     [Observed,]
                     <fieldnames>)
               values(:etc)
        *)

        var
          I: integer;
          TheFieldList, TheSQL, TheValueList: string;

        procedure AddField(const ThisFieldName: string);
          (* common code *)

          begin (* AddField *)
            TheFieldList := Format('%s,  %s', [TheFieldList, ThisFieldName]);
            TheValueList := Format('%s, :%s', [TheValueList, ThisFieldName])
          end   (* AddField *);

        begin (* MakeStoreDataQuery *)
          with Global.LatestData do
            begin (* Global.LatestData *)
              TheFieldList := ' UID,  WhenRecorded,  WhenRecordedUTC';
              TheValueList := ':UID, :WhenRecorded, :WhenRecordedUTC';

              if TableInfo[TheTableUsed].UsesObserved then
                AddField('ObservedUID');

              for I := 1 to NArgs do
                if Table[I] = TheTableUsed then
                  if not (WMArg[I] in NoValueMeasurements) then
                    AddField(FieldName[I]);

              TheSQL := 'insert into                                    ' +
                           TheTableName + Format('(%s) ', [TheFieldList]) +
                           Format('values(%s)', [TheValueList]);

              qStoreData := tAdsQuery.Create(nil);
              qStoreData.DatabaseName := 'cnHC3Data';
              qStoreData.SQL.Text := TheSQL;

              with fmDashboard do
                if ckbxShowGetRecentSQL.Checked then
                  ExtendChat('sd_181   ' +
                             Format('qStoreData[%s]: %s',
                                    [TheTableName, TrimInternalWhiteSpace(TheSQL)]))
            end   (* Global.LatestData *)
        end   (* MakeStoreDataQuery *);

      begin (* tSensorDevice.Create *)
        inherited Create;

        // TheLastReportTimeUTC   := LastReportUTC;
        // TheLastReportTimeLocal := UTCToLocal(TheLastReportTimeUTC);
        TheObservedUID         := ObservedUID;
        TheSensorType          := SensorType;
        TheSensorUID           := Global.LatestData.TheSensorUID;
        TheTableName           := TableInfo[TU].TheTableName;
        TheTableUsed           := TU;
        MakeRecentDataQuery;
        MakeStoreDataQuery
      end   (* tSensorDevice.Create *);
 
    destructor tSensorDevice.Destroy;
      (* reclaim storage *)

      begin (* tSensorDevice.Destroy *)
        qGetRecentData.Free;
        qStoreData.Free;
        inherited Destroy
      end   (* tSensorDevice.Destroy *);

    procedure tSensorDevice.RunStoreDataQuery;
      (* mainly just a lot of ParamByName *)

      var
        FieldType: tRRFFieldType;
        I, NewUID: integer;
        WM: tWhatMeasured2;

      begin (* tSensorDevice.RunStoreDataQuery *)
        with Global.LatestData do
          begin (* Global.LatestData *)
            with qStoreData do
              begin (* qStoreData *)
                NewUID := LastUIDOf(TheTableName) + 1;

                ParamByName('UID'            ).AsInteger  := NewUID;
                ParamByName('WhenRecorded'   ).AsDateTime := LocalStamp;
                ParamByName('WhenRecordedUTC').AsDateTime := UTCStamp;
                if TableInfo[TheTableUsed].UsesObserved then
                  ParamByName('ObservedUID').AsInteger := ord(TheObservedUID);

                for I := 1 to NArgs do
                  if Table[I] = TheTableUsed then
                    begin (* one field *)
                      WM := WMArg[I];
                      if WM <> wm2Calibrate then
                        begin (* nondegenerate field *)
                          FieldType := MeasurementInfo2[WM].FieldType;
                          if FieldType in [rrftBoolean, rrftFloat, rrftInteger] then
                            if FieldName[I] = nullstring then
                              ShowMessageFmt('arg #%d: ord(WM) = %d, ord(FieldType) = %d and FieldName null',
                                             [I, ord(WM), ord(FieldType)])
                            else
                              case FieldType of
                                rrftBoolean: ParamByName(FieldName[I]).AsBoolean := BoolArg[I];
                                  rrftFloat: ParamByName(FieldName[I]).AsFloat   := RealArg[I];
                                rrftInteger: ParamByName(FieldName[I]).AsInteger := IntArg[I]
                                end (* case *)
                        end   (* nondegenerate field *);

                    end   (* one field *);

                ExecSQL
              end   (* qStoreData *);

            RecentUID := NewUID
          end   (* Global.LatestData *)
      end   (* tSensorDevice.RunStoreDataQuery *);
end.
