unit Extremes2;

  interface
    uses
      AdsData          (* tAdsStringField *),
      AdsFunc          (* used by AdsTable *),
      AdsTable         (* tAdsQuery, tAdsTable *),
      AdvGrid          (* TMS *),
      AdvObj           (* TMS *),
      AdvUtil          (* TMS *),
      BaseGrid         (* TMS *),
      Buttons          (* tBitBtn *),
      Classes          (**),
      ComCtrls         (* tDateTimePicker, tPageControl, tTabSheet *),
      Controls         (* crHourglass, tCursor, tDate *),
      DB               (* tBooleanField, tDataSet, tDataSource, tDateField,
                            tFloatField, tIntegerField, tNumericField,
                            tStringField; also used by DBGrids *),
      DBAdvGrid        (* tDBAdvGrid *),
      DBCtrls          (* tDBNavigator, tDBText *),
      //DBGrids          (* tDBGrid *),
      ExtCtrls         (* tLabeledEdit, tRadioGroup *),
      Forms            (* tForm, Screen *),
      Graphics         (* TMS *),
      Grids            (* TMS *),
      LabeledSpinEdits (* tLabeledSpinInteger*),
      PickDateRange    (* tDateRange *),
      RRFSpinBase      (* used by LabeledSpinEdits *),
      StdCtrls         (* tButton, tCheckBox, tGroupBox, tLabel *),
      Windows          (**);

    type
      tfmExtremes2 = class(tForm)
          pgctlExtremes2: tPageControl;
            tabConfigure: tTabSheet;
              qNewExtremeSpec: tAdsQuery;
              grpNewExtreme: tGroupBox;
                btnNewOK: tBitBtn;
                grpNewFromData: TGroupBox;
                  qDataTables: tAdsQuery;
                    qDataTablesTableName: tAdsStringField;
                    qDataTablesTableUID: tIntegerField;
                    dataDataTables: tDataSource;
                      qDataTableFields: tAdsQuery;
                        dataDataTableFields: tDataSource;
                          dbadvgridDataTableFields: tDBAdvGrid;
                      qObserved: tAdsQuery;
                        dataObserved: tDataSource;
                          dbadvgridObserved: tDBAdvGrid;
                      dbadvgridDataTables: tDBAdvGrid;
                grpNewFromSummary: TGroupBox;
                  qSummaryFields: tAdsQuery;
                    dataSummaryFields: tDataSource;
                      dbadvgridSummaryFields: tDBAdvGrid;
                lbedColumnHeader:  tLabeledEdit;
                lbedDisplayFormat: tLabeledEdit;
                lbedNewName:       tLabeledEdit;
                lbspintNewSeq: tLabeledSpinInteger;
                rgNewKind:   tRadioGroup;
                rgNewMaxMin: tRadioGroup;
            tabExtremes: tTabSheet;
              qEmptyRainStreaks: tAdsQuery;
              qFindExtremes:     tAdsQuery;
                qFindExtremesTheDate: tDateField;
                qFindExtremesTheDatum: tFloatField;
                dataFindExtremes: tDataSource;
                  dbadvgridFindExtremes: tDBAdvGrid;
                  navExtremes: tDBNavigator;
                  dbtxWhichExtreme: tDBText;
              qFindInHC3Data: tAdsQuery;
                qFindInHC3DataTheDate: tDateField;
                qFindInHC3DataTheDatum: tFloatField;
              qFindInHomeControl: tAdsQuery;
                qFindInHomeControlTheDate: tDateField;
                qFindInHomeControlTheDatum: tFloatField;
              qRainForConsecutive: tAdsQuery;
              qRainStreaks:        tAdsQuery;
                qRainStreaksTheDate: tDateField;
                qRainStreaksTheDatum: tIntegerField;
              tbRainStreaks: tAdsTable;
              btnFindExtremes: tButton;
              grpDatePicker: tGroupBox;
                btnDatePicker: tButton;
                dtpPicker: tDateTimePicker;
              lbRank: tLabel;
              rgDateRange:     tRadioGroup;
              rgOrderExtremes: tRadioGroup;
            tabConfigureTable: tTabSheet;
              qExtremeSpecs: tAdsQuery;
                qExtremeSpecsColHeader:     tAdsStringField;
                qExtremeSpecsDisplayFormat: tAdsStringField;
                qExtremeSpecsKind:          tAdsStringField;
                qExtremeSpecsName:          tAdsStringField;
                qExtremeSpecsTheFieldName:  tAdsStringField;
                qExtremeSpecsMaxTrueMinFalse: tBooleanField;
                qExtremeSpecsSequence:       tIntegerField;
                qExtremeSpecsTheObservedUID: tIntegerField;
                qExtremeSpecsTheTableUID:    tIntegerField;
                qExtremeSpecsUID:            tIntegerField;
                qExtremeSpecsXFieldName: tStringField;
                qExtremeSpecsXObserved:  tStringField;
                qExtremeSpecsXTableName: tStringField;
                dataExtremeSpecs: tDataSource;
                  dbadvgridChooseExtremes: tDBAdvGrid;
              dbadvgridExtremeSpecs: tDBAdvGrid;
              navExtremeSpecs: tDBNavigator;

          procedure qDataTablesAfterScroll  (DataSet: tDataSet);
          procedure qExtremeSpecsAfterOpen  (DataSet: tDataSet);
          procedure qExtremeSpecsAfterScroll(DataSet: tDataSet);
          procedure qExtremeSpecsCalcFields (DataSet: tDataSet);

          procedure btnDatePickerClick           (Sender: tObject);
          procedure btnFindExtremesClick         (Sender: tObject);
          procedure btnNewOKClick                (Sender: tObject);
          procedure dbadvgridFindExtremesDblClick(Sender: TObject);
          procedure FormCreate                   (Sender: tObject);
          procedure FormDblClick                 (Sender: tObject);
          procedure FormDestroy                  (Sender: tObject);
          procedure FormShow                     (Sender: tObject);
          procedure lbedNewNameChange            (Sender: tObject);
          procedure rgDateRangeClick             (Sender: tObject);
          procedure rgNewKindClick               (Sender: tObject);
          procedure rgOrderExtremesClick         (Sender: tObject);
        private
          TheDateRange: tDateRange;
          mmRainTrace: real;
          procedure Validate;
        end (* tfmExtremes2 *);

    var
      fmExtremes2: tfmExtremes2;

  implementation
    uses
      DateUtils      (* DateOf, IncYear, StartOfAYear, Today *),
      DBAdvGridUtils (* FieldReadOnly, SetUpDBAdvGrid, SetUpDBAdvGridReadOnly *),
      Dialogs        (* ShowMessageFmt *),
      HC3DM          (* UsualFormColor *),
      HC4Main        (* fmHC4Main *),
      HC3Utilities   (* GetFieldWidth, INILoadFloat, LastUIDOfOld,
                          TrimInternalWhiteSpace *),
      MyConsts       (* nullstring *),
      ObservedAndWM  (* GetObservedName, tObserved, tTableUsed *),
      SQLUtils       (* qRequestLiveAfterOpen *),
      SysUtils       (* Format, FormatDateTime, Trim *),
      TableManager   (* TableInfo *);
      {$R *.dfm}

