#!/bin/sh
# \
  exec oagwish "$0" "$@"

set auto_path [linsert $auto_path 0  /usr/local/oag/apps/lib/$env(HOST_ARCH)]
set auto_path [linsert $auto_path 0  /usr/local/oag/lib_patch/$env(HOST_ARCH)]
APSDebugPath

#uses procedures in SRConfig.tcl

set CVSRevisionAuthor "\$Revision: 2.00 $ \$Author: borland $"

APSApplication . -name SRBPMOffset \
  -overview "SRBPMOffset provides convenience controls for storage ring BPM offset measurement."

set bpmOffsetConfigStatus ""
APSScrolledStatus .status -parent .userFrame -width 100 \
  -textVariable bpmOffsetConfigStatus 

set ALLBPMList {A:P0 A:P1 A:P2 A:P3 A:P4 A:P5 A:P6 B:P6 B:P5 B:P4 B:P3 B:P2 B:P1 B:P0}
set BPMList {A:P0 A:P1 A:P2 A:P3 A:P4 A:P5 A:P6 B:P6 B:P5 B:P4 B:P3 B:P2 B:P1 B:P0}

set BPMDir /home/helios/oagData/sr/BPMStatus
set BPMMissingList [exec sddsprocess $BPMDir/config.sdds -pipe=out \
                      -filter=col,NonexistentH,1,1,NonexistentV,1,1,| -noWarning \
                      | sdds2stream -pipe -column=DeviceName]

set P2Mode ""
set P4Mode ""

APSSRSectorButtons .bpmButtons -parent .userFrame -rootname bpmOffset \
  -orientation horizontal -sectorControl 1 \
  -label "BPM selections" -description "BPM selections" \
  -itemList $ALLBPMList -packOption "-side top" \
  -itemLabelList $ALLBPMList \
  -missingList $BPMMissingList \
  -colorDesc 0    
#unselect all buttons
APSSetSRSectorButtons -mode all-off -rootname bpmOffset -itemList $ALLBPMList -missingList $BPMMissingList

set quadLimitNominal(Q1) 12
set quadLimitNominal(Q3) 9
set quadLimitNominal(Q6) 8
set quadLimitNominal(Q7) 11
set quadLimitNominal(Q8) 14
proc SetQuadLimits {factor} {
    global quadLimit quadLimitNominal
    foreach quad [array names quadLimitNominal] {
        set quadLimit($quad) [expr $quadLimitNominal($quad)*$factor]
    }
}
SetQuadLimits 1

set bumpLimitNominal(Q1,x) 0.8
set bumpLimitNominal(Q3,x) 0.8
set bumpLimitNominal(Q6,x) 0.6
set bumpLimitNominal(Q7,x) 0.6
set bumpLimitNominal(Q8,x) 0.8
set bumpLimitNominal(Q1,y) 0.4
set bumpLimitNominal(Q3,y) 0.4
set bumpLimitNominal(Q6,y) 0.4
set bumpLimitNominal(Q7,y) 0.4
set bumpLimitNominal(Q8,y) 0.4
proc SetQuadBumpLimits {factor plane} {
    global bumpLimit bumpLimitNominal
    foreach quad [list Q1 Q3 Q6 Q7 Q8] {
        set bumpLimit($quad,$plane) [expr $bumpLimitNominal($quad,$plane)*$factor]
    }
}

proc IsSpecialP0 {args} {
    set bpm ""
    if {[APSStrictParseArguments {bpm}] || ![string length $bpm]} {
        return -code error "IsSpecialP0 problem: bad arguments"
    }
    set specialList [list S39B:P0x S39B:P0y]
    if [lsearch -exact $specialList $bpm]!=-1 {
        return 1
    } 
    return 0
}

proc MakeOptionWidget {widget args} {
    global quadLimit negativeOnly positiveOnly quadSteps bumpLimit bumpSteps num2Ave sextupoleFractionalChange sextupoleChangePause
    global bumpLimitSext bumpStepsSext sextupoleOrbitCorrectionInterval sextupoleOrbitCorrectionGain sextupoleConvergenceRequirement
    global sextupoleConvergenceCount sextupoleOrbitCorrectionPVType quadChangePause

    set parent ""
    APSParseArguments {parent}

    set w $parent$widget
    APSFrame $widget -parent $parent -label "Options"
    $w.frame configure -relief flat
    set tabList {Quadrupole-based Sextupole-based}
    set widgetList [APSTabFrame .options -parent $parent$widget.frame -label "" \
                      -labelList $tabList -width 1000 -height 155 ]

    set index 0
    set w [lindex $widgetList $index]

    APSFrame .quad -parent $w -packOption {-side left -anchor nw} -label "Quadrupole scan"
    #$w.quad.frame configure -relief flat
    APSFrameGrid .presets -parent $w.quad.frame -xList {label content} -relief flat -packOption "-side top"
    APSLabel .text -parent $w.quad.frame.presets.label -text "Multiplier for presets: "
    APSButton .b1 -parent $w.quad.frame.presets.content -text "1" -command "SetQuadLimits 1" -size small -packOption "-side left" 
    APSButton .b1o2 -parent $w.quad.frame.presets.content -text "2/3" -command "SetQuadLimits 0.66666" -size small -packOption "-side left" 
    APSButton .b1o3 -parent $w.quad.frame.presets.content -text "1/2" -command "SetQuadLimits 0.5" -size small -packOption "-side left" 
    APSButton .b1o4 -parent $w.quad.frame.presets.content -text "1/3" -command "SetQuadLimits 0.33333" -size small -packOption "-side left" 
    
    APSLabeledEntryFrame .qLimit -parent $w.quad.frame -label "Change (A) Q1,3,6,7,8:" \
      -variableList "quadLimit(Q1) quadLimit(Q3) quadLimit(Q6) quadLimit(Q7) quadLimit(Q8)" -width 3 -orientation horizontal \
      -contextHelp {Amplitude of range of quadrupole scan. The quad used is the one closest to the selected BPM whose offset is to be measured.}
#    APSCheckButtonFrame .negativeOnly -parent $w.quad.frame -label "Negative Only?" \
        -variableList {negativeOnly} -buttonList {"Yes"} -orientation horizontal \
      -contextHelp {Go only negative on the quadrupoles.}
    APSCheckButtonFrame .positiveOnly -parent $w.quad.frame -label "Positive Only?" \
        -variableList {positiveOnly} -buttonList {"Yes"} -orientation horizontal \
      -contextHelp {Go only positive on the quadrupoles.}
    APSLabeledEntry .qSteps -parent $w.quad.frame -label "Steps" \
        -textVariable quadSteps -width 5 \
        -contextHelp {Number of steps in quad scan.}

    APSFrame .corr -parent $w -packOption  {-side left -anchor nw} -label "Bump scan"
    #$w.corr.frame configure -relief flat
    APSFrameGrid .xpresets -parent $w.corr.frame -xList {label content} -relief flat -packOption "-side top"
    APSLabel .text -parent $w.corr.frame.xpresets.label -text "Multiplier for presets: "
    APSButton .b1 -parent $w.corr.frame.xpresets.content -text "1" -command "SetQuadBumpLimits 1 x" -size small -packOption "-side left" 
    APSButton .b1o2 -parent $w.corr.frame.xpresets.content -text "2/3" -command "SetQuadBumpLimits 0.666666  x" -size small -packOption "-side left" 
    APSButton .b1o3 -parent $w.corr.frame.xpresets.content -text "1/2" -command "SetQuadBumpLimits 0.5 x" -size small -packOption "-side left" 
    APSButton .b1o4 -parent $w.corr.frame.xpresets.content -text "1/3" -command "SetQuadBumpLimits 0.333333 x" -size small -packOption "-side left" 
    APSLabeledEntryFrame .cLimitx -parent $w.corr.frame -label "hx/mm Q1,3,6,7,8:" \
        -variableList "bumpLimit(Q1,x) bumpLimit(Q3,x) bumpLimit(Q6,x) bumpLimit(Q7,x) bumpLimit(Q8,x)"  -width 4 \
        -orientation horizontal -contextHelp {Amplitude of bump scan in millimeters.}
    APSFrameGrid .ypresets -parent $w.corr.frame -xList {label content} -relief flat -packOption "-side top"
    APSLabel .text -parent $w.corr.frame.ypresets.label -text "Multiplier for presets: "
    APSButton .b1 -parent $w.corr.frame.ypresets.content -text "1" -command "SetQuadBumpLimits 1 y" -size small -packOption "-side left" 
    APSButton .b1o2 -parent $w.corr.frame.ypresets.content -text "2/3" -command "SetQuadBumpLimits 0.666666 y" -size small -packOption "-side left" 
    APSButton .b1o3 -parent $w.corr.frame.ypresets.content -text "1/2" -command "SetQuadBumpLimits 0.5 y" -size small -packOption "-side left" 
    APSButton .b1o4 -parent $w.corr.frame.ypresets.content -text "1/3" -command "SetQuadBumpLimits 0.333333 y" -size small -packOption "-side left" 
    APSLabeledEntryFrame .cLimity -parent $w.corr.frame -label "hy/mm Q1,3,6,7,8:" \
        -variableList "bumpLimit(Q1,y) bumpLimit(Q3,y) bumpLimit(Q6,y) bumpLimit(Q7,y) bumpLimit(Q8,y)"  -width 4 \
        -orientation horizontal -contextHelp {Amplitude of bump scan in millimeters.}
    APSLabeledEntryFrame .cSteps -parent $w.corr.frame -label "Steps Q1,3,6,7,8:" \
      -variableList "bumpSteps(Q1) bumpSteps(Q3) bumpSteps(Q6) bumpSteps(Q7) bumpSteps(Q8)" -width 4 \
       -orientation horizontal -contextHelp {Number of steps in bump scan.}

    APSFrame .ave -parent $w -packOption  {-side left -anchor nw} -label Measurement
    #$w.ave.frame configure -relief flat
    APSLabeledEntry .csave -parent $w.ave.frame -label "\# to ave." \
      -textVariable num2Ave -width 5 \
      -contextHelp {Enter the number of BPM readings to average.}
    APSLabeledEntry .pause -parent $w.ave.frame -label "Post-change wait (s)" \
      -textVariable quadChangePause -width 5 \
      -contextHelp {Enter the duration of pause after changing quadrupole or correctors.}

    incr index
    set w [lindex $widgetList $index]

    APSFrame .sext -parent $w -packOption {-side left -anchor nw} -label "Sextupole Change"
    #$w.sext.frame configure -relief flat
    APSLabeledEntry .sextFracLim -parent $w.sext.frame -label "Fraction" \
        -textVariable sextupoleFractionalChange -width 5 \
        -contextHelp {Fractional change of sextupole when performing sextupole-based measurements}
    
    APSFrame .corr -parent $w -packOption  {-side left -anchor nw} -label "Bump scan"
    #$w.corr.frame configure -relief flat
    APSLabeledEntry .cLimit -parent $w.corr.frame -label "Limit (mm)" \
      -textVariable bumpLimitSext  -width 5 \
      -contextHelp {Amplitude of bump scan in millimeters.}
    APSLabeledEntry .cSteps -parent $w.corr.frame -label "Steps per plane" \
      -textVariable bumpStepsSext -width 5 \
      -contextHelp {Number of steps in each dimension of the bump scan.}

    APSFrame .orbcor -parent $w -packOption  {-side left -anchor nw} -label "Orbit Correction"
    #$w.orbcor.frame configure -relief flat
    APSLabeledEntry .gain -parent $w.orbcor.frame -label "Gain" \
        -textVariable sextupoleOrbitCorrectionGain -width 5 \
        -contextHelp {Enter the gain for the orbit correction processes used in sextupole-based measurements.}
    APSLabeledEntry .interval -parent $w.orbcor.frame -label "Interval (s)" \
        -textVariable sextupoleOrbitCorrectionInterval -width 5 \
        -contextHelp {Enter the update interval for the orbit correction processes used in sextupole-based measurements.}
    APSRadioButtonFrame .pvtype -parent $w.orbcor.frame -label "PV Type" \
        -variable sextupoleOrbitCorrectionPVType -orientation horizontal \
        -valueList "Sampled LowPass1s" -buttonList "Sampled LowPass1s" \
        -commandList "APSNoOp WarnLowPass1sOnSimulator" \
        -contextHelp {Select PV type for the orbit correction processes used in sextupole-based measurements.}

    APSFrame .orbconv -parent $w -packOption {-side left -anchor nw} -label "Orbit Convergence"
    APSLabeledEntry .conv -parent $w.orbconv.frame -label "Convergence (um)" \
        -textVariable sextupoleConvergenceRequirement -width 5 \
        -contextHelp {Enter the convergence criterion for the orbit correction processes.}
    APSLabeledEntry .count -parent $w.orbconv.frame -label "Count" \
        -textVariable sextupoleConvergenceCount -width 5 \
        -contextHelp {Enter the number of in-range samples for convergence of the orbit correction processes.}
    APSLabeledEntry .sextPause -parent $w.orbconv.frame -label "Time limit (s) " \
        -textVariable sextupoleChangePause -width 5 \
        -contextHelp {Maximum time to wait for orbit convergence.}

    APSFrame .ave -parent $w -packOption  {-side left -anchor nw} -label "Measurement"
    #$w.ave.frame configure -relief flat
    APSLabeledEntry .csave -parent $w.ave.frame -label "\# to ave." \
      -textVariable num2Ave -width 5 \
      -contextHelp {Enter the number of BPM readings to average.}

}

