unit Dispatcher;
  interface
    procedure InitializeDispatcher;
    procedure ProcessDataLine;

  implementation
    uses
      BoardManager       (* fmBoardManager *),
      Dashboard          (* fmDashboard *),
      HC3DM              (* dmHC3, Global, SillyIntegerValue *),
      HC4Main            (* fmHC4Main *),
      HC3Utilities       (* MSMToMilTime *),
      IndoorInstruments  (* fmIndoorSensors *),
      LogManager         (* fmLogManager *),
      MyConsts           (* nullstring *),
      ObservedAndWM      (* tFailureReason, tMeasurements2, tSensorType,
                              tWhatMeasured2, wm2CO2ppm, wm2DeviceFailure, wm2RH,
                              wm2SpeedupChanged, wm2TempF *),
      OutdoorInstruments (* fmOutdoorInstruments *),
      RelayManager       (* ProcessCurrentLine_relay *),
      SysUtils           (* FloatToStr, Format, StrToIntDef *),
      TableManager       (* tuCO2, tuLight, tuOnOff, tuPond, tuPower, tuRain,
                              tuSPS30, tuThermostats, tuTRHP, tuVC0706, tuWind*),
      table_Generic      (* ProcessCurrentLine_Generic *),
      table_Light        (* ProcessCurrentLine_Light *),
      table_OnOff        (* ProcessCurrentLine_OnOff *),
      table_Pond         (* ProcessCurrentLine_Pond *),
      table_Rain         (* ProcessCurrentLine_Rain *),
      table_Thermostats  (* ProcessCurrentLine_Thermostats *),
      VC0706             (* fmVC0706 *);

    const
      MaxSCDxx = 5;

    type
      tSCDxxCache = record
        TheSensorID: integer;
        UsesRHTempF: boolean;
        end (* tSCDxxCache *);

    var
      SCDxxCache: array[1 .. 5] of tSCDxxCache;

    procedure InitializeDispatcher;
      (* clear cache *)

      var
        I: integer;

      begin (* InitializeDispatcher *)
        for I := 1 to MaxSCDxx do
          SCDxxCache[I].TheSensorID := SillyIntegerValue
      end   (* InitializeDispatcher *);

    procedure ProcessDataLine;
      (* service routine for Dashboard.  The Parsed line is
        count                                                         Count-2   Count-1
        index     0              1             2      3   (4      5       6)*    3*NArgs+1
          (btwDumpData or
             btwLiveData)  <UTCtimestamp>  <GoodTag> <Slot> (<wm> <man> <negExp>)* <checksum>

        OR                                 <BadTag>  <Slot>  <reason>
       *)

      var
        Crashed: boolean;
        intBoardSlot: integer;

      procedure DispatchOnTables;
        (* debulk ProcessDataLine.  This is called even if tag is bad, to
         let sensor-specific unit react *)

        procedure DeviceFailure;
          (* need to see which instrument UI will handle it *)

          var
            Indoor: boolean;

          begin (* DeviceFailure *)
            with Global.LatestData do
              begin (* almost a case statement *)
                case Table[1] of
                         tuPond,
                         tuWind: Indoor := false;

                        tuPower,
                  tuThermostats: Indoor := true
                  
                            else Indoor := (ObservedUID <> obOutside)
                  end (* case *);

                if Indoor then
                  fmIndoorSensors.BadData(tFailureReason(IntArg[1]))
                else
                  fmOutdoorInstruments.BadData(tFailureReason(IntArg[1]))
              end   (* almost a case statement *)
          end   (* DeviceFailure *);

        procedure ProcessTheLine;
          (* mostly table-specific *)

          var
            I: integer;

          function RHorTempWanted: boolean;
            (* at least one wanted in table.  This code ASSUMES that there will
             never be more than MaxSCDxx SCDxx sensors *)

            var
              I, SensorNumber: integer;

            begin (* MeasureWanted *)
              Result := false;
              SensorNumber := Global.LatestData.TheSensorUID;

              for I := 1 to MaxSCDxx do
                with SCDxxCache[I] do
                  if TheSensorID = SensorNumber then
                    begin (* cached *)
                      Result := UsesRHTempF;
                      break
                    end   (* cached *)

                  else if TheSensorID = SillyIntegerValue then
                    begin (* need to check table *)
                      with dmHC3.qSensorMeasurementsWanted do
                        begin (* qSensorMeasurementsWanted *)
                          Close;
                          ParamByName('TableUID' ).AsInteger := ord(tuTRHP);
                          ParamByName('SensorUID').AsInteger := SensorNumber;
                          Open;
                          while not EOF do
                            begin (* one record *)
                              if Fields[0].AsInteger in [ord(wm2RH), ord(wm2TempF)] then
                                begin (* result known *)
                                  Result := true;
                                  break
                                end   (* result known *);

                              Next
                            end   (* one record *);
                          Close
                        end   (* qSensorMeasurementsWanted *);
                      TheSensorID := SensorNumber;
                      UsesRHTempF := Result;
                      if not Result then
                        fmDashboard.ExtendChat(Format('di_ 147 sensor #%d does not report RH or temp',
                                                      [SensorNumber]));
                      break
                    end   (* need to check table *)
            end   (* MeasureWanted *);

          begin (* ProcessTheLine *)
            with Global.LatestData, fmDashboard do
              begin (* Global.LatestData, fmDashboard *)
                if ckbxDataInListbox.Checked then
                  for I := 1 to NArgs do
                    if WMArg[I] = wm2Calibrate then
                      ExtendChat(Format('di_159   #%d: calibrate = %s',
                                        [I, FloatToStr(RealArg[I])] ))
                    else if WMArg[I] in NoValueMeasurements then
                      ExtendChat(Format('di_162   #%d: %s',
                                        [I,
                                         MeasurementInfo2[WMArg[I]].Name]))
                    else
                      ExtendChat(Format('di_166   #%d: %s = %.2f, %s.%s',
                                        [I,
                                         MeasurementInfo2[WMArg[I]].Name,
                                         RealArg[I],
                                         TableInfo[Table[I]].TheTableName,
                                         FieldName[I]]));

                if SensorType in [stSCD40, stSCD30] then
                  begin (* one sensor, two tables *)
                    if wm2CO2ppm in Measurements then
                      ProcessCurrentLine_Generic(tuCO2);
                    if RHorTempWanted then
                      ProcessCurrentLine_Generic(tuTRHP)
                  end   (* one sensor, two tables *)
                
                else
                  case Table[1] of
                          tuPower,
                          tuSPS30,
                           tuTRHP,
                           tuWind: if not ProcessCurrentLine_Generic(Table[1]) then
                                     ExtendChat('di_184 could not process generic line');
                          tuLight: ProcessCurrentLine_Light;       // maintains pairs
                          tuOnOff: ProcessCurrentLine_OnOff;       // maintains pairs
                           tuPond: ProcessCurrentLine_Pond;        // sparse, so needs day start
                           tuRain: ProcessCurrentLine_Rain;        // special accumulation
                    tuThermostats: ProcessCurrentLine_Thermostats; // maintains pairs
                         tuVC0706: fmVC0706.ProcessCurrentLine     // cycles
                    end (* case on FirstTable *)
              end   (* Global.LatestData, fmDashboard *)
          end   (* ProcessTheLine *);

        begin (* DispatchOnTables *)
          // fmDashboard.ExtendChat('di_192 DispatchOnTables');
          with Global.LatestData do
            begin (* Global.LatestData *)
              fmLogManager.NewDataLine();
              if NArgs >= 1 then
                case WMArg[1] of
                       wm2Bogus,
                       wm2Debugging,
                       wm2Undefined: with fmLogManager.lbxSelected do
                                       ItemIndex := Items.Add(ExpandedLine);

                   wm2DeviceFailure: DeviceFailure;

                  wm2SpeedupChanged: fmBoardManager.LogSpeedupChange(IntArg[1] > 0);

                                else ProcessTheLine
                  end (* case on WMArg[1] *)
            end   (* Global.LatestData *)
        end   (* DispatchOnTables *);

      procedure LogXBeeRegistration;
        (* line is
              0        1        2   3    4
              2 <UTC timestamp> X <MY> <NI>
        *)

        var
          MY: integer;

        begin (* LogXBeeRegistration *)
          with Global.LatestData do
            begin (* Global.LatestData *)
              MY := StrToIntDef(Strings[3], 0);
              fmDashboard.ExtendChat(Format('di_232 XBee MY = %d (0x%x), NI = "%s"',
                                            [MY, MY, Strings[4]]))
            end   (* Global.LatestData *)
        end   (* LogXBeeRegistration *);
                                                                 {$WARNINGS OFF}

      begin (* ProcessDataLine *)
      (* service routine for Dashboard.  The Parsed line is
        count                                                         Count-2   Count-1
        index     0              1             2      3   (4      5       6)*    3*NArgs+1
          (btwDumpData or
             btwLiveData)  <UTCtimestamp>  <GoodTag> <Slot> (<wm> <man> <negExp>)* <checksum>

        OR                                 <BadTag>  <Slot>  <reason>
       *)
        // fmDashboard.ExtendChat('di_228: ProcessDataLine');
        with Global.LatestData do
          if Count >= 4 then
            if Strings[2] <> nullstring then
              begin (* Parser *)
                BoardTag     := Strings[2][1];
                intBoardSlot := StrToIntDef(Strings[3], 0);
                if not fmBoardManager.BoardTagRecognized(BoardTag) then
                  fmDashboard.ExtendChat(Format('di_255 board tag %s not recognized in "%s"',
                                                [Strings[2], TheLine]))
                else
                  begin (* tag OK *)
                    Objects[3] := tObject(intBoardSlot); // generates warning
                    if BoardTag = 'X' then
                      LogXBeeRegistration

                    else if intBoardSlot = ord(bsBoardReboot) then
                      begin (* describe reboot *)
                        Crashed := true;
                        if Count >= 6 then
                          Crashed := (StrToIntDef(Strings[5], 0) = 0);

                        fmBoardManager.LogReboot(BoardTag, Crashed)
                      end   (* describe reboot *)

                    else if ObjectsOrganized then
                      begin (* line parsed *)
                        fmBoardManager.NewData(BoardTag, LocalStamp);
                        DispatchOnTables
                      end   (* line parsed *)

                    else
                      fmDashboard.LineFlunked
                  end   (* tag OK *)
              end   (* Parser *)
      end   (* ProcessDataLine *);
                                                                 {$WARNINGS ON}

end.