{****** infrastructure ********************************************************}

    var
      DataColumn: integer;

    procedure tfmExtremes2.FormCreate(Sender: tObject);
      (* embedded objects *)
    
      begin (* tfmExtremes2.FormCreate *)
        TheDateRange := tDateRange.Create('extremes');
        TheDateRange.SetToAllDates
      end   (* tfmExtremes2.FormCreate *);

    procedure tfmExtremes2.FormDblClick(Sender: tObject);
      (* show master form *)
    
      begin (* tfmExtremes2.FormDblClick *)
        fmHC4Main.Show
      end   (* tfmExtremes2.FormDblClick *);

    procedure tfmExtremes2.FormDestroy(Sender: tObject);
      (* reclaim storage *)
    
      begin (* tfmExtremes2.FormDestroy *)
        TheDateRange.Free
      end   (* tfmExtremes2.FormDestroy *);

    procedure tfmExtremes2.FormShow(Sender: tObject);
      (* initialize *)
    
      begin (* tfmExtremes2.FormShow *)
        self.Color := UsualFormColor;
        { extremes page }
            SetUpDBAdvGridReadOnly(dbadvgridChooseExtremes);

            SetUpDBAdvGridReadOnly(dbadvgridFindExtremes);
              DataColumn := FieldNameToColumn('TheDatum');

        { configure page }
            lbedColumnHeader.MaxLength  := GetFieldWidth('ExtremeSpecs', 'ColHeader');
            lbedDisplayFormat.MaxLength := GetFieldWidth('ExtremeSpecs', 'DisplayFormat');
            lbedNewName.MaxLength       := GetFieldWidth('ExtremeSpecs', 'Name');
            mmRainTrace                 := INILoadFloat('Rain', 'mmTrace');
            rgNewKindClick(nil);
            qExtremeSpecs.Open;
            qSummaryFields.Open;
              SetUpDBAdvGridReadOnly(dbadvgridSummaryFields);

            qDataTables.Open;
              SetUpDBAdvGridReadOnly(dbadvgridDataTables);

            qDataTableFields.Open;
              SetUpDBAdvGridReadOnly(dbadvgridDataTableFields);

            qObserved.Open;
              SetUpDBAdvGridReadOnly(dbadvgridObserved);

        { configuration table }
            SetUpDBAdvGrid(dbadvgridExtremeSpecs);
              FieldReadOnly('UID');
              FieldReadOnly('Name');
              FieldReadOnly('Kind');
              FieldReadOnly('XTableName');
              FieldReadOnly('XFieldName');
              FieldReadOnly('XObserved');
              FieldReadOnly('ColHeader');
              FieldReadOnly('DisplayFormat');

        Validate;
        pgctlExtremes2.ActivePage := tabExtremes
      end   (* tfmExtremes2.FormShow *);

    procedure tfmExtremes2.qExtremeSpecsAfterScroll(DataSet: tDataSet);
      (* disambiguate UI *)
    
      begin (* tfmExtremes2.qExtremeSpecsAfterScroll *)
        dbadvgridFindExtremes.Visible := false
      end   (* tfmExtremes2.qExtremeSpecsAfterScroll *);