proc SetDateTimeToToday {args} {
    set rootname ""
    set hour 0
    APSStrictParseArguments {rootname hour}
    global todayMonth todayYear todayDay 
    global ${rootname}Month ${rootname}Year ${rootname}Day ${rootname}Hour
    
    APSDateBreakDown -dayVariable todayDay -yearVariable todayYear \
      -monthVariable todayMonth -twoDigitYear 0 -leadingZeros 0
    set ${rootname}Hour $hour
    set ${rootname}Month $todayMonth
    set ${rootname}Day $todayDay
    set ${rootname}Year $todayYear
}


proc MakeActionWidget {widget args} {
    global abortOffsetMeasurement buttonList

    set parent ""
    APSParseArguments {parent}

    set buttonList ""
    set w $parent$widget
    APSFrame $widget -parent $parent -label "Action Buttons"
    $w.frame configure -relief flat
    set tabList {Setup Measure Process Review Install}
    set widgetList [APSTabFrame .actions -parent $parent$widget.frame -label "" \
                      -labelList $tabList -width 1000 -height 250 ]
    
    #setup buttons
    set index 0
    set w [lindex $widgetList $index]
    MakeSetupWidget -parent $w

    global P2Mode P4Mode plane

    # measure buttons
    incr index
    set w [lindex $widgetList $index]
    APSRadioButtonFrame .plane -parent $w -label "Plane: " \
      -orientation horizontal \
      -variable plane \
      -buttonList {x y x/y} \
      -valueList {x y xy} \
      -contextHelp "Select the plane for BPM offsets to be measured. If selecting x/y plane then x and y offset will be measured alternatively."

    APSRadioButtonFrame .sextP2 -parent $w -label "Sextupole-based mode for P2's (Both planes recommended): " \
        -orientation horizontal -variable P2Mode \
        -buttonList {"Both planes" "x only" "y only" "Neither"} \
        -valueList {"xy" "x" "y" ""} -contextHelp \
        "Select the mode for P2 measurements. If \"Neither\" is selected, measurements use quadrupoles or TGDs. If \"Both planes\" is selected, measurements are performed only when Plane=x."
    
    APSRadioButtonFrame .sextP4 -parent $w -label "Sextupole-based mode for P4's (x only recommended): " \
        -orientation horizontal -variable P4Mode \
        -buttonList {"Both planes" "x only" "y only" "Neither"} \
        -valueList {"xy" "x" "y" ""} -contextHelp \
        "Select the mode for P4 measurements. If \"Neither\" is selected, measurements use quadrupoles or TGDs. If \"Both planes\" is selected, measurements are performed only when Plane=x."

    APSButton .measure -text "MEASURE" \
      -command "set abortOffsetMeasurement 0; DisableButtons; MeasureOffsets" \
      -parent $w \
      -contextHelp "Starts the offset measurement of selected bpms."
    lappend buttonList $w.measure.button
    APSButton .abort  -text " ABORT " -command "set abortOffsetMeasurement 1" \
      -parent $w \
      -contextHelp "Aborts at the next convenient time during measurement."

	 # processing buttons
    incr index
    set w [lindex $widgetList $index]
    APSRadioButtonFrame .plane -parent $w -label "Plane: " \
      -orientation horizontal \
      -variable plane \
      -buttonList {x y x/y} \
      -valueList {x y xy} \
      -contextHelp "Select the plane for BPM offsets process."
    APSButton .single -text "SINGLE..." \
      -command "ProcessSingle" \
      -parent $w  \
      -contextHelp "Processes the data for the single bpm selected in the above selection frames. A scroll window will appear to allow selection of past measurements."
    APSButton .last -text "LATEST"\
      -command "ProcessSingle -last 1" \
      -parent $w \
      -contextHelp "Processes the data for the single bpm selected in the above selection frames. The latest data file for that bpm will be processed."
    APSButton .list -text "LIST w/o plots"\
      -command "ProcessSequence" \
      -parent $w \
      -contextHelp "Processes the data for a list of bpms. The list is generated with the MAKE LIST command."
    APSButton .listP -parent $w -text "LIST with plots" \
	-command {
	    set bpmList ""
	    set bpmList [CreateList -rootname bpmOffset -plane $plane -suffixLists $BPMList]
	    if [catch {ProcessSequence_NormalHumanBeing -plane $plane -bpmList $bpmList} result] {
		APSSetVarAndUpdate bpmOffsetConfigStatus  "ProcessSequence_NormalHumanBeing: $result"
	    }
	} -contextHelp "Processes the data for a list of bpms with plots. Equivalent to pressing SINGLE many times. Would be good to limit list length to about 10 bpms, otherwise the number of windows would be too much."
    lappend buttonList $w.single.button $w.last.button $w.list.button $w.listP.button 

    #review frame
    incr index
    set w [lindex $widgetList $index]
    APSFrameGrid .group -parent $w -xList {one two} -relief flat
    APSRadioButtonFrame .plane -parent $w.group.one -label "Plane: " \
      -orientation horizontal \
      -variable plane \
      -buttonList {x y x/y} \
      -valueList {x y xy} \
      -packOption "-side top" \
      -contextHelp "Selected the plane for BPM offsets to be reviewed."
    
    APSFrame .date -parent $w.group.two -label "Date/Time Range for searching" -packOption "-side top"
    set w1 $w.group.two.date.frame
    APSDateTimeAdjEntry .startDate -parent $w1 \
      -yearVariable StartYear -monthVariable StartMonth -dayVariable StartDay \
      -label "Starting date/time (year, month, day): " -defaultHour 0
    APSDateTimeAdjEntry .endDate -parent $w1 \
      -yearVariable EndYear -monthVariable EndMonth -dayVariable EndDay \
      -label "Ending date/time (year, month, day):   " -defaultHour 24 
    SetDateTimeToToday -rootname Start -hour 0
    SetDateTimeToToday -rootname End  -hour 24

    APSFrameGrid .buttons -parent $w -yList {one two three} -relief flat -packOption "-side top"
    APSButton .review0 -text "Review w/ Plots " \
      -command "ReviewSingleOffset -makePlots 1 -last 0" \
      -parent $w.buttons.one \
      -contextHelp "Reviews the data taken by the MEASURE command, and the calculated offset.  Makes plots."
    APSButton .review1 -text "Review w/o Plots" \
      -command "ReviewSingleOffset -makePlots 0 -last 0" \
      -parent $w.buttons.one \
      -contextHelp "Reviews the data taken by the MEASURE command, and the calculated offset.  Doesn't make plots."

    APSButton .review2 -text "Long-term Review w/ Processing" \
      -command "LongTermReview -makePlots 1 -processing 1 -outputSorted 0" \
      -parent $w.buttons.two \
      -contextHelp "Reviews all the data taken for the selected bpms from past measurements. Recreates summary using all measurements first. May be time consuming if many BPMs are selected."
    APSButton .review3 -text "Long-term Review w/o Processing" \
      -command "LongTermReview -makePlots 1 -processing 0 -outputSorted 0" \
      -parent $w.buttons.two \
      -contextHelp "Reviews all the data taken for the selected bpm from past measurements. Does not recreate summary, does not guarantee that all the measurements are in the summary."
    APSButton  .selectnomeas -text "Find unmeasured" -parent $w.buttons.two -command "SelectNoMeasurementBPMs -rootname bpmOffset" \
      -contextHelp "Selects all those BPMs for which there is no measurement."

    #APSButton .searchreplace -text "Search\n(replace)" \
    #  -command "SearchBPMs -mode replace" \
    # -parent $w -contextHelp "Find the bpms measured in the selected date range and replace the selected bpms."
    #APSButton .searchor -text "Search\n(or)" \
    #  -command "SearchBPMs -mode and" \
    #  -parent $w -contextHelp "Find the bpms measured in the selected date range and add to the selected bpms."
    APSButton .review4 -text "Measurement dates" \
      -command "LongTermReview -makePlots 0 -processing 0 -outputSorted 1" \
      -parent $w.buttons.two \
      -contextHelp "Sorts and outputs latest measurement date for marked bpms. Does not reprocess summary files. Use to find bpms that have not been measured in a long time."
    APSButton .selector -text "ID Suspects (and)" -parent $w.buttons.three -command "SelectSuspectOffsets -rootname bpmOffset -mode and" \
      -contextHelp "Selects all those BPMs for which the most recent measurement is suspect."
    APSButton .selectrepl -text "ID Suspects (replace)" -parent $w.buttons.three -command "SelectSuspectOffsets -rootname bpmOffset -mode replace" -contextHelp \
      "Selects all those BPMs for which the most recent measurement is suspect."
    APSButton  .selectmarginalrepl -text "Select marginal (replace)" -parent $w.buttons.three \
      -command "SelectMarginalOffsets -rootname bpmOffset -mode replace" \
      -contextHelp "Selects all those BPMs for which the last measurement is marginal"
    APSButton  .selectmarginaland -text "Select marginal (and)" -parent $w.buttons.three \
      -command "SelectMarginalOffsets -rootname bpmOffset -mode and" \
      -contextHelp "Selects all those BPMs for which the last measurement is marginal"
    
    #install frame
    incr index
    set w [lindex $widgetList $index]
    APSRadioButtonFrame .plane -parent $w -label "Plane: " \
      -orientation horizontal \
      -variable plane \
      -buttonList {x y} \
      -valueList {x y} \
      -contextHelp "Selected the plane for BPM offsets to be installed."

    APSFrameGrid .fg -parent $w -label "" -yList {y1 y2}

    APSButton .single1 -text "SINGLE (w/o setpoint adj)..." -command "InstallSingleOffset -adjustSetpoint 0" -parent $w.fg.y1 \
      -contextHelp "Installs the offset data for a single bpm."
    APSButton .single2 -text "SINGLE (w/setpoint adj)..." -command "InstallSingleOffset -adjustSetpoint 1" -parent $w.fg.y1 \
      -contextHelp "Installs the offset data for a single bpm."
    APSButton .list -text "LIST..." -command "InstallList" -parent $w.fg.y1 \
      -contextHelp "Prepares lists of offsets of various qualities and allows their installation."

}

proc WarnLowPass1sOnSimulator {} {
    global env
    if [string match head*.cluster $env(HOSTNAME)] {
        APSSetVarAndUpdate bpmOffsetConfigStatus  "Using LowPass1s mode on the simulator is not recommended."
        APSSetVarAndUpdate bpmOffsetConfigStatus  "If you insist, increase the correction interval to 6s."
    }
}

