unit table_Thermostats;
  interface
    procedure ProcessCurrentLine_Thermostats;

  implementation
    uses
      BoardManager  (* stRelay *),
      Dashboard     (* fmDashboard *),
      DateUtils     (* DateOf, IncDay, StartOfTheDay *),
      GraphManager2 (* fmGraphManager *),
      HC3DM         (* dmHC3, Global *),
      HC3Utilities  (* LastSecondOfTheDay, LastUIDOf *),
      RRF_StringAndTimeUtils (* LocalToUTC *),
      ObservedAndWM (* GetObservedName, tObserved *),
      SensorDevice  (* tSensorDevice *),
      TableManager  (* tuThermostat *);

    type
      tThermostatDevice = class(tSensorDevice)
        private
          TargetAtEdge: real;
          WhenAtEdgeLocal: tDateTime;
          UIDAtEdge: integer;
          TheObservedUID: tObserved;
        public
          constructor Create(const ObservedUID: tObserved);
          procedure GenerateNewPair(const AnchorTime: tDateTime;
                                    const NewTarget: real);
          procedure NewData(const NewTargetF: real);
        end (* tThermostatDevice *);

    const
      MaxDevices = 5;

    var
      NDevices: integer;
      ThermostatDevice: array[1 .. MaxDevices] of tThermostatDevice;

    procedure ProcessCurrentLine_Thermostats;
      (* if needed, create device.  Arg is new TargetF *)

      var
        DeviceIndex: integer;

      procedure GetDeviceIndex(const ObservedUID: tObserved);
        (* this may entail creating the device, if this ObservedUID &
         station tag are new *)
      
        function FindDeviceIndex: integer;
          (* if not found, return 0 *)

          var
            I: integer;

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

        begin (* GetDeviceIndex *)
          DeviceIndex := FindDeviceIndex;
          if DeviceIndex = 0 then
            begin (* create new device *)
              inc(NDevices);
              ThermostatDevice[NDevices] := tThermostatDevice.Create(ObservedUID);
              DeviceIndex := NDevices
            end   (* create new device *)
        end   (* GetDeviceIndex *);

      begin (* ProcessCurrentLine_Thermostats *)
        with Global.LatestData do
          begin (* pass arg to the sensor device *)
            GetDeviceIndex(ObservedUID);
            ThermostatDevice[DeviceIndex].NewData(RealArg[1])
          end   (* pass arg to the sensor device *);
      end   (* ProcessCurrentLine_Thermostats *);

{ tThermostatDevice }

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

      const
        DefaultFallback = 40;

      var
        AnchorTime: tDateTime;
        
      begin (* tThermostatDevice.Create *)
        inherited Create(tuThermostats, ObservedUID, Global.LatestData.UTCStamp, stRelay);
        TheObservedUID := ObservedUID;

        with fmDashboard do
          ExtendChat('th_100 created thermostat device for ' + GetObservedName(ObservedUID),
                     llblCreated);
                     
        with qGetRecentData do
          begin (* qGetRecentData *)
            Open;
            if RecordCount = 0 then
              begin (* make up starter set *)
                AnchorTime := StartOfTheDay(Global.LatestData.LocalStamp);
                TargetAtEdge := DefaultFallback;
                GenerateNewPair(AnchorTime, TargetAtEdge);
                UIDAtEdge := LastUIDOf('Thermostats');
                WhenAtEdgeLocal := IncSecond(AnchorTime)
              end   (* make up starter set *)

            else
              begin (* normal case *)
                TargetAtEdge    := FieldByName('TargetF'     ).AsFloat;
                UIDAtEdge       := FieldByName('UID'         ).AsInteger;
                WhenAtEdgeLocal := FieldByName('WhenRecorded').AsDateTime
              end   (* normal case *);

            Close
          end   (* qGetRecentData *);
      end   (* tThermostatDevice.Create *);

    procedure tThermostatDevice.GenerateNewPair(const AnchorTime: tDateTime;
                                                const NewTarget: real);
        (* new target at anchor, new edge just after it *)

        var
          AnchorUTC: tDateTime;
          NewUID: integer;

      begin (* tThermostatDevice.GenerateNewPair *)
        AnchorUTC := LocalToUTC(AnchorTime);
        NewUID    := LastUIDOf('Thermostats') + 1;

        with dmHC3.qNewThermostatEntry do
          begin (* qNewThermostatEntry *)
            ParamByName('ObservedUID'    ).AsInteger  := ord(TheObservedUID);
            ParamByName('TargetF'        ).AsFloat    := NewTarget;
            ParamByName('WhenRecorded'   ).AsDateTime := AnchorTime;
            ParamByName('WhenRecordedUTC').AsDateTime := AnchorUTC;
            ParamByName('UID'            ).AsInteger  := NewUID;
            ExecSQL;

            WhenAtEdgeLocal := IncSecond(AnchorTime);
            UIDAtEdge := NewUID + 1;
            TargetAtEdge := NewTarget;

            ParamByName('WhenRecorded'   ).AsDateTime := WhenAtEdgeLocal;
            ParamByName('WhenRecordedUTC').AsDateTime := LocalToUTC(WhenAtEdgeLocal);
            ParamByName('UID'            ).AsInteger  := UIDAtEdge;
            ExecSQL
          end   (* qNewThermostatEntry *)
      end   (* tThermostatDevice.GenerateNewPair *);

    procedure tThermostatDevice.NewData(const NewTargetF: real);
      (* add data or just advance edge *)

      procedure AdvanceEdge(const NewEdgeTimeLocal: tDateTime);
        (* new times, new HoursOn *)

        var
          NewEdgeTimeUTC: tDateTime;

        begin (* AdvanceEdge *)
          NewEdgeTimeUTC := LocalToUTC(NewEdgeTimeLocal);

          with dmHC3.qMoveThermostatEdge do
            begin (* dmHC3.qMoveThermostatEdge *)
              ParamByName('NewEdgeTimeLocal').AsDateTime := NewEdgeTimeLocal;
              ParamByName('NewEdgeTimeUTC'  ).AsDateTime := NewEdgeTimeUTC;
              ParamByName('UID'             ).AsInteger  := UIDAtEdge;
              ExecSQL
            end   (* dmHC3.qMoveThermostatEdge *);

          WhenAtEdgeLocal := NewEdgeTimeLocal
        end   (* AdvanceEdge *);

      procedure AdvanceOneDay;
        (* move edge to midnight, then start new pair just past midnight. *)

        begin (* AdvanceOneDay *)
          AdvanceEdge(LastSecondOfTheDay(WhenAtEdgeLocal));
          GenerateNewPair(StartOfTheDay(IncDay(WhenAtEdgeLocal)), TargetAtEdge)
        end   (* AdvanceOneDay *);

      begin (* tThermostatDevice.NewData *)
        with Global.LatestData do
          begin (* Global.LatestData *)
            while DateOf(WhenAtEdgeLocal) < DateOf(LocalStamp) do
              AdvanceOneDay;

            if NewTargetF = TargetAtEdge then
              AdvanceEdge(LocalStamp)
            else
              begin (* real new data *)
                AdvanceEdge(IncSecond(LocalStamp, -1));
                GenerateNewPair(LocalStamp, NewTargetF)
              end   (* real new data *)
          end   (* Global.LatestData *);

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

end.