{****** end infrastructure ****************************************************}
{****** configure *************************************************************}
    procedure tfmExtremes2.btnNewOKClick(Sender: tObject);
      (* everything is ready *)

      var
        MaxTrueMinFalse: boolean;
        NewUID, TableUID: integer;
        Kind: string;

      begin (* tfmExtremes2.btnNewOKClick *)
        with rgNewKind do
          Kind := copy(Items[ItemIndex], 1, 1);
        MaxTrueMinFalse := (rgNewMaxMin.ItemIndex = 0);
        NewUID := LastUIDOfOld('ExtremeSpecs') + 1;

        with qNewExtremeSpec do
          begin (* qNewExtremeSpec *)
            ParamByName('ColHeader'    ).AsString  := lbedColumnHeader.Text;
            ParamByName('DisplayFormat').AsString  := lbedDisplayFormat.Text;
            ParamByName('Kind'         ).AsString  := Kind;
            ParamByName('Name'         ).AsString  := Trim(lbedNewName.Text);
            ParamByName('Sequence'     ).AsInteger := lbspintNewSeq.Value;
            ParamByName('UID'          ).AsInteger := NewUID;

            ParamByName('TheTableUID' ).Clear;
            ParamByName('TheFieldName').Clear;

            case Kind[1] of
              'D': begin (* simple from data *)
                     TableUID := qDataTables.FieldByName('TableUID').AsInteger;
                     ParamByName('TheTableUID'    ).AsInteger := TableUID;
                     ParamByName('TheFieldName'   ).AsString  := qDataTableFields.Fields[0].AsString;
                     ParamByName('MaxTrueMinFalse').AsBoolean := MaxTrueMinFalse;
                     with ParamByName('TheObservedUID') do
                       if TableInfo[tTableUsed(TableUID)].UsesObserved then
                         AsInteger := qObserved.FieldByName('UID').AsInteger
                       else
                         Clear
                   end   (* simple from data *);

              'S': begin (* simple from summary *)
                     ParamByName('TheFieldName'   ).AsString := qSummaryFields.Fields[0].AsString;
                     ParamByName('MaxTrueMinFalse').AsBoolean := MaxTrueMinFalse
                   end   (* simple from summary *)
              end (* case *);

            ExecSQL
          end   (* qNewExtremeSpec *);

        qExtremeSpecs.Close;
        qExtremeSpecs.Open;
        lbedNewName.Clear;
        Validate
      end   (* tfmExtremes2.btnNewOKClick *);

    procedure tfmExtremes2.lbedNewNameChange(Sender: tObject);
      (* must not be null *)
    
      begin (* tfmExtremes2.lbedNewNameChange *)
        Validate
      end   (* tfmExtremes2.lbedNewNameChange *);

    procedure tfmExtremes2.qDataTablesAfterScroll(DataSet: tDataSet);
      (* list choice of Observed? *)

      var
        TU: tTableUsed;

      begin (* tfmExtremes2.qDataTablesAfterScroll *)
        with DataSet do
          TU := tTableUsed(FieldByName('TableUID').AsInteger);
        dbadvgridObserved.Visible := TableInfo[TU].UsesObserved
      end   (* tfmExtremes2.qDataTablesAfterScroll *);

    procedure tfmExtremes2.qExtremeSpecsAfterOpen(DataSet: tDataSet);
      (* paranoia *)

      begin (* tfmExtremes2.qExtremeSpecsAfterOpen *)
        SQLUtils.qRequestLiveAfterOpen(tAdsQuery(DataSet))
      end   (* tfmExtremes2.qExtremeSpecsAfterOpen *);

    procedure tfmExtremes2.qExtremeSpecsCalcFields(DataSet: tDataSet);
      (* prettyprinting *)

      var
        MaxTrueMinFalse: boolean;
        Observed: tObserved;
        TU: tTableUsed;
        Kind, TheFieldName, XFieldName, XObserved, XTableName: string;

      begin (* tfmExtremes2.qExtremeSpecsCalcFields *)
        with DataSet do
          begin (* DataSet *)
            Kind            :=            FieldByName('Kind'           ).AsString;
            MaxTrueMinFalse :=            FieldByName('MaxTrueMinFalse').AsBoolean;
            Observed        := tObserved (FieldByName('TheObservedUID' ).AsInteger);
            TU              := tTableUsed(FieldByName('TheTableUID'    ).AsInteger);
            TheFieldName    :=            FieldByName('TheFieldName'   ).AsString;

            if MaxTrueMinFalse then
              XFieldName := Format('max(%s)', [TheFieldName])
            else
              XFieldName := Format('min(%s)', [TheFieldName]);

            XObserved := 'N/A';
            case Kind[1] of
              'D': begin (* from data *)
                     XTableName := TableInfo[TU].TheTableName;
                     if TableInfo[TU].UsesObserved then
                       XObserved := GetObservedName(Observed)
                   end   (* from data *);

              'S': XTableName := '(summary)'

              else begin (* special case *)
                     XTableName := 'N/A';
                     XFieldName := 'N/A'
                   end   (* special case *)
              end (* case *);

            FieldByName('XFieldName').AsString := XFieldName;
            FieldByName('XObserved' ).AsString := XObserved;
            FieldByName('XTableName').AsString := XTableName
          end   (* DataSet *)
      end   (* tfmExtremes2.qExtremeSpecsCalcFields *);

    procedure tfmExtremes2.rgNewKindClick(Sender: tObject);
      (* gross type of new spec *)

      begin (* tfmExtremes2.rgNewKindClick *)
        grpNewFromSummary.Visible := false;
        grpNewFromData.Visible := false;

        with rgNewKind do
          case ItemIndex of
                0: begin (* from data *)
                     grpNewFromData.Visible := true;
                     grpNewFromData.Left := grpNewFromSummary.Left
                   end   (* from data *);

                1: grpNewFromSummary.Visible := true
            end (* case *);

        Validate
      end   (* tfmExtremes2.rgNewKindClick *);

    procedure tfmExtremes2.Validate;
      (* ready to add spec? *)

      begin (* tfmExtremes2.Validate *)
        btnNewOK.Enabled := (Trim(lbedNewName.Text) <> nullstring)
      end   (* tfmExtremes2.Validate *);

{****** end configure *********************************************************}
{****** extremes tab **********************************************************}
    procedure tfmExtremes2.btnDatePickerClick(Sender: tObject);
      (* locate, even if not in order *)
    
      begin (* tfmExtremes2.btnDatePickerClick *)
        with dbadvgridFindExtremes.DataSource.DataSet do
          if Active then
            begin (* dbadvgridFindExtremes.DataSource.DataSet *)
              First;
              while not EOF do
                begin (* one record *)
                  if DateOf(FieldByName('TheDate').AsDateTime) = DateOf(dtpPicker.Date) then
                    break;
                  Next
                end   (* one record *)
            end   (* dbadvgridFindExtremes.DataSource.DataSet *)
      end   (* tfmExtremes2.btnDatePickerClick *);

    procedure tfmExtremes2.btnFindExtremesClick(Sender: tObject);
      (* generate query and run it *)

      const
        ConvertDateTest =
          'convert(WhenRecorded, SQL_DATE) between :DomainBegin and :DomainEnd ';
        DateTest = 'TheDate between :DomainBegin and :DomainEnd ';

      type
        tRainWanted = (rwDry, rwTrace, rwWet);

      var
        MaxTrueMinFalse: boolean;
        OldCursor: tCursor;
        ObservedUID: integer;
        TU: tTableUsed;
        ColHeader, Kind, MinMaxField, TheDisplayFormat, TheFieldName: string;

      function OrderBy: string; forward;

      procedure ConsecutiveRainyDays(const RainWanted: tRainWanted);
        (* debulk btnFindExtremesClick *)

        var
          InStreak, ThisDateQualifies: boolean;
          PreviousDate, ThisDate: tDate;
          RainMMThisDate: double;
          StreakLength: integer;

        procedure StartStreak;
          (* this day different *)
        
          begin (* StartStreak *)
            StreakLength := 1;
            with tbRainStreaks do
              begin (* new record *)
                Insert;
                  FieldByName('StartRun').AsDateTime := ThisDate;
                  FieldByName('RunLength').AsInteger := StreakLength;
                Post
              end   (* new record *);
            InStreak := true
          end   (* StartStreak *);

        begin (* ConsecutiveRainyDays *)
          qEmptyRainStreaks.ExecSQL;
          InStreak          := false;
          PreviousDate      := 0;
          ThisDateQualifies := false;  // avoid compiler warning
          tbRainStreaks.Open;

          with qRainForConsecutive do
            begin (* qRainForConsecutive *)
              Close;
              TheDateRange.SetQueryDates(qRainForConsecutive);
              Open;

              while not EOF do
                begin (* one record *)
                  ThisDate       := DateOf(FieldByName('TheDate').AsDateTime);
                  RainMMThisDate :=        FieldByName('Rainmm' ).AsFloat;

                  case RainWanted of
                      rwDry: ThisDateQualifies := (RainMMThisDate = 0);
                    rwTrace: ThisDateQualifies := (RainMMThisDate > 0);
                      rwWet: ThisDateQualifies := (RainMMThisDate > mmRainTrace)
                    end (* case *);

                  if DaysBetween(ThisDate, PreviousDate) <> 1 then
                    InStreak := false
                  else if not ThisDateQualifies then
                    InStreak := false
                  else if InStreak then
                    with tbRainStreaks do
                      begin (* tbRainStreaks *)
                        inc(StreakLength);
                        Edit;
                          FieldByName('RunLength').AsInteger := StreakLength;
                        Post
                      end   (* tbRainStreaks *)
                  else
                    StartStreak;

                  PreviousDate := ThisDate;
                  Next
                end   (* one record *);

              Close
            end   (* qRainForConsecutive *);

          tbRainStreaks.Close;
          qRainStreaks.Close;
          dataFindExtremes.DataSet := qRainStreaks
        end   (* ConsecutiveRainyDays *);

      procedure HumidityRange;
        (* debulk btnFindExtremesClick *)

        begin (* HumidityRange *)
          MaxTrueMinFalse := true;
          qFindExtremes.SQL.Text := TrimInternalWhiteSpace(
                                      'select                                        ' +
                                      '  TheDate,                                    ' +
                                      '  cast(MaxRH-MinRH as SQL_DOUBLE) as TheDatum ' +
                                      'from                                          ' +
                                      '  Summary3                                    ' +
                                      'where                                         ' +
                                      '  MaxRH is not null and                       ' +
                                      '  MinRH is not null and                       ' +
                                         DateTest                                      +
                                      OrderBy);
        end   (* HumidityRange *);

      function OrderBy: string;
        (* use 'desc'? *)

        begin (* OrderBy *)
          if rgOrderExtremes.ItemIndex = 0 then
            begin (* by datum *)
              Result := ' order by TheDatum ';
              if MaxTrueMinFalse then
                Result := Result + 'desc '
            end   (* by datum *)
          else
            Result := ' order by TheDate desc '
        end   (* OrderBy *);

      procedure Photoperiod;
        (* debulk btnFindExtremesClick.  Might have special format for TheDatum *)

        begin (* Photoperiod *)
          qFindExtremes.SQL.Text := TrimInternalWhiteSpace(
             'select                                                            ' +
             '  TheDate,                                                        ' +
             '  cast(TimeStampDiff(SQL_TSI_Minute, Sunrise, Sunset)/60.0 as SQL_DOUBLE) as TheDatum ' +
             'from                                                              ' +
             '  Summary3                                                        ' +
             'where                                                             ' +
             '  Sunrise is not null and Sunset is not null and                  ' +
                DateTest                                                          +
             OrderBy);
        end   (* Photoperiod *);

      procedure PressureRange;
        (* debulk btnFindExtremesClick *)

        var
          OldSQLSchema: string;

        begin (* PressureRange *)
          qFindExtremes := qFindInHC3Data;
          MaxTrueMinFalse := true;
          OldSQLSchema := TrimInternalWhiteSpace(
                            'select ' +
                            '  convert(WhenRecorded, SQL_DATE)            as TheDate, ' +
                            '   max(PressureInches) - min(PressureInches) as TheDatum ' +
                            'from                                          ' +
                            '  TRHP_old                                    ' +
                            'where                                         ' +
                            '  PressureInches is not null and              ' +
                               ConvertDateTest                               +
                            'group by TheDate                              ');
          TheDateRange.SchemaToSQL(TU, OldSQLSchema, qFindExtremes, OrderBy)
        end   (* PressureRange *);

      procedure SimpleFromData;
        (* debulk btnFindExtremesClick *)

        var
          OldSQLSchema, TheTableName: string;

        function ObservedCheck: string;
          (* check iff needed *)
        
          begin (* ObservedCheck *)
            if TableInfo[TU].UsesObserved then
              Result := Format('ObservedUID = %d and ', [ObservedUID])
            else
              Result := nullstring
          end   (* ObservedCheck *);

        begin (* SimpleFromData *)
          qFindExtremes := qFindInHC3Data;
          TheTableName := TableInfo[TU].TheTableName;

          OldSQLSchema := TrimInternalWhiteSpace(
                            'select ' +
                            '  convert(WhenRecorded, SQL_DATE) as TheDate, ' +
                               MinMaxField + ' as TheDatum                 ' +
                            'from                                          ' +
                               TheTableName + '_old '                        +
                            'where                                         ' +
                               TheFieldName + ' is not null and            ' +
                               ObservedCheck()                               +
                               ConvertDateTest                               +
                            'group by TheDate                              ');
          TheDateRange.SchemaToSQL(TU, OldSQLSchema, qFindExtremes, OrderBy);
          // ShowMessageFmt('SQL: "%s"', [qFindExtremes.SQL.Text])
        end   (* SimpleFromData *);

      procedure SimpleFromSummary;
        (* debulk btnFindExtremesClick *)

        begin (* SimpleFromSummary *)
          qFindExtremes.SQL.Text := TrimInternalWhiteSpace(
            'select                                                  ' +
            '  TheDate,                                              ' +
               'cast(' + TheFieldName + ' as SQL_DOUBLE) as TheDatum ' +
            'from                                                    ' +
            '  Summary3                                              ' +
            'where                                                   ' +
               TheFieldName + ' is not null and                      ' +
               DateTest                                                +
            OrderBy);
        end   (* SimpleFromSummary *);

      procedure TemperatureRange;
        (* debulk btnFindExtremesClick *)

        begin (* TemperatureRange *)
          qFindExtremes.SQL.Text := TrimInternalWhiteSpace(
                                      'select                          ' +
                                      '  TheDate,                      ' +
                                      '  MaxTempF-MinTempF as TheDatum ' +
                                      'from                            ' +
                                      '  Summary3                      ' +
                                      'where                           ' +
                                      '  MaxTempF is not null and      ' +
                                      '  MinTempF is not null and      ' +
                                         DateTest                        +
                                      OrderBy);
        end   (* TemperatureRange *);

      begin (* tfmExtremes2.btnFindExtremesClick *)
        OldCursor := Screen.Cursor;
        Screen.Cursor := crHourglass;
        try     (* restore cursor *)
          with qExtremeSpecs do
            begin (* qExtremeSpecs *)
              ColHeader        :=            FieldByName('ColHeader'      ).AsString;
              Kind             :=            FieldByName('Kind'           ).AsString;
              MaxTrueMinFalse  :=            FieldByName('MaxTrueMinFalse').AsBoolean;
              MinMaxField      :=            FieldByName('XFieldName'     ).AsString;
              ObservedUID      :=            FieldByName('TheObservedUID' ).AsInteger;
              TheDisplayFormat :=            FieldByName('DisplayFormat'  ).AsString;
              TheFieldName     :=            FieldByName('TheFieldName'   ).AsString;
              TU               := tTableUsed(FieldByName('TheTableUID'    ).AsInteger)
            end   (* qExtremeSpecs *);
          dataFindExtremes.DataSet := nil;
          qFindExtremes.Close;
          qFindExtremes := qFindInHomeControl; // tentative, for Summary

          case Kind[1] of
            'd': ConsecutiveRainyDays(rwDry);
            'r': ConsecutiveRainyDays(rwTrace);
            'R': ConsecutiveRainyDays(rwWet);
            else
              begin (* usual case *)
                case Kind[1] of
                  'D': SimpleFromData;
                  'H': HumidityRange;
                  'P': PressureRange;
                  'p': Photoperiod;
                  'S': SimpleFromSummary;
                  'T': TemperatureRange
                  end (* case *);
                dataFindExtremes.DataSet := qFindExtremes;
                TheDateRange.SetQueryDates(qFindExtremes)
              end   (* usual case *)
            end (* case *);
          lbRank.Visible := false;
          dbadvgridFindExtremes.Visible := true;
          with dataFindExtremes.DataSet do
            tNumericField(Fields[1]).DisplayFormat := TheDisplayFormat;
          dbadvgridFindExtremes.Columns[DataColumn].Header := ColHeader;
          dataFindExtremes.DataSet.Open
        finally (* restore cursor *)
          Screen.Cursor := OldCursor
        end     (* restore cursor *)
      end   (* tfmExtremes2.btnFindExtremesClick *);

    procedure tfmExtremes2.dbadvgridFindExtremesDblClick(Sender: tObject);
      (* show rank *)

      var
        DateFound: boolean;
        TheDate, ThisDate: tDate;
        N, Rank: integer;

      begin (* tfmExtremes2.dbgridFindExtremesDblClick *)
        with dbadvgridFindExtremes.DataSource.DataSet do
          begin (* dbadvgridFindExtremes.DataSource.DataSet *)
            TheDate := FieldByName('TheDate').AsDateTime;
            dtpPicker.Date := TheDate;
            Rank := 0;
            DateFound := false;
            N := RecordCount;

            First;
            while not EOF do
              begin (* one record *)
                inc(Rank);
                ThisDate := FieldByName('TheDate').AsDateTime;
                if ThisDate = TheDate then
                  begin (* got it *)
                    DateFound := true;
                    break
                  end   (* got it *);
                Next
              end   (* one record *);
            Locate('TheDate', TheDate, []);

            with lbRank do
              begin (* lbRank *)
                Visible := true;
                if DateFound then
                  Caption := Format('%s was #%d of %d',
                                    [FormatDateTime('yyyy-mm-dd', TheDate),
                                     Rank, N])
                else
                  Caption := FormatDateTime('yyyy-mm-dd" not found"', TheDate)
              end   (* lbRank *)
          end   (* dbadvgridFindExtremes.DataSource.DataSet *)
      end   (* tfmExtremes2.dbadvgridFindExtremesDblClick *);

    procedure tfmExtremes2.rgDateRangeClick(Sender: tObject);
      (* change date range *)
    
      begin (* tfmExtremes2.rgDateRangeClick *)
        with TheDateRange do
          case rgDateRange.ItemIndex of
            0: SetToAllDates;
            1: SetRange(StartOfAYear(YearOf(Today)-1), Today);
            2: SetRange(StartOfAYear(YearOf(Today)  ), Today)
            end (* case *);
        btnFindExtremes.Enabled := true
      end   (* tfmExtremes2.rgDateRangeClick *);

    procedure tfmExtremes2.rgOrderExtremesClick(Sender: tObject);
      (* reorder *)

      begin (* tfmExtremes2.rgOrderExtremesClick *)
        btnFindExtremesClick(nil)
      end   (* tfmExtremes2.rgOrderExtremesClick *);


{****** end extremes tab ******************************************************}


end.