proc ProcessSingle {args} {
    global BPMList plane bpmSequence FileSelection

    set last 0
    APSParseArguments {last}

    set bpmSequence [CreateList -rootname bpmOffset -plane $plane -suffixLists $BPMList]
    foreach bpm $bpmSequence {
        APSSetVarAndUpdate bpmOffsetConfigStatus  "BPM $bpm selected"

        set offsetFiles [lsort [concat [getOffsetFiles -bpm $bpm -suffix "-quadMeasSettings.sdds"] [getOffsetFiles -bpm $bpm -suffix "-sextMeasSettings.sdds"]]]
        if ![string length $offsetFiles] {
            APSSetVarAndUpdate bpmOffsetConfigStatus "ProcessOffsetCommand: No files found"
            return
        }
        if !$last {
            set FileSelection ""
            APSScrolledListWindow .process -name "Offset data selection" -label "select an offset file" \
                -itemList $offsetFiles -selectionVar FileSelection
            tkwait variable FileSelection
            if ![string length $FileSelection] {
                APSSetVarAndUpdate bpmOffsetConfigStatus  "ProcessOffsetCommand: No files chosen!"
                return
            }
            destroy .process
        } else {
            set FileSelection [lindex [lsort $offsetFiles] end]
        }
        APSSetVarAndUpdate bpmOffsetConfigStatus "File $FileSelection selected"

        if [string match *-quadMeasSettings.sdds $FileSelection] {
            regexp {(....-..-..)/([AB]P.[xy])/(S.+[AB]P.[xy])-(..)-quadMeasSettings\.sdds} $FileSelection {} date bpmDir bpm index
            regsub {P} $bpm {:P} bpm
            if ![string length $date] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "date part of filename not found."
                return
            }
            if ![string length $bpm] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "bpm part of filename not found."
                return
            }
            ProcessQuadrupoleMeasurement -date $date -bpm $bpm -index $index -review 0 -makePlots 1 -printout 1 \
                -statusCallback "APSSetVarAndUpdate bpmOffsetConfigStatus"
        } else {
            regexp {(....-..-..)/([AB]P.[xy])/(S.+[AB]P.[xy])-(..)-sextMeasSettings\.sdds} $FileSelection {} date bpmDir bpm index
            regsub {P} $bpm {:P} bpm
            if ![string length $date] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "date part of filename not found."
                return
            }
            if ![string length $bpm] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "bpm part of filename not found."
                return
            }
            ProcessSextupoleMeasurement -date $date -bpm $bpm -index $index -review 0 -makePlots 1 -printout 1 \
                -statusCallback "APSSetVarAndUpdate bpmOffsetConfigStatus"
        }
    }
}

proc InstallSingleOffset {args} {
    set adjustSetpoint 0
    if [APSStrictParseArguments {adjustSetpoint}] {
        return -code error "InstallSingleOffset: problem with arguments"
    }
    
    global BPMList plane bpmSequence FileSelection

    set bpmSequence [CreateList -rootname bpmOffset -plane $plane -suffixLists $BPMList]
    set bpm [lindex $bpmSequence 0]
    APSSetVarAndUpdate bpmOffsetConfigStatus  "BPM $bpm selected"

    set offsetFiles [lsort [concat [getOffsetFiles -bpm $bpm -suffix "-quadResults.sdds"] [getOffsetFiles -bpm $bpm -suffix "-sextResults.sdds"]]]
    if ![string length $offsetFiles] {
        APSSetVarAndUpdate bpmOffsetConfigStatus "InstallSingleOffset: No files found"
        return
    }
    set FileSelection ""
    APSScrolledListWindow .process -name "Offset data selection" -label "select an offset file" \
      -itemList $offsetFiles -selectionVar FileSelection
    tkwait variable FileSelection
    if ![string length $FileSelection] {
        APSSetVarAndUpdate bpmOffsetConfigStatus  "InstallSingleOffset: No files chosen!"
        return
    }
    APSSetVarAndUpdate bpmOffsetConfigStatus "File $FileSelection selected"
    destroy .process
    InstallOffset -fileName $FileSelection -statusCallback "APSSetVarAndUpdate bpmOffsetConfigStatus" \
        -adjustSetpoint $adjustSetpoint
}

proc ReviewSingleOffset {args} {
    global BPMList plane bpmSequence FileSelection

    set makePlots 1
    set last 0
    APSStrictParseArguments {makePlots last}

    set bpmSequence [CreateList -rootname bpmOffset -plane $plane -suffixLists $BPMList]
    foreach bpm $bpmSequence {
        APSSetVarAndUpdate bpmOffsetConfigStatus  "BPM $bpm selected"

	 # bpm argument for procedure getOffsetFiles should have the form S1A:P1x
	 # (i.e. same as widget flag)
    set offsetFiles [lsort [concat [getOffsetFiles -bpm $bpm -suffix "-quadResults.sdds"] [getOffsetFiles -bpm $bpm -suffix "-sextResults.sdds"]]]
    if ![string length $offsetFiles] {
        APSSetVarAndUpdate bpmOffsetConfigStatus "ReviewSingleOffset: No files found"
        return
    }
    if !$last {
        set FileSelection ""
        APSScrolledListWindow .review -name "Offset data selection" -label "Select an offset file" -itemList $offsetFiles \
          -selectionVar FileSelection
        tkwait variable FileSelection
        if ![string length $FileSelection] {
            APSSetVarAndUpdate bpmOffsetConfigStatus  "ReviewSingleOffset: No files chosen!"
            return
        }
        destroy .review
    } else {
        set FileSelection [lindex [lsort $offsetFiles] end]
    }
    APSSetVarAndUpdate bpmOffsetConfigStatus "File $FileSelection selected"

    if [string match *-quadResults.sdds $FileSelection] {
        regexp {(....-..-..)/([AB]P.[xy])/(S.+[AB]P.[xy])-(..)-quadResults\.sdds} $FileSelection {} date bpmDir bpm index
        regsub {P} $bpm {:P} bpm
        if ![string length $date] {
            APSSetVarAndUpdate bpmOffsetConfigStatus "date part of filename not found."
            return
        }
        if ![string length $bpm] {
            APSSetVarAndUpdate bpmOffsetConfigStatus "bpm part of filename not found."
            return
        }
        # bpm argument should have the form S1A:P1x
        ProcessQuadrupoleMeasurement -date $date -bpm $bpm -index $index \
            -review 1 -makePlots $makePlots -printout 1 \
            -statusCallback "APSSetVarAndUpdate bpmOffsetConfigStatus"
    } else {
        regexp {(....-..-..)/([AB]P.[xy])/(S.+[AB]P.[xy])-(..)-sextResults\.sdds} $FileSelection {} date bpmDir bpm index
        regsub {P} $bpm {:P} bpm
        if ![string length $date] {
            APSSetVarAndUpdate bpmOffsetConfigStatus "date part of filename not found."
            return
        }
        if ![string length $bpm] {
            APSSetVarAndUpdate bpmOffsetConfigStatus "bpm part of filename not found."
            return
        }
        ProcessSextupoleMeasurement -date $date -bpm $bpm -index $index -review 1 -makePlots $makePlots -printout 1 \
            -statusCallback "APSSetVarAndUpdate bpmOffsetConfigStatus"
    }
    }
}

proc LongTermReview {args} {
    global BPMSelection IDSelection FileSelection plane BPMList 
    APSParseArguments {processing makePlots outputSorted}
    set dataDir /home/helios/oagData/sr/bpmOffsets/results

    set bpmSequence [CreateList -rootname bpmOffset -plane $plane -suffixLists $BPMList]
    if ![llength $bpmSequence] {
	APSSetVarAndUpdate bpmOffsetConfigStatus "No files for chosen bpms found."
	return
    }
    set continue 1
    if {[llength $bpmSequence] > 10 && $processing} {
	set continue [APSMultipleChoice .manyBpms -question \
			  "You have chosen to review [llength $bpmSequence] bpms. Are you sure it's not too many?" \
			  -labelList [list "Yes, continue" "No, cancel"] \
			  -returnList [list 1 0]]
    } 

    if !$continue {
	APSSetVarAndUpdate bpmOffsetConfigStatus "Processing cancelled."
        return
    }

    set plotString ""
    set fileList ""

    foreach BPMSelection $bpmSequence {
        switch -glob $BPMSelection {
            *P2* -
            *P4* {
                set type [APSMultipleChoice .method -question "Which type of measurements are of interest for $BPMSelection?" \
                              -labelList {Quadrupole Sextupole} -returnList {quad sext}]
            }
            *P3* {
                set type sext
            }
            default {
                set type quad
            }
        }
        set sectorSelection [os editstring d%/A:P//%/B:P//e2b2d $BPMSelection ]
        # this variable is used to search in subdirectory that have names like AP2
        set BPMSel [os editstring zAzB $BPMSelection]
        set BPMSelectionNoColon [os editstring %/:// $BPMSelection]
        set BPMSelNoColon [os editstring %/:// $BPMSel]
        
        APSSetVarAndUpdate bpmOffsetConfigStatus  "BPM $BPMSelection selected (sector $sectorSelection)"
        
        set summaryFile $dataDir/$BPMSelection-$type.summary

        if {$processing || ![file exists $summaryFile]} {
            set resultsFiles [getOffsetFiles -bpm $BPMSelection -suffix "-${type}Results.sdds"]
            if ![llength $resultsFiles] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "LongTermReview: No results found"
                continue
            }
            APSSetVarAndUpdate bpmOffsetConfigStatus "[llength $resultsFiles] files found"
            
            catch {file delete -force $summaryFile}
            set editCommand %+${dataDir}/++%+${BPMSelNoColon}/++%+${BPMSelectionNoColon}-++%/-${type}Results.sdds//
            switch $type {
                quad {
                    if [catch {eval exec sddscombine $resultsFiles -pipe=out -collapse \
                                   | sddsconvert -pipe -retain=col,MeanOffset,SigmaOffset,Filename,BPMName,QuadName \
                                   | sddsprocess -pipe=in $summaryFile \
                                   -edit=col,Date,Filename,$editCommand} result ] {
                        APSSetVarAndUpdate bpmOffsetConfigStatus "LongTermReview: $result" 
                        return
                    }
                }
                sext {
                    if [catch {eval exec sddscombine $resultsFiles -pipe=out -collapse \
                                   | sddsconvert -pipe -retain=col,x0,y0,Filename,BPMName,SextupoleName \
                                   | sddsprocess -pipe=in $summaryFile -convert=col,?0,um,m,1e6 \
                                   -edit=col,Date,Filename,$editCommand} result ] {
                        APSSetVarAndUpdate bpmOffsetConfigStatus "LongTermReview: $result" 
                        return
                    }
                }
            }
            catch {exec chmod g+rw $summaryFile}
            APSSetVarAndUpdate bpmOffsetConfigStatus "Summary file $summaryFile created."
        }
        if [file exists $summaryFile] {
            lappend fileList $summaryFile
        }
        switch $type {
            quad {
                append plotString " -col=Date,MeanOffset,SigmaOffset -graph=error \"-topline=Summary of $type offsets for $BPMSelection,sca=1.5\" $summaryFile -end "
            }
            sext {
                append plotString " -col=Date,x0 -graph=symbol \"-topline=Summary of $type offsets for $BPMSelection,sca=1.5\" $summaryFile -end "
                append plotString " -col=Date,y0 -graph=symbol \"-topline=Summary of $type offsets for $BPMSelection,sca=1.5\" $summaryFile -end "
            }
        }
    }
    
    if $makePlots {
        if [string length $plotString] {
            eval exec sddsplot -pspace=0.15,0.95,0.30,0.90 -title= -ylabel=sca=1.5 \
                $plotString &
        }
    }
    
    if $outputSorted {
        #------ Make sorted-by-date summary
        set tmpRoot /tmp/[APSTmpString]
        exec sddsmakedataset $tmpRoot.bpms -col=BPMName,type=string -data=[join $bpmSequence ,]
        eval exec sddscombine $fileList -pipe=out \
            | sddsprocess -pipe -clip=0,1,inv \"-reedit=col,Date,10f100d\" \
            \"-edit=col,YearS,Date,4f10d\" \"-edit=col,MonthS,Date,5d2f10d\" \"-edit=col,DayS,Date,8d\" \
            -scan=col,Year,YearS,%lf -scan=col,Month,MonthS,%lf -scan=col,Day,DayS,%lf \
            \"-redef=col,Sort,Year 366 * Month 31 * + Day +\" \
            | sddscombine -pipe -merge \
            | sddsxref -pipe $tmpRoot.bpms -take=BPMName -nowarning \
            | sddssort -pipe -col=Sort \
            | sddsconvert -pipe=in $tmpRoot.sorted -del=col,YearS,Year,MonthS,Month,DayS,Day,Sort
        if [catch {APSExecLog .printout \
                       -lineLimit 2048 -width 120 \
                       -name "BPMs sorted by date" \
                       -unixCommand "sddsprintout $tmpRoot.sorted -col=*Name -col=Date -col=MeanOffset"} pid] {
            APSSetVarAndUpdate status "Error running locoFitting: $pid"
        }
        file delete $tmpRoot.bpms
    }
}

proc SelectSuspectOffsets {args} {
    global plane ALLBPMList
    set mode replace
    set rootname ""
    if {[APSStrictParseArguments {rootname mode}]} {
        return -code error "SelectSuspectOffsets: invalid arguments"
    }
    if [string compare xy $plane]==0 {
        APSSetVarAndUpdate bpmOffsetConfigStatus "Select either x or y plane first."
        return
    }
    APSSetVarAndUpdate bpmOffsetConfigStatus "Working"

    if [catch {eval exec sddscombine [glob /home/helios/oagData/sr/bpmOffsets/results/????-??-??/???${plane}/*-quadResults.sdds] -pipe=out -collapse \
                 | sddssort -pipe -column=BPMName -column=Filename \
                 | sddsbreak -pipe -change=BPMName \
                 | sddsprocess -pipe -clip=0,1,invert -filter=col,SuspectResult,1,1e100 -nowarning \
                 | sddscombine -pipe -merge \
                 | sdds2stream -pipe -column=BPMName} BPMList] {
        return -code error "SelectSuspectOffsets: $BPMList"
    }
    APSSetVarAndUpdate bpmOffsetConfigStatus "[llength $BPMList] suspect offsets found"
    if [llength $BPMList]==0 {
        return
    }

    global ${rootname}CBWidget
    for {set sector 1} {$sector<41} {incr sector} {
        set snn [format S%02d $sector]
        foreach item $ALLBPMList {
            set nameFlag ${rootname}${snn}$item
            global $nameFlag
        }
    }

    if [string compare $mode "replace"]==0 {
        for {set sector 1} {$sector<41} {incr sector} {
            set snn [format S%02d $sector]
            foreach bpmType $ALLBPMList {
                set nameFlag ${rootname}${snn}$bpmType
                if [set $nameFlag] {
                    [set ${rootname}CBWidget(${snn}$bpmType)] invoke
                }
            }
        }
        foreach bpmName $BPMList {
            regexp {(.*):(.)} $bpmName all prefix coord
            set nameFlag ${rootname}${prefix}
            [set ${rootname}CBWidget($prefix)] invoke
        }
    } elseif [string compare $mode "and"]==0 {
        for {set sector 1} {$sector<41} {incr sector} {
            set snn [format S%02d $sector]
            foreach bpmType $ALLBPMList {
                set bpmName ${snn}$bpmType
                set nameFlag ${rootname}$bpmName
                if [set $nameFlag] {
                    if [lsearch -exact $BPMList $bpmName:$plane]==-1 {
                        [set ${rootname}CBWidget($bpmName)] invoke
                    }
                }
            }
        }
        set total 0
        for {set sector 1} {$sector<41} {incr sector} {
            set snn [format S%02d $sector]
            foreach bpmType $ALLBPMList {
                set bpmName ${snn}$bpmType
                set nameFlag ${rootname}$bpmName
                if [set $nameFlag] {
                    incr total
                }
            }
        }
        APSSetVarAndUpdate bpmOffsetConfigStatus "$total BPMs selected"
    }
}

proc SelectNoMeasurementBPMs {args} {
    global plane ALLBPMList
    set rootname ""
    if {[APSStrictParseArguments {rootname}]} {
        return -code error "SelectNoMeasurementBPMs: invalid arguments"
    }
    if [string compare xy $plane]==0 {
        APSSetVarAndUpdate bpmOffsetConfigStatus "Select either x or y plane first."
        return
    }
    APSSetVarAndUpdate bpmOffsetConfigStatus "Working"

    if [catch {eval exec sddscombine [glob /home/helios/oagData/sr/bpmOffsets/results/????-??-??/???${plane}/*-quadResults.sdds] -pipe=out -collapse \
                 | sddssort -pipe -column=BPMName -column=Filename \
                 | sddsbreak -pipe -change=BPMName \
                 | sddsprocess -pipe -clip=0,1,invert -filter=col,DCCTOk,1,1 -nowarning \
                 | sddscombine -pipe -merge \
                 | sdds2stream -pipe -column=BPMName} BPMList] {
        return -code error "SelectNoMeasurementBPMs: $BPMList"
    }
    APSSetVarAndUpdate bpmOffsetConfigStatus "[llength $BPMList] good quadrupole-based offsets found"

    global ${rootname}CBWidget
    for {set sector 1} {$sector<41} {incr sector} {
        set snn [format S%02d $sector]
        foreach item $ALLBPMList {
            set nameFlag ${rootname}${snn}$item
            global $nameFlag
        }
    }

    for {set sector 1} {$sector<41} {incr sector} {
        set snn [format S%02d $sector]
        foreach bpmType $ALLBPMList {
            set nameFlag ${rootname}${snn}$bpmType
            if ![set $nameFlag] {
                [set ${rootname}CBWidget(${snn}$bpmType)] invoke
            }
        }
    }

    foreach bpmName $BPMList {
        regexp {(.*):(.)} $bpmName all prefix coord
        set nameFlag ${rootname}${prefix}
        [set ${rootname}CBWidget($prefix)] invoke
    }

    if [catch {eval exec sddscombine [glob /home/helios/oagData/sr/bpmOffsets/results/????-??-??/???${plane}/*-sextResults.sdds] -pipe=out -collapse \
                 | sddssort -pipe -column=BPMName -column=Filename \
                 | sddsbreak -pipe -change=BPMName \
                 | sddsprocess -pipe -clip=0,1,invert -nowarning \
                 | sddscombine -pipe -merge \
                 | sdds2stream -pipe -column=BPMName} BPMList] {
        return -code error "SelectNoMeasurementBPMs: $BPMList"
    }
    APSSetVarAndUpdate bpmOffsetConfigStatus "[llength $BPMList] sextupole-based offsets found"

    foreach bpmName $BPMList {
        set nameFlag ${rootname}${bpmName}
        if [set $nameFlag] {
            [set ${rootname}CBWidget($bpmName)] invoke
        }
    }

}

proc SelectMarginalOffsets {args} {
    global plane ALLBPMList
    set mode replace
    set rootname ""
    if {[APSStrictParseArguments {rootname mode}]} {
        return -code error "SelectSuspectOffsets: invalid arguments"
    }
    if [string compare xy $plane]==0 {
        APSSetVarAndUpdate bpmOffsetConfigStatus "Select either x or y plane first."
        return
    }
    APSSetVarAndUpdate bpmOffsetConfigStatus "Working"

    if [catch {eval exec sddscombine [glob /home/helios/oagData/sr/bpmOffsets/results/????-??-??/???${plane}/*-quadResults.sdds] -pipe=out -collapse \
                 | sddssort -pipe -column=BPMName -column=Filename \
                 | sddsbreak -pipe -change=BPMName \
                 | sddsprocess -pipe -clip=0,1,invert \
                 "{-define=column,BumpHalfRange,BumpMax BumpMin - 2 /,units=um}" \
                 "{-define=column,FractionalDistanceFromCenter,MeanOffset BumpCenter - abs BumpHalfRange /}" \
                 "{-test=column,FractionalDistanceFromCenter 0.5 >}" -nowarning \
                 | sddscombine -pipe -merge \
                 | sdds2stream -pipe -column=BPMName} BPMList] {
        return -code error "SelectMarginalOffsets: $BPMList"
    }
    APSSetVarAndUpdate bpmOffsetConfigStatus "[llength $BPMList] marginal offsets found"
    if [llength $BPMList]==0 {
        return
    }

    global ${rootname}CBWidget
    for {set sector 1} {$sector<41} {incr sector} {
        set snn [format S%02d $sector]
        foreach item $ALLBPMList {
            set nameFlag ${rootname}${snn}$item
            global $nameFlag
        }
    }

    if [string compare $mode "replace"]==0 {
        for {set sector 1} {$sector<41} {incr sector} {
            set snn [format S%02d $sector]
            foreach bpmType $ALLBPMList {
                set nameFlag ${rootname}${snn}$bpmType
                if [set $nameFlag] {
                    [set ${rootname}CBWidget(${snn}$bpmType)] invoke
                }
            }
        }
        foreach bpmName $BPMList {
            regexp {(.*):(.)} $bpmName all prefix coord
            set nameFlag ${rootname}${prefix}
            [set ${rootname}CBWidget($prefix)] invoke
        }
    } elseif [string compare $mode "and"]==0 {
        for {set sector 1} {$sector<41} {incr sector} {
            set snn [format S%02d $sector]
            foreach bpmType $ALLBPMList {
                set bpmName ${snn}$bpmType
                set nameFlag ${rootname}$bpmName
                if [set $nameFlag] {
                    if [lsearch -exact $BPMList $bpmName:$plane]==-1 {
                        [set ${rootname}CBWidget($bpmName)] invoke
                    }
                }
            }
        }
        set total 0
        for {set sector 1} {$sector<41} {incr sector} {
            set snn [format S%02d $sector]
            foreach bpmType $ALLBPMList {
                set bpmName ${snn}$bpmType
                set nameFlag ${rootname}$bpmName
                if [set $nameFlag] {
                    incr total
                }
            }
        }
        APSSetVarAndUpdate bpmOffsetConfigStatus "$total BPMs selected"
    }
}



proc CreateList {args} {
    set rootname ""
    set suffixLists ""
    set plane ""
    set pv 0
    APSParseArguments {rootname suffixLists plane pv}
    if { ![string length $rootname] || ![string length $suffixLists] || ![string length $plane] } {
        return -code error "CreateList: Bad arguments" 
    }
    set names ""
    for {set sector 1} {$sector < 41} {incr sector} {
        set Sector S[format %02d $sector]
        foreach suffix $suffixLists {
            set nameFlag ${rootname}${Sector}$suffix
            global $nameFlag
            if [set $nameFlag] {
                # if x y planes are requested then alternate planes.
                switch $plane {
                    x {
                        if $pv {
                            lappend names ${Sector}${suffix}:x:SetpointC
                        } else {
                            lappend names ${Sector}${suffix}x
                        }
                    }
                    y {
                        if $pv {
                            lappend names ${Sector}${suffix}:y:SetpointC
                        } else {
                            lappend names ${Sector}${suffix}y
                        }
                    }
                    xy {
                        if $pv {
                            lappend names ${Sector}${suffix}:x:SetpointC ${Sector}${suffix}:y:SetpointC
                        } else {
                            lappend names ${Sector}${suffix}x ${Sector}${suffix}y
                        }
                    }
                }
            }
        }
    }
    return $names
}

proc MeasureOffsets {} {
    global BPMList plane bpmSequence
    set bpmSequence ""
    set bpmSequence [CreateList -rootname bpmOffset -plane $plane -suffixLists $BPMList]
    APSSetVarAndUpdate bpmOffsetConfigStatus "$bpmSequence"
    
    # bpm averaging will be restored at the end of the sequence
    # or after the abort button is pressed.
#    SaveOrbitControllaw
#    turnOffOrbitCorrection
    ExecuteNext
}

proc RestoreControllaw {args} {
}

proc ProcessSequence {} {
    global BPMList plane bpmSequence
    set bpmSequence ""
    set bpmSequence [CreateList -rootname bpmOffset -plane $plane -suffixLists $BPMList]
    ProcessNext
}

proc ProcessSequence_NormalHumanBeing {args} {
    APSParseArguments {plane bpmList}
    APSSetVarAndUpdate bpmOffsetConfigStatus "BPM list: $bpmList"
    foreach bpm $bpmList {
	set offsetFiles [lsort [concat [getOffsetFiles -bpm $bpm -suffix "-quadResults.sdds"] [getOffsetFiles -bpm $bpm -suffix "-sextResults.sdds"]]]
	if ![string length $offsetFiles] {
	    APSSetVarAndUpdate bpmOffsetConfigStatus "No files found for $bpm"
	    continue
	}
	set offsetFile [lindex [lsort $offsetFiles] end]
	APSSetVarAndUpdate bpmOffsetConfigStatus "File $offsetFile selected"

        if [string match *-quadResults.sdds $offsetFile] {
            regexp {(....-..-..)/([AB]P.[xy])/(S.+....)-(..)-quadResults\.sdds} $offsetFile {} date bpmDir {} index
            if ![string length $date] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "date part of filename not found."
                return
            }
            if ![string length $bpm] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "bpm part of filename not found."
                return
            }
            if [catch {ProcessQuadrupoleMeasurement -date $date -bpm $bpm -index $index \
                           -review 0 -makePlots 1 -printout 1 \
                           -statusCallback "APSSetVarAndUpdate bpmOffsetConfigStatus"} result] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "ProcessQuadrupoleMeasurement: $result"
            }
        } else {
            regexp {(....-..-..)/([AB]P.[xy])/(S.+[AB]P.[xy])-(..)-sextResults\.sdds} $offsetFile {} date bpmDir bpm index
            regsub {P} $bpm {:P} bpm
            if ![string length $date] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "date part of filename not found."
                return
            }
            if ![string length $bpm] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "bpm part of filename not found."
                return
            }
            if [catch {ProcessSextupoleMeasurement -date $date -bpm $bpm -index $index \
                           -review 0 -makePlots 1 -printout 1 \
                           -statusCallback "APSSetVarAndUpdate bpmOffsetConfigStatus"} result] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "ProcessSextupoleMeasurement: $result"
            }
        }
    }
}

proc InstallList {} {
    global BPMList plane
    set fileList ""
    set bpmSequence [CreateList -rootname bpmOffset -plane $plane -suffixLists $BPMList]
    set Plane [string toupper $plane]
    APSSetVarAndUpdate bpmOffsetConfigStatus "Preparing lists of offsets in different categories for $bpmSequence"
    set quadFileList ""
    set sextFileList ""
    foreach bpm $bpmSequence {
        switch -glob $bpm {
            *P\[0156\]* {
                lappend quadFileList [lindex [lsort [getOffsetFiles -bpm $bpm -suffix "-quadResults.sdds"]] end]
            }
            *P3* {
                lappend sextFileList [lindex [lsort [getOffsetFiles -bpm $bpm -suffix "-sextResults.sdds"]] end]
            } default {
                # P2 and P4 can be measured using both methods
                if [llength [set candidateFileList [lsort [getOffsetFiles -bpm $bpm -suffix "-sextResults.sdds"]]]] {
                    # Check whether the measurement allows installation for this plane
                    if [catch {eval exec sddscombine $candidateFileList -pipe=out \
                                   | sddscollapse -pipe \
                                   | sddsprocess -pipe -nowarning -filter=column,Do${Plane}Plane,1,1 -clip=0,1,invert \
                                   | sdds2stream -pipe -column=Filename} sextFile] {
                        return -code error "$sextFiles"
                    }
                }
                set quadFile [lindex [lsort [getOffsetFiles -bpm $bpm -suffix "-quadResults.sdds"]] end]
                if {[string length $quadFile]} {
                    if {[string length $sextFile] && [file mtime $sextFile]>[file mtime $quadFile]} {
                        lappend sextFileList $sextFile
                    } else {
                        lappend quadFileList $quadFile
                    }
                } elseif [string length $sextFile] {
                    lappend sextFileList $sextFile
                }
            }
        }
    }

    set description(goodOffsets) "good $plane offsets"
    set description(marginalOffsets) "marginal $plane offsets"
    set description(badOffsets) "bad $plane offsets"
    set description(unknownQuality) "$plane offsets of unknown quality"

    if [llength $quadFileList] {
        set tmpRoot /tmp/[APSTmpString]
        APSSetVarAndUpdate bpmOffsetConfigStatus "[llength $quadFileList] quadrupole measurement files"
        set combFile $tmpRoot.offsets
        set goodOffsets $tmpRoot.goodOffsets
        set unknownQuality $tmpRoot.unknownQuality
        set marginalOffsets $tmpRoot.marginalQuality
        set badOffsets $tmpRoot.badOffsets
        APSAddToTmpFileList -ID InstallList -fileList "$combFile $goodOffsets $unknownQuality $marginalOffsets $badOffsets"
        if [catch {eval exec sddscombine $quadFileList -pipe=out -collapse \
                       | sddsprocess -pipe=in $combFile \
                       -edit=col,ControlName,BPMName,ei/:BpmZeroOffsetC/ \
                       -print=col,ControlType,pv \
                       -print=col,Lineage,- \
                       -define=col,Count,1,type=long \
                       -print=col,ValueString,%lf,MeanOffset \
                       -print=para,SnapType,Absolute \
                   } result ] {
            APSDeleteTmpFileList -ID InstallList
            return -code error "InstallList: $result"
        }
        
        if [catch {exec sddsprocess $combFile $unknownQuality -noWarning -filter=col,RangeFraction,0,0 
            exec sddsprocess $combFile $goodOffsets -noWarning -filter=col,RangeFraction,0,0,!,RangeFraction,-1.1,1.1,&
            exec sddsprocess $combFile $badOffsets -noWarning -filter=col,RangeFraction,-100,-1.5,RangeFraction,1.5,100,|
            exec sddsprocess $combFile $marginalOffsets -noWarning -filter=col,RangeFraction,-1.5,-1.1,RangeFraction,1.1,1.5,| \
                   } result ] {
            APSDeleteTmpFileList -ID InstallList
            return -code error "InstallList: $result"
        }
        foreach fileVar {goodOffsets marginalOffsets badOffsets unknownQuality } {
            set filename [set $fileVar]
            if ![string compare [exec sdds2stream -rows $filename] "0 rows"] {
                continue
            }
            APSAddToTmpFileList -ID InstallList -fileList "$filename.present $filename.print $filename.both"
            if [catch {exec sddscasr -save $filename $filename.present} result] {
                APSDeleteTmpFileList -ID InstallList
                return -code error "InstallList: $result"
            }
            if [catch {exec sddsxref $filename $filename.present -pipe=out -take=ValueString \
                           -rename=col,ValueString=ExistingValueString \
                           | sddsprocess -pipe -scan=col,ExistingValue,ExistingValueString,%lf,type=double,units=um \
                           | tee $filename.both \
                           | sddsprintout -pipe=in ${filename}.print \
                           "-title=Printout for $plane $description($fileVar) from quadrupole scans.\n" \
                           -col=ControlName,format=%25s -col=ExistingValue,format=%10.3f -col=MeanOffset,format=%10.3f \
                           -col=RangeFraction,format=%12.3f } result ] {
                APSDeleteTmpFileList -ID InstallList
                return -code error "InstallList: $result"
            }
            set w [APSUniqueName .$fileVar]
            APSFileDisplayWindow $w -fileName ${filename}.print -comment "Offset measurements" -deleteOnClose 1 \
                -width 80 -height 20 -printCommand "enscript -r" -contextHelp "Printout for $$description($fileVar)."
            APSDialogBoxAddButton .install -parent $w \
                -text "INSTALL" -command "exec sddscasr -restore $filename.both; APSSetVarAndUpdate bpmOffsetConfigStatus \"Installed $description($fileVar).\"" \
                -contextHelp "Installs the offsets in this window"
            APSDialogBoxAddButton .installAdjust -parent $w \
                -text "INSTALL/ADJUST SETPOINTS" -command "InstallAdjustFromFile -filename $filename.both -offsetName MeanOffset; APSSetVarAndUpdate bpmOffsetConfigStatus \"Installed $description($fileVar).\"" \
                -contextHelp "Installs the offsets in this window and adjusts the setpoint so that the orbit readback does not change. Useful in keeping the same orbit."
        }
    }

    if [llength $sextFileList] {
        set tmpRoot /tmp/[APSTmpString]
        APSSetVarAndUpdate bpmOffsetConfigStatus "[llength $sextFileList] sextupole measurement files"
        set combFile $tmpRoot.offsets
        set goodOffsets $tmpRoot.goodOffsets
        set unknownQuality $tmpRoot.unknownQuality
        set marginalOffsets $tmpRoot.marginalQuality
        set badOffsets $tmpRoot.badOffsets
        APSAddToTmpFileList -ID InstallList -fileList "$combFile $goodOffsets $unknownQuality $marginalOffsets $badOffsets"
        if [catch {eval exec sddscombine $sextFileList -pipe=out \
                       | sddsprocess -pipe -process=ZeroResidual,max,%sMax \
                       | sddscollapse -pipe \
                       | sddsprocess -pipe=in $combFile \
                       -convert=column,x0,um,m,1e6 -convert=column,y0,um,m,1e6 \
                       -edit=col,ControlName,BPMName,ei/:$plane:BpmZeroOffsetC/ \
                       -print=col,ControlType,pv \
                       -print=col,Lineage,- \
                       -define=col,Count,1,type=long \
                       -print=col,ValueString,%lf,${plane}0 \
                       -print=para,SnapType,Absolute \
                   } result ] {
            APSDeleteTmpFileList -ID InstallList
            return -code error "InstallList: $result"
        }
        if [catch {exec sddsprocess $combFile $unknownQuality -noWarning -filter=col,ZeroResidualMax,0,0
            exec sddsprocess $combFile $goodOffsets -noWarning -filter=col,ZeroResidualMax,0,0,! -filter=col,ZeroResidualMax,0,2e-6
            exec sddsprocess $combFile $marginalOffsets -noWarning -filter=col,ZeroResidualMax,0,0,! -filter=col,ZeroResidualMax,0,2e-6,! \
                       -filter=col,ZeroResidualMax,2e-6,5e-6
            exec sddsprocess $combFile $badOffsets -noWarning -filter=col,ZeroResidualMax,0,0,! -filter=col,ZeroResidualMax,0,1e-5,! \
                       -filter=col,ZeroResidualMax,2e-6,5e-6,!
                   } result ] {
            APSDeleteTmpFileList -ID InstallList
            return -code error "InstallList: $result"
        }
        foreach fileVar {goodOffsets  marginalOffsets badOffsets unknownQuality } {
            set filename [set $fileVar]
            if ![string compare [exec sdds2stream -rows $filename] "0 rows"] {
                continue
            }
            APSAddToTmpFileList -ID InstallList -fileList "$filename.present $filename.print $filename.both"
            if [catch {exec sddscasr -save $filename $filename.present} result] {
                APSDeleteTmpFileList -ID InstallList
                return -code error "InstallList: $result"
            }
            if [catch {exec sddsxref $filename $filename.present -pipe=out -take=ValueString -rename=col,ValueString=ExistingValueString \
                           | sddsprocess -pipe -scan=col,ExistingValue,ExistingValueString,%lf,type=double,units=um \
                           | tee $filename.both \
                           | sddsprintout -pipe=in ${filename}.print \
                           "-title=Printout for $description($fileVar) from sextupole scans.\n" \
                           -col=ControlName,format=%25s -col=ExistingValue,format=%10.3f -col=${plane}0,format=%13.6e \
                           -col=ZeroResidualMax,format=%13.6e } result ] {
                APSDeleteTmpFileList -ID InstallList
                return -code error "InstallList: $result"
            }
            set w [APSUniqueName .$fileVar]
            APSFileDisplayWindow $w -fileName ${filename}.print \
                -comment "Offset measurements" -deleteOnClose 1 \
                -width 80 -height 20 -printCommand "enscript -r" \
                -contextHelp "Printout for $$description($fileVar)."
            APSDialogBoxAddButton .install -parent $w \
                -text "INSTALL" -command "exec sddscasr -restore $filename; APSSetVarAndUpdate bpmOffsetConfigStatus \"Installed $description($fileVar).\"" \
                -contextHelp "Installs the offsets in this window"
            APSDialogBoxAddButton .installAdjust -parent $w \
                -text "INSTALL/ADJUST SETPOINTS" -command "InstallAdjustFromFile -filename $filename.both -offsetName ${plane}0; APSSetVarAndUpdate bpmOffsetConfigStatus \"Installed $description($fileVar).\"" \
                -contextHelp "Installs the offsets in this window and adjusts the setpoint so that the orbit readback does not change. Useful in keeping the same orbit."
        }
    }
}

proc InstallAdjustFromFile {args} {
    set filename ""
    set offsetName ""
    if {[APSStrictParseArguments {filename offsetName}] || ![string length $filename] || \
            ![string length $offsetName]} {
        return -code error "InstallAdjustFromFile: bad arguments"
    }

    # Restore the new offsets
    if [catch {exec sddscasr -restore $filename} result] {
        return -code error "InstallAdjustFromFile: $result"
    }
    
    # Acquire the existing setpoints, compute new setpoints, then restore
    # NewSetpoint = OldSetpoint - (NewOffset - OldOffset)
    # NewOffset is in the column named by offsetName (e.g., MeanOffset)
    # OldOffset is in the column ExistingValue
    if [catch {exec sddsprocess $filename -pipe=out -reedit=col,ControlName,%/:BpmZeroOffsetC/:SetpointC/ \
                   | sddscasr -pipe -save \
                   | sddsprocess -pipe -scan=col,OldSetpoint,ValueString,%le \
                   "-define=col,NewSetpoint,OldSetpoint $offsetName ExistingValue - -" \
                   -reprint=column,ValueString,%le,NewSetpoint \
                   | sddscasr -pipe=in -restore} result] {
        return -code error "InstallAdjustFromFile: $result"
    }
}

proc ExecuteNext {} {
    global bpmSequence quadLimit negativeOnly positiveOnly bumpLimit quadSteps bumpSteps num2Ave sextupoleFractionalChange
    global bumpLimitSext bumpStepsSext sextupoleOrbitCorrectionInterval sextupoleOrbitCorrectionGain sextupoleConvergenceRequirement
    global sextupoleConvergenceCount sextupoleConvergenceCount sextupoleOrbitCorrectionPVType
    global abortOffsetMeasurement tcl_platform P2Mode P4Mode sextupoleChangePause quadChangePause
    if ![llength $bpmSequence] {
        APSSetVarAndUpdate bpmOffsetConfigStatus "BPM sequence terminated."
        catch {exec play -q -v .7 /usr/local/oag/sounds/game_over.au}
        exec echo "\007"
        after 200
        exec echo "\007"
        RestoreControllaw
        RestoreButtons
        return
    }
    foreach bpm $bpmSequence {
        if {[catch {exec cavget -list=S-DCCT:CurrentM} current] || [expr abs($current) < 0.5]} {
            APSSetVarAndUpdate bpmOffsetConfigStatus "Insufficient stored beam (<0.5 mA)"
            RestoreControllaw
            RestoreButtons
            return
        }
        if {[string match *\[AB\]:P0* $bpm] && ![IsSpecialP0 -bpm $bpm]} {
            APSSetVarAndUpdate bpmOffsetConfigStatus "Skipping $bpm measurement---measure the corresponding P1 instead to get this BPM automatically."
        } else {
            set useSext [UseSextupoleBasedMethod -bpm $bpm -P2Mode $P2Mode -P4Mode $P4Mode]
            #APSSetVarAndUpdate bpmOffsetConfigStatus "useSext = $useSext"
            switch $useSext {
                0 {
                    if [catch {PerformQuadrupoleOffsetMeasurement -bpm $bpm -pause $quadChangePause \
                                   -quadLimitList [array get quadLimit] -quadSteps $quadSteps -negativeOnly $negativeOnly -positiveOnly $positiveOnly \
                                   -bumpLimitList [array get bumpLimit] \
                                   -bumpStepsList [array get bumpSteps] -num2Ave $num2Ave \
                                   -statusCallback "APSSetVarAndUpdate bpmOffsetConfigStatus"} result] {
                        APSSetVarAndUpdate bpmOffsetConfigStatus "$result"
                    }
                    APSSetVarAndUpdate bpmOffsetConfigStatus "Returned from PerformQuadrupoleOffsetMeasurement"
                }
                -1 {
                }
                default {
                    APSSetVarAndUpdate bpmOffsetConfigStatus "Calling PerformSextupoleOffsetMeasurement"
                    if [catch {PerformSextupoleOffsetMeasurement -bpm $bpm -steps 2 -fractionalChange $sextupoleFractionalChange \
                                   -changePause $sextupoleChangePause -orbitCorrectionInterval $sextupoleOrbitCorrectionInterval \
                                   -orbitCorrectionGain $sextupoleOrbitCorrectionGain \
                                   -orbitConvergence $sextupoleConvergenceRequirement \
                                   -orbitConvergedCount $sextupoleConvergenceCount \
                                   -orbitPVType $sextupoleOrbitCorrectionPVType \
                                   -bumpAmplitude $bumpLimitSext -bumpSteps $bumpStepsSext -num2Ave $num2Ave \
                                   -statusCallback "APSSetVarAndUpdate bpmOffsetConfigStatus" \
                                   -useSext $useSext} result] {
                        APSSetVarAndUpdate bpmOffsetConfigStatus "$result"
                    }
                    APSSetVarAndUpdate bpmOffsetConfigStatus "Returned from PerformSextupoleOffsetMeasurement"
                }
            }
        }
        if $abortOffsetMeasurement {
            APSSetVarAndUpdate bpmOffsetConfigStatus "BPM sequence terminated by abort button press."
            RestoreControllaw
            RestoreButtons         
            return                  
        }
        # set bpmSequence [lrange $bpmSequence 1 end]
    }
    return
}

proc RestoreButtons {} {
    global buttonList
    foreach button $buttonList {
		  #  APSEnableButton $button
    }
    return
}

proc DisableButtons {} {
    global buttonList
    foreach button $buttonList {
		  #  APSDisableButton $button
    }
    return
}

proc ProcessNext {} {
	 # process the most recent measurement file for each bpm
    global bpmSequence 
    global abortOffsetMeasurement
    if ![llength $bpmSequence] {
        APSSetVarAndUpdate bpmOffsetConfigStatus "All BPMS done."
        return
    }
    if $abortOffsetMeasurement {
        APSSetVarAndUpdate bpmOffsetConfigStatus "BPM sequence terminated by abort button press."
        return
    }
    set bpm [lindex $bpmSequence 0]
	 # chop off one element of the sequence list
    set bpmSequence [lrange $bpmSequence 1 end]
    set offsetFiles [lsort [concat [getOffsetFiles -bpm $bpm -suffix "-quadResults.sdds"] [getOffsetFiles -bpm $bpm -suffix "-sextResults.sdds"]]]
    if ![string length $offsetFiles] {
        APSSetVarAndUpdate bpmOffsetConfigStatus "No files found for $bpm"
        ProcessNext
    }
    set offsetFile [lindex [lsort $offsetFiles] end]
    APSSetVarAndUpdate bpmOffsetConfigStatus "File $offsetFile selected"

    if [string match *-quadResults.sdds $offsetFile] {
        regexp {(....-..-..)/([AB]P.[xy])/(S.+[AB]P.[xy])-(..)-quadResults\.sdds} $offsetFile {} date bpmDir bpm index
        regsub {P} $bpm {:P} bpm
        if ![string length $date] {
            APSSetVarAndUpdate bpmOffsetConfigStatus "date part of filename not found."
            return
        }
        if ![string length $bpm] {
            APSSetVarAndUpdate bpmOffsetConfigStatus "bpm part of filename not found."
            return
        }
        ProcessQuadrupoleMeasurement -date $date -bpm $bpm -index $index \
            -review 0 -makePlots 0 -printout 0 \
            -statusCallback "APSSetVarAndUpdate bpmOffsetConfigStatus" \
            -ultimateCallBack ProcessNext
    } else {
        regexp {(....-..-..)/([AB]P.[xy])/(S.+[AB]P.[xy])-(..)-sextResults\.sdds} $offsetFile {} date bpmDir bpm index
        regsub {P} $bpm {:P} bpm
        if ![string length $date] {
            APSSetVarAndUpdate bpmOffsetConfigStatus "date part of filename not found."
            return
        }
        if ![string length $bpm] {
            APSSetVarAndUpdate bpmOffsetConfigStatus "bpm part of filename not found."
            return
        }
        ProcessSextupoleMeasurement -date $date -bpm $bpm -index $index \
            -review 0 -makePlots 0 -printout 0 \
            -statusCallback "APSSetVarAndUpdate bpmOffsetConfigStatus" \
            -ultimateCallBack ProcessNext
    }
}

proc MakeSetupWidget {args} {
    set parent ""
    APSParseArguments {parent}
    
    set tabWidgetList [APSTabFrame .config -parent $parent -label "" \
                         -labelList {horizontal vertical} -width 880 -height 70]
    set w1 [lindex $tabWidgetList 0]
    set w2 [lindex $tabWidgetList 1]
    MakeConfigWidget -plane h -parent $w1
    MakeConfigWidget -plane v -parent $w2
    APSButton .gen -parent $w1 -text "GENERATE" \
      -command "GenerateControllawFiles -plane h -regenerateFiles 1"\
      -contextHelp "Forces a regeneration of tests, bpm-in-use, and other files for response matrix selected. Necessary when bpm or corrector limits are changed.\n\nThis button does not start controllaw."

    APSButton .start -parent $w1 -text "RUN" \
      -command "StartControllaw -plane h" \
      -contextHelp "Press to start orbit correction using the above correction configuration in h plane."
    APSButton .info -parent $w1 -text "INFO" \
      -command "exec medm -x -attach -macro RCPV=S:RC:OrbitControlLawXC /C2/iocapps/adlsys/sr/psApp/APSRunControlSingle.adl  &" \
      -contextHelp "Press to orbit orbit correction in h plane."
    APSButton .abort -parent $w1 -text "ABORT" \
      -command "AbortControllaw -plane h" \
      -contextHelp "Press to orbit orbit correction in h plane."
    APSButton .gen -parent $w2 -text "GENERATE" \
      -command "GenerateControllawFiles -plane v -regenerateFiles 1"\
      -contextHelp "Forces a regeneration of tests, bpm-in-use, and other files for response matrix selected. Necessary when bpm or corrector limits are changed.\n\nThis button does not start controllaw."

    APSButton .start -parent $w2 -text "RUN" \
      -command "StartControllaw -plane v" \
      -contextHelp "Press to start orbit correction using the above correction configuration in v plane."
    APSButton .info -parent $w2 -text "INFO" \
      -command "exec medm -x -attach -macro RCPV=S:RC:OrbitControlLawYC /C2/iocapps/adlsys/sr/psApp/APSRunControlSingle.adl  &" \
      -contextHelp "Press to orbit orbit correction in h plane."
    APSButton .abort -parent $w2 -text "ABORT" \
      -command "AbortControllaw -plane v" \
      -contextHelp "Press to orbit orbit correction in v plane"
    APSFrameGrid .fg -parent $parent -xList {x1 x2 x3}
    set w $parent.fg.x1
    APSFrame .orbit -parent $w -label "Orbit Setpoints"
    set w $w.orbit.frame
    APSButton .save -parent $w -text "SAVE" -command "OrbitSetpoints -action save" \
      -contextHelp "Save current orbit setpoints"
    APSButton .zero -parent $w -text "ZERO" -command "OrbitSetpoints -action zero" \
      -contextHelp "Zero the orbit setpoints"
    APSButton .restore -parent $w -text "RESTORE" -command "OrbitSetpoints -action restore" \
      -contextHelp "Restore the previously saved orbit setpoints"
    
    set w $parent.fg.x2
    APSFrame .corr -parent $w -label "Corrector Setpoints"
    set w $w.corr.frame
    APSButton .save -parent $w -text "SAVE" -command "CorrectorSetpoints -action save" \
      -contextHelp "Save corrector setpoints."
    APSButton .restore -parent $w -text "RESTORE" -command "CorrectorSetpoints -action restore" \
      -contextHelp "restore corrector setpoints"
    APSButton .ramp -parent $w -text "RAMP" -command "CorrectorSetpoints -action ramp" \
      -contextHelp "Ramp corrector setpoints."

    set w $parent.fg.x3
    APSFrame .adt -parent $w -label "BPM Error ADT"
    set w $w.adt.frame
    APSButton .bpm -parent $w -text "Launch ADT" -command "LaunchADT" \
      -contextHelp \
      "Launches ADT (Array Display Tool) for BPMs, showing only those BPMs that are selected in the configuration file"
}

proc MakeConfigWidget {args} {
    set plane h
    set parent ""
    
    APSParseArguments {plane parent}
    
    set rootname ${plane}bpmOffset
    global ${rootname}MainDir ${rootname}Lattice 
    set ${rootname}MainDir /home/helios/oagData/sr/orbitControllaw/lattices
    set ${rootname}Lattice default
    global ${rootname}Plane ${rootname}Readdir ${rootname}ReadDescription
    global ${rootname}FilterEnd1 
    set ${rootname}Plane $plane
    set ${rootname}Readdir ${plane}.bpmOffsetMeasurement
    set ${rootname}ReadDescription ""
    set ${rootname}FilterEnd1 .*
    global ${rootname}Interval ${rootname}Gain ${rootname}PVType ${rootname}Enable
    set ${rootname}Enable 0
    set ${rootname}Interval 2.0
    set ${rootname}Gain 0.5
    set ${rootname}PVType LowPass1s
    
    APSFileSelectWidget .file -parent $parent -variable ${rootname}Readdir -label "Config: " \
      -pathVariableList [list ${rootname}MainDir ${rootname}Lattice] \
      -mode directory -filterVariableList \
      [list ${rootname}Plane ${rootname}FilterEnd1] -width 80 \
      -incrementButtons 1 -contextHelp \
      "Enter the name of the configuration file to read."
	 
    APSLabeledEntry .descrip -parent $parent -label "Description:" -width 78 -packOption "-side top -anchor w" \
      -textVariable ${rootname}ReadDescription -contextHelp \
      "Shows the description of the last-read configuration file."
    
    APSButton .readDesc -parent $parent.descrip -text "Read" \
      -command "ReadDescription  -plane $plane -sourceType bpmOffset" \
      -contextHelp "Reads the description of the configuration."

    APSFrameGrid .fg3 -parent $parent -label "" -xList "g0 g1 g2 g3"
    APSRadioButtonFrame .enable -parent $parent.fg3.g0 -label "Enable: " \
      -orientation horizontal -buttonList "Yes No" -valueList "1 0" -variable ${rootname}Enable \
      -contextHelp "Select whether to use orbit correction automatically."
    
    APSLabeledEntry .interval -parent $parent.fg3.g1 -label "Interval (s): " -width 10 \
      -textVariable ${rootname}Interval -contextHelp \
      "Update interval for orbit correction."
    
    APSLabeledEntry .gain -parent $parent.fg3.g2 -label "Gain: " -width 10 \
      -textVariable ${rootname}Gain -contextHelp \
      "Gain for orbit correction."
    
    APSRadioButtonFrame .type -parent $parent.fg3.g3 -label "PV Type: " \
        -orientation horizontal -buttonList "Sampled LowPass1s" -valueList "Sampled LowPass1s" \
        -commandList "APSNoOp WarnLowPass1sOnSimulator" \
        -variable ${rootname}PVType -contextHelp "Type of PVs to use for orbit correction."
}

proc ReadDescription {args} {
    set sourceType ""
    set plane ""
    APSParseArguments {sourceType plane}

    set rootname $plane$sourceType
    global ${rootname}Readdir ${rootname}ReadDescription
    set config [subst \$${rootname}Readdir]

    APSSetVarAndUpdate bpmOffsetConfigStatus  "Reading description of $config..."
    set tmpfile /tmp/[APSTmpString]
    
    # read the configuration
    set baseDir /home/helios/oagData/sr/orbitControllaw
    switch $plane {
        h {set coord x}
        v {set coord y}
    }
    set configDir $baseDir/lattices/default/$config
    if ![file exists $configDir/config] {
        APSSetVarAndUpdate bpmOffsetConfigStatus  "Can't find config file for $configName."
        return
    }
    set ${rootname}ReadDescription [lindex [exec sdds2stream $configDir/config -para=Description] 0]
    APSSetVarAndUpdate bpmOffsetConfigStatus  "Done."
    return
}

proc StartControllaw {args} {
    global hbpmOffsetReaddir vbpmOffsetReaddir num2Ave runControlPV
    global hbpmOffsetGain hbpmOffsetInterval vbpmOffsetGain vbpmOffsetInterval
    global hbpmOffsetPVType vbpmOffsetPVType 
    global hbpmOffsetEnable vbpmOffsetEnable 
    set plane ""
    set matrixDir(h) /home/helios/oagData/sr/orbitControllaw/lattices/default/$hbpmOffsetReaddir
    set matrixDir(v) /home/helios/oagData/sr/orbitControllaw/lattices/default/$vbpmOffsetReaddir

    APSParseArguments {plane}
    if ![string length $plane] {
        return -code error "Plane for running orbit correction is not given"
    }
    switch $plane {
        h {
            set runControlPV S:RC:OrbitControlLawXC
            set widget .xcontrollaw
            set config $hbpmOffsetReaddir
            set interval $hbpmOffsetInterval
            set gain $hbpmOffsetGain
            set pvType $hbpmOffsetPVType
            if !$hbpmOffsetEnable {
                return
            }
        }
        v {
            set runControlPV S:RC:OrbitControlLawYC
            set widget .ycontrollaw
            set config $vbpmOffsetReaddir
            set interval $vbpmOffsetInterval
            set gain $vbpmOffsetGain
            set pvType $vbpmOffsetPVType
            if !$vbpmOffsetEnable {
                return
            }
        }
        default {
            return -code error "plane should be h or v!"
        }
    }
    
    set dataDir /home/helios/oagData/sr/orbitControllaw/lattices/default
    if [catch {APSSRGenerateControllawFiles -dataDir $dataDir -config $config -regenerateFiles 1 -timeFilter $pvType} result] {
        return -code error "$result"
    }

    # check presence of beam
    if [catch {exec cavget -list=S-DCCT:CurrentM} current] {
        return -code error "StartControllaw: $current"
    }
    if [expr abs($current) < 0.1 ] {
        return -code error "StartControllaw: not enough current in ring."
    }
    
    set steps 10000
    if [catch {APSSRPrepareControllawOptions -config $config -steps $steps -interval $interval -gain $gain \
             -runControlPV $runControlPV -RCTimeout 10 -runControlDesc "$plane offset meas claw"} options] {
	return -code error "Error prepare controllaw option: $options"
    }
    if [exec cavget -list=$runControlPV.RUN -pend=30] {
        if [catch {AbortControllaw -plane $plane } result] {
            return -code error "$result"
        }
    }
    
    if {0} {
    if [catch {exec logMessage -sourceId=offsetMeasurement \
                 -tag=Instance $runControlPV -tag=Action Start \
             } result ] {
        APSAlertBox .alert -errorMessage "error with logMessage: $result"
        return -code error "Error!"
    }
    }
    
    global controllawDone
    set controllawDone 0
    set abortComm "AbortControllaw -plane $plane"
    APSExecLog $widget -width 95 \
	-name "SR ${plane}-orbit correction for $plane offset measurement" \
	-unixCommand "sddscontrollaw $options" \
	-abortCallback $abortComm \
	-cancelCallback $abortComm
    
}

proc AbortControllaw {args} {
    set plane ""
    APSParseArguments {plane}
    APSSetVarAndUpdate bpmOffsetConfigStatus "aborting $plane controllaw..."
    switch $plane {
        h -
	x {
            set runControlPV S:RC:OrbitControlLawXC
        }
        v -
	y {
            set runControlPV S:RC:OrbitControlLawYC
        }
        default {
            return -code error "plane should be h or v!"
        }
    }
    
    if [catch {exec cavget -list=${runControlPV}.RUN } running] {
	return -code error "Error readding $plane runControlPV: $result"
    }
    if !$running {
	APSSetVarAndUpdate bpmOffsetConfigStatus "$plane controllaw is not running."
	return
    }
    APSSetVarAndUpdate bpmOffsetConfigStatus "Aborting $plane controllaw ($runControlPV)..."
    if [catch {exec cavput -list=${runControlPV}.ABRT=1 } result] {
	return -code error "Error abort $plane controllaw: $result"
    }
    exec cawait -waitFor=${runControlPV}.RUN,equalTo=0
    exec cavput -list=${runControlPV}.CLR=1
    APSSetVarAndUpdate bpmOffsetConfigStatus "$plane controllaw aborted."
    if {0} {
    if [catch {exec logMessage -sourceId=offsetMeasurement \
                 -tag=Instance $runControlPV -tag=Action Abort \
             } result ] {
        APSAlertBox .alert -errorMessage "error with logMessage: $result"
        return -code error "Error!"
    }
    }

}

proc OrbitSetpoints {args} {
    global BPMList orbitSnapshot
    set action ""
    APSParseArguments {action}

    set refFile /home/helios/oagData/SCR/snapshots/SR/SR-BPMOffsetReference.gz
    if ![file exists $refFile] {
        APSSetVarAndUpdate bpmOffsetConfigStatus "Storage ring BPM offset reference file not found!"
        return
    }
    if ![string length $action] {
        APSSetVarAndUpdate bpmOffsetConfigStatus "Action is not provided"
        return
    }
    switch $action {
        save {    
            APSSetVarAndUpdate bpmOffsetConfigStatus "Taking snapshot of setpoint ..."
            set orbitSnapshot /tmp/[APSTmpString].orbitSnap
            APSAddToTmpFileList -ID bpmOffset -fileList "$orbitSnapshot"
            if [catch {exec sddsprocess $refFile -pipe=out -match=col,ControlName=*SetpointC,ControlName=*RfSetpointC,!,& \
                           | sddscasr -pipe -save -pendIOTime=5 \
                           | sddsprocess -pipe=in -match=col,ControlName=,! $orbitSnapshot} result] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "$result"
                set orbitSnapshot ""
                return
            }
            APSSetVarAndUpdate bpmOffsetConfigStatus "Setpoint snapshot taken."
        }
        restore {
            if {$orbitSnapshot == ""} {
                APSSetVarAndUpdate bpmOffsetConfigStatus "Orbit snapshot has not been taken, can not restore setpoints."
                return
            }
            APSSetVarAndUpdate bpmOffsetConfigStatus "Restoring saved setpoints..."
            if [catch {exec sddscasr -restore $orbitSnapshot } result] {
                return -code error "OrbitSetpoints: $result"
            }
            APSSetVarAndUpdate bpmOffsetConfigStatus "Restore complete."
        }
        zero {
            APSSetVarAndUpdate bpmOffsetConfigStatus "Zeroing setpoints..."
            if ![string length $orbitSnapshot] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "Save the setpoints first before setting it to zero."
                return
            }
            set bpmList [CreateList -rootname bpmOffset -plane xy -suffixLists $BPMList -pv 1]
            if ![llength $bpmList] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "No BPMs selected!"
                return
            }
            set tmpRoot /tmp/[APSTmpString]
            APSAddToTmpFileList -ID OrbitSetpoints -fileList "$tmpRoot.selected $tmpRoot.otherSetpoint"
            APSAddToTmpFileList -ID OrbitSetpoints -fileList "$tmpRoot.otherError"
            APSSetVarAndUpdate bpmOffsetConfigStatus "Transfering errors of other BPMs..."
            # Create a file that contains the list of selected BPMs
            if [catch {exec sddsmakedataset $tmpRoot.selected -col=ControlName,type=string -data=[join $bpmList ,]} result] {
                return -code error "OrbitSetpoints: $result"
            }
            # Create a file that contains the setpoints for the non-selected BPMs
            APSSetVarAndUpdate bpmOffsetConfigStatus "Made list of selected BPMs"
            if [catch {exec sddsselect $orbitSnapshot $tmpRoot.selected -match=ControlName -invert -pipe=out \
                           | sddsconvert -pipe -delete=col,DeviceName \
                           | sddsprocess -pipe -match=col,ControlName=S???:P?:* -edit=col,DeviceName,ControlName,%/:SetpointC// \
                           | sddscasr -pipe -save \
                           | sddsprocess -pipe=in -scan=col,Setpoint,ValueString,%le $tmpRoot.otherSetpoint} result] {
                return -code error "OrbitSetpoints: $result"
            }
            APSSetVarAndUpdate bpmOffsetConfigStatus "Acquired setpoints for non-selected BPMs"
            # Create a file that contains the errors for the non-selected BPMs
            # Use sddssnapshot instead of sddscasr so we can average the LowPass1sErrorM values
            if [catch {exec sddsprocess $tmpRoot.otherSetpoint -pipe=out \
                           -reprint=col,ControlName,%s:LowPass1sErrorM,DeviceName \
                           | sddssnapshot -pipe=in $tmpRoot.otherError -name=Error -average=15,0.1} result] {
                return -code error "OrbitSetpoints: $result"
            }
            APSSetVarAndUpdate bpmOffsetConfigStatus "Created snapshot of errors in non-selected BPMs"
            # Compute Setpoint+Error for other BPMs, send it back to the setpoints
            if [catch {exec sddsxref $tmpRoot.otherSetpoint $tmpRoot.otherError -match=DeviceName -pipe=out -take=Error \
                           | sddsprocess -pipe "-define=col,NewSetpoint,Setpoint Error +" \
                           -reprint=col,ValueString,%le,NewSetpoint \
                           -reprint=col,ControlName,%s:SetpointC,DeviceName \
                           | sddscasr -pipe=in -restore} result] {
                return -code error "OrbitSetpoints: $result"
            }
            APSSetVarAndUpdate bpmOffsetConfigStatus "Sent Setpoint+Error back to non-selected BPMs"
            # May as well delete these now (tmp file list contains other files we need to keep)
            file delete $tmpRoot.selected $tmpRoot.otherSetpoint $tmpRoot.otherError 
            # Set setpoints for selected BPMs to zero
            APSSetVarAndUpdate bpmOffsetConfigStatus "Setting selected BPMs setpoints to zero..."
            if [catch {exec cavput -list=[join $bpmList =0,]=0 -pend=30} result ] {
                return -code error "OrbitSetpoints: $result"
            }
            # Why?
            after 1000
        }
    }
    APSSetVarAndUpdate bpmOffsetConfigStatus "Done."
}

proc CorrectorSetpoints {args} {
    global corrSnapshot
    set snapDir /home/helios/oagData/SCR/snapshots/SRCorSetpts
    
    set action ""
    APSParseArguments {action}
    if ![string length $action] {
        APSSetVarAndUpdate bpmOffsetConfigStatus "Action is not provided!"
        return
    }
    switch $action {
        save {    
            APSSetVarAndUpdate bpmOffsetConfigStatus "Taking corrector setpoint snapshot..."
            if [catch {APSSaveMachine -machine SRCorSetpts \
                         -description "Routine save: BPM offset measurement."} \
                  corrSnapshot] {
                return -code error "CorrectorSetpoints: $snapshot"
            }
            APSSetVarAndUpdate bpmOffsetConfigStatus "Snapshot taken."
        }
        restore {
            if ![string length $corrSnapshot] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "No corrector snapshot file found."
                APSInfoWindow .info -name "Restore Warning"  -infoMessage "No corrector snapshot file found. The corrector setpoints were probably never saved during this instance of SRBPMOffset.\n\nIf you're in trouble look for a possible good file in SaveCompareRestore SRCorSetpts system, restore it using SaveCompareRestore, then press the SAVE button in SRBPMOffset."
                return  -code ok
            }
            APSSetVarAndUpdate bpmOffsetConfigStatus "Restoring original corrector setpoints..."
            if [catch {exec sddscasr -restore $snapDir/$corrSnapshot } result] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "CorrectorSetpoints: $result"
                return
            }
            APSSetVarAndUpdate bpmOffsetConfigStatus "Restore complete."
        }
        ramp {
            if ![string length $corrSnapshot] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "No corrector snapshot file found."
                APSInfoWindow .info -name "Restore Warning"  -infoMessage "No corrector snapshot file found. The corrector setpoints were probably never saved during this instance of SRBPMOffset.\n\nIf you're in trouble look for a possible good file in SaveCompareRestore SRCorSetpts system, restore it using SaveCompareRestore, then press the SAVE button in SRBPMOffset."
                return 
            }
            APSSetVarAndUpdate bpmOffsetConfigStatus "Ramping to original corrector setpoints..."
            set tempFile /tmp/[APSTmpString]
            
            if [catch {APSRampToSnapshot -initDialog 1 -fileName $snapDir/$corrSnapshot} result] {
                APSSetVarAndUpdate bpmOffsetConfigStatus "$result"
                return
            }
            APSSetVarAndUpdate bpmOffsetConfigStatus "Ramping complete."
        }
        default {
            APSSetVarAndUpdate bpmOffsetConfigStatus "Unknow action given"
            return
        }
    }
}

proc GenerateControllawFiles {args} {
    set plane ""
    set regenerateFiles 1
    global hbpmOffsetReaddir vbpmOffsetReaddir
    APSParseArguments {plane regenerateFiles}
    
    set dataDir /home/helios/oagData/sr/orbitControllaw/lattices/default
    set directory [set ${plane}bpmOffsetReaddir]
    APSSetVarAndUpdate bpmOffsetConfigStatus "Generating orbit correction files ..."
    if [catch {APSSRGenerateControllawFiles -dataDir $dataDir -config $directory -rangeErrorLimit 300 \
                  -regenerateFiles $regenerateFiles -timeFilter LowPass1s} result] {
        APSSetVarAndUpdate bpmOffsetConfigStatus "$result"
        return
    }
    APSSetVarAndUpdate bpmOffsetConfigStatus "done"
}

proc LaunchADT {args} {
    global hbpmOffsetReaddir vbpmOffsetReaddir dataDir
    global hbpmOffsetPVType vbpmOffsetPVType
    
    set oldDir [pwd]
    cd /home/helios/oagData/ADTFiles/srBpm
    exec adt -f sr.bpm.sampled.error.pv &
    cd $oldDir
}

proc SearchBPMs {args} {
    set mode ""
    APSParseArguments {mode}
    global plane
    if [catch {FindDateDirList} validDirList] {
        APSSetVarAndUpdate bpmOffsetConfigStatus  "$validDirList"
        return
    }
    set oldDir [pwd]
    set mainDir /home/helios/oagData/sr/bpmOffsets/quadMethod
    cd $mainDir
    set allList [glob -nocomplain *]
    set dirList ""
    foreach dir $allList {
        if {[lsearch -exact $validDirList $dir]>=0} {
            lappend dirList $dir
        }
    }
    if ![llength $dirList] {
        cd $oldDir
        APSSetVarAndUpdate bpmOffsetConfigStatus  "no data found in the date range"
        return
    }
    set bpmvarList ""
    set rootname bpmOffset
    if {$mode=="replace"} {
        ResetAllBPMs
    }
    foreach dir $dirList {
        set newList [glob -nocomplain $dir/*${plane}]
        foreach name $newList {
            regexp {\/(.+)} $name a b
            set suffix [string range $b 0 0]:[string range $b 1 2]
            set fileList [lsort [concat [glob -nocomplain $name/*-quadResults.sdds] [glob -nocomplain $name/*-sextResults.sdds]]]
            foreach file $fileList {
                regexp {\/(.+)} $file a b
                if [regexp {\/(.+)} $b c d] {
                    set sector [scan $d S%2d]
                    if {$sector=="{}"} {
                    } else {
                        set nameflag ${rootname}S${sector}$suffix
                        if {[lsearch -exact $bpmvarList $nameflag]<0} {
                            lappend bpmvarList ${rootname}S${sector}$suffix
                        }
                    }
                }
                
            }
        }
    }
    foreach bpmvar $bpmvarList {
        global $bpmvar
        set $bpmvar 1
    }
    cd $oldDir
}

proc FindDateDirList {args} {
    global StartYear StartMonth StartDay StartHour EndYear EndMonth EndDay EndHour
    
    set dateDirList ""
    set startmonth [format %d $StartMonth]
    set startday [format %d $StartDay]
    set endmonth [format %d $EndMonth]
    set endday [format %d $EndDay]
    if {$StartYear>$EndYear} {
        return -code error "Wrong date range entered-- the endyear was smaller than the startyear"
    }
    for {set year $StartYear} {$year<=$EndYear} {incr year} {
        
        if {$StartYear==$EndYear} {
            set sameyear 1
        } else {
            set sameyear 0
        }
        if $sameyear {
            if {$endmonth<$startmonth} {
                return -code error "Wrong date range entered-- the endmonth was smaller than the startmonth (within the same year)"
            }
            if {$startmonth==$endmonth} {
                set samemonth 1
            } else {
                set samemonth 0
            }
            if $samemonth {
                if {$endday<$startday} {
                    return -code error "Wrong date range entered-- the endday was smaller than the startday (within the same year and month)"
                }
                set month [format %.2d $startmonth]
                for {set day $startday} {$day<=$endday} {incr day} {
                    lappend dateDirList $year-$month-[format %.2d $day]
                }
            } else {
                for {set month $startmonth} {$month<=$endmonth} {incr month} {
                    if {$month==$startmonth} {
                        for {set day 1} {$day<=31} {incr day} {
                            if {$day>=$startday} {
                                lappend dateDirList $year-[format %.2d $month]-[format %.2d $day]
                            }
                        }
                    } elseif {$month<$endmonth} {
                        for {set day 1} {$day<=31} {incr day} {
                            lappend dateDirList $year-[format %.2d $month]-[format %.2d $day]
                        }
                    } elseif {$month==$endmonth} {
                        for {set day 1} {$day<=31} {incr day} {
                            if {$day<=$endday} {
                                lappend dateDirList $year-[format %.2d $month]-[format %.2d $day]
                            }
                        }
                    }
                }
            }
        } else {
            #startyear< #endyear
            if {$year==$StartYear} {
                for {set month 1} {$month<=12} {incr month} {
                    if {$month==$startmonth} {
                        for {set day 1} {$day<=31} {incr day} {
                            if {$day>=$startday} {
                                lappend dateDirList $year-[format %.2d $month]-[format %.2d $day]
                            }
                        }
                    } elseif {$month>$startmonth} {
                        for  {set day 1} {$day<=31} {incr day} {
                            lappend dateDirList $year-[format %.2d $month]-[format %.2d $day]
                        }
                    }
                }
            } elseif {$year<$EndYear} {
                for {set month 1} {$month<=12} {incr month} {
                    for  {set day 1} {$day<=31} {incr day} {
                        lappend dateDirList $year-[format %.2d $month]-[format %.2d $day]
                    }
                }
            } elseif {$year==$EndYear} {
                for {set month 1} {$month<=12} {incr month} {
                    if {$month==$endmonth} {
                        for {set day 1} {$day<=31} {incr day} {
                            if {$day<=$endday} {
                                lappend dateDirList $year-[format %.2d $month]-[format %.2d $day]
                            }
                        }
                    } elseif {$month<$endmonth} {
                        for  {set day 1} {$day<=31} {incr day} {
                            lappend dateDirList $year-[format %.2d $month]-[format %.2d $day]
                        }
                    }
                }
            }
        }
    }
    return $dateDirList
}

proc ResetAllBPMs {args} {
    set rootname bpmOffset
    set bpmList {A:P0 A:P1 A:P2 A:P3 A:P4 B:P5 B:P4 B:P3 B:P2 B:P1 B:P0 C:P0}
    for {set sector 1} {$sector <=40} {incr sector} {
        foreach bpm $bpmList {
            set nameflag ${rootname}S${sector}$bpm
            global $nameflag
            set $nameflag 0
        }
    }
}

proc SetStatus {text} {
    global bpmOffsetConfigStatus
    set status $text
    update
}

set orbitSnapshot ""
set corrSnapshot ""
# Only one can be 1. Not enforced by code. (for now)
set negativeOnly 0
set positiveOnly 1
set quadSteps 3
set sextupoleFractionalChange 0.5
set sextupoleChangePause 120
set sextupoleOrbitCorrectionGain 0.5
set sextupoleOrbitCorrectionInterval 2.0
set sextupoleConvergenceRequirement 2.0
set sextupoleConvergenceCount 4
set sextupoleOrbitCorrectionPVType LowPass1s
if [string match head*cluster $env(HOSTNAME)] {
    set quadChangePause 5.0
} else {
    set quadChangePause 2.0
}    
set bumpSteps(Q1) 3
set bumpSteps(Q3) 3
set bumpSteps(Q6) 3
set bumpSteps(Q7) 3
set bumpSteps(Q8) 3
set bumpLimitSext 0.3
set bumpStepsSext 3
set num2Ave 20
#set configDir /home/helios/oagData/sr/bpmOffsets/Method/configurations
set plane x
set abortOffsetMeasurement 0

MakeOptionWidget .options -parent .userFrame
MakeActionWidget .ops -parent .userFrame

# intruction to start with zero setpoint bpm values so that the 
# range of the bump centers the possible offset value.
update
if [string compare $env(USER) borland] {
APSInfoWindow .info -name "Operational Recommendations" -infoMessage "Before measuring any offsets, it is highly recommended to run orbit correction with all bpms setpoint set to zero and with the best known offsets. That will ensure that the range of the corrector bump with include the value of the offset.\n\nWe also suggest to measure and install all of the horizontal offsets first as the measurement uncertainty of the vertical offsets is affected by the presence of a large x-orbit in the scanned quadrupole. See AOP-TN-2011-004." -modal 1
}

SetQuadBumpLimits [expr 1/3.] x
SetQuadBumpLimits [expr 1/3.] y
SetStatus "Set bump heights to 1/3 nominal"
set bpmOffsetConfigStatus Ready.

# Local Variables:
# mode: tcl
# End:
