
proc APSBPLDglobals {args} {
    global aboveLimit tolerance timeLimit statusCallback
}
    
proc APSBPLDScan {args} {
    global aboveLimit
    global sequenceList direction statusCallback xSetpoint ySetpoint PVTripName firstPass
    global verificationType firstChannelBPM secondChannelBPM

    set statusCallback "puts"
    set sector ""
    APSStrictParseArguments {sector IsDigital statusCallback}

    if ![string length $sector] {
        eval $statusCallback {"No sector specified"}
        return
    }
    set sector1 [expr $sector + 1]
    # determine trip limits and setpoints for steering.
    # This will change for DBPLDs since there are different
    # limits on each side of the straight section.
    if {!$IsDigital} {
        # 4 values returned
        set command "cavget -pend=30 -list=S -list=${sector} -list=BPLD: \
                      -list=x,y -list=Up,Low -list=TripLimitAO"
    } else {
        set command "cavget -pend=30 -list=$firstChannelBPM($sector),$secondChannelBPM($sector) -list=:dbpld: \
                      -list=x,y -list=hi,lo -list=AO.VAL"
    }
    set tripLimits [eval exec $command]
    if {[lsearch -exact $tripLimits "?"] > -1} {
        eval $statusCallback \
          {"Problem in getting trip limits in APSBPLDSetBpmSetpoints."}
        return
    }
    if {!$IsDigital} {
        set xSetpoint(center) 0
        set ySetpoint(center) 0
        set xSetpoint(xLo) [expr -$aboveLimit + [lindex $tripLimits 1]]
        set xSetpoint(xHi) [expr $aboveLimit + [lindex $tripLimits 0]]
        set xSetpoint(yLo) 0
        set xSetpoint(yHi) 0
        set ySetpoint(xLo) 0
        set ySetpoint(xHi) 0
        set ySetpoint(yLo) [expr -$aboveLimit + [lindex $tripLimits 3]]
        set ySetpoint(yHi) [expr $aboveLimit + [lindex $tripLimits 2]]
    } else {
        set xSetpoint(center.us) 0
        set ySetpoint(center.us) 0
        set xSetpoint(center.ds) 0
        set ySetpoint(center.ds) 0
        set xSetpoint(xLo.us) [expr -$aboveLimit + [lindex $tripLimits 1]]
        set xSetpoint(xHi.us) [expr $aboveLimit + [lindex $tripLimits 0]]
        set xSetpoint(xLo.ds) [expr -$aboveLimit + [lindex $tripLimits 5]]
        set xSetpoint(xHi.ds) [expr $aboveLimit + [lindex $tripLimits 4]]
        set xSetpoint(yLo.us) 0
        set xSetpoint(yHi.us) 0
        set xSetpoint(yLo.ds) 0
        set xSetpoint(yHi.ds) 0
        set ySetpoint(xLo.us) 0
        set ySetpoint(xHi.us) 0
        set ySetpoint(xLo.ds) 0
        set ySetpoint(xHi.ds) 0
        set ySetpoint(yLo.us) [expr -$aboveLimit + [lindex $tripLimits 3]]
        set ySetpoint(yHi.us) [expr $aboveLimit + [lindex $tripLimits 2]]
        set ySetpoint(yLo.ds) [expr -$aboveLimit + [lindex $tripLimits 7]]
        set ySetpoint(yHi.ds) [expr $aboveLimit + [lindex $tripLimits 6]]
    }
    set PVTripName(xLo) Xin
    set PVTripName(xHi) Xout
    set PVTripName(yLo) Ybot
    set PVTripName(yHi) Ytop

# start controllaw for global correction of P2 bpms using H2 and V2.
# by changing the bpm septoints, local bumps will be produced.
    if [catch {StartControllaw -statusCallback $statusCallback} result] {
        return -code error "APSBPLDScan: $result"
    } 

    # scan the four directions (in the order of a circle)
    switch $verificationType {
        pendingTrips {
            global XIN XOUT YBOTTOM YTOP
            set XIN 1 
            set XOUT 1
            set YBOTTOM 1
            set YTOP 1
            APSDialogBox .direction -name "Choose Direction Dialog Box" -okCommand "destroy .direction" \
              -cancelCommand "destroy .direction"
            APSCheckButtonFrame .check -parent .direction.userFrame -label "Check Directions: " \
              -buttonList {"x in" "x out" "y bottom" "y top"} -variableList {XIN XOUT YBOTTOM YTOP} \
              -orientation horizontal
            tkwait window .direction
            set seqlist ""
            if $XIN {lappend seqlist xLo}
            if $YBOTTOM {lappend seqlist yLo}
            if $XOUT {lappend seqlist xHi}
            if $YTOP {lappend seqlist yHi}
            set sequenceList [concat center $seqlist center]
            #set sequenceList {center xLo yLo xHi yHi center}
        }
        heartbeatStop {
            set sequenceList {center xLo center}
        }
        default {
            set sequenceList {center xLo yLo xHi yHi center}
        }
    }
    set firstPass 1
    # disable SCAN buttons
    DisableScanButtons
    # bring up medm windows
    if {!$IsDigital} {
        if [catch {exec medm -x -attach -macro "UNIT=S${sector}BPLD" \
                     ./srbpm/diagApp/BPLDa.adl &
            exec medm -x -attach -macro "UNIT=S${sector}BPLD,P1=$firstChannelBPM($sector),P2=$secondChannelBPM($sector)" \
                     ./srbpm/diagApp/BPLDaRaw.adl & \
                 } result ] {
            return -code error  $result
        }
    } else {
        regexp {S.*:(P.)} $firstChannelBPM($sector) {} button
        if [catch {exec medm -x -attach -macro "bpm=${sector}B,label=upstream,id=${sector},but=$button" ./srbpm/diagApp/dbpldDetail.adl & \
          } result ] {
            return -code error  $result
        }
        set sector1 [expr $sector + 1]
        regexp {S.*:(P.)} $secondChannelBPM($sector) {} button
        if [catch {exec medm -x -attach -macro "bpm=${sector1}A,label=downstream,id=${sector},but=$button" ./srbpm/diagApp/dbpldDetail.adl & \
          } result ] {
            return -code error  $result
        }
    }
    ExecuteNext -sector $sector -IsDigital $IsDigital
    return
}

proc ExecuteNext {args} {
    global sequenceList direction statusCallback xSetpoint ySetpoint PVTripName firstPass
    global abortBPLDTests firstChannelBPM secondChannelBPM
    global verificationType localMPSResults mainMPSResults

    set sector ""
    set IsDigital 0
    APSStrictParseArguments {sector IsDigital}
    if ![string length $sector] {
        eval $statusCallback {"No sector specified"}
        return
    }
    set directions {xLo xHi yLo yHi}
    foreach item  {xLo xHi yLo yHi} {
        global ${item}1Results ${item}2Results
    }
    if ![llength $sequenceList] {
        eval $statusCallback {"Trip limit test sequence terminated."}
        if {[string match $verificationType "heartbeatStop"]} {
            #check the local MPS module and Main MPS module
            set moduleNumber [expr ($sector + 1 ) / 2 * 2 - 1 ]
            if [catch {exec cavget -pend=30 -list=S${moduleNumber}:MPS:outputStatBI,S:MPS:readyCC \
                     } mpsTripStatus] {
                return -code error "APSBPLDScan: $mpsTripStatus"
            }
            if ![string compare \
                   [string tolower \
                      [lindex $mpsTripStatus 0]] "fault"] {
                set localMPSResults($sector) Trip
            } else {
                set localMPSResults($sector) Problem
            }
            if ![string compare [lindex $mpsTripStatus 1] "0"] {
                set mainMPSResults($sector) Trip
            } else {
                set mainMPSResults($sector) Problem
            }
        }
        StopControllaw
        if {[string match $verificationType "pending"]} {
            resetBPLDTrips
        }
        EnableScanButtons
        return
    }
    if $abortBPLDTests {
        eval $statusCallback {"Trip limit test sequence terminated by ABORT button press."}
        set abortBPLDTests 0
        StopControllaw
        resetBPLDTrips
        EnableScanButtons
        return
    }

    if !$firstPass {
        if ![string compare $verificationType "pending"] {
            if [catch {exec cavput -pend=30 -list=S${sector}BPLD:ResetBO=1 \
                     } result] {
                return -code error "ExecuteNext: problem with cavput: $result"
            }
        }
        # Assess results from previous steering (and direction).
        # If the direction was "center" then there was no trips to check.
        if [string compare $direction "center"] {
            eval $statusCallback {"Assessing results from $PVTripName($direction) test on BPLD $sector"}
            # reset trips if any for pending trip type.
            if ![string compare $verificationType "pending"] {
                if [catch {resetBPLDTrips} result] {
                    return -code error "ExecuteNext: $result"
                }
            }
            if {!$IsDigital} {
                if [catch {exec cavget -pend=30 -list=S${sector}BPLD:TripPendingCC.VAL \
                         } pendingTrip] {
                    return -code error "ExecuteNext: $pendingTrip"
                }
                if !$pendingTrip {
                    set tripStatus [list Problem Problem]
                } else {
                    if [catch {exec cavget -pend=30 -list=S${sector}BPLD:CH -list=1,2 -list=:$PVTripName($direction)TripBI} tripStatus] {
                        return -code error "ExecuteNext: $tripStatus"
                    }
                }
                if ![string compare [lindex $tripStatus 0] "Trip"] {
                    set ${direction}1Results($sector) Trip
                } else {
                    set ${direction}1Results($sector) Problem
                }
                if ![string compare [lindex $tripStatus 1] "Trip"] {
                    set ${direction}2Results($sector) Trip
                } else {
                    set ${direction}2Results($sector) Problem
                }
            } else {
                # for digital BPLDs
                if [catch {exec cavget -pend=30 -list=$firstChannelBPM($sector),$secondChannelBPM($sector) \
                             -list=:dbpld:[string tolower $direction]BI.VAL \
                         } pendingTrips] {
                    return -code error "ExecuteNext: $pendingTrips"
                }
                set ${direction}1Results($sector) [lindex $pendingTrips 0] 
                set ${direction}2Results($sector) [lindex $pendingTrips 1]
            }
        }
    }
    set firstPass 0
    set direction [lindex $sequenceList 0]
    set sequenceList [lrange $sequenceList 1 end]
    # new steering setpoints
    if {!$IsDigital} {
        if [catch {exec cavput -pend=30 -list=$firstChannelBPM($sector):ms:x:SetpointAO=$xSetpoint($direction),$firstChannelBPM($sector):ms:y:SetpointAO=$ySetpoint($direction),$secondChannelBPM($sector):ms:x:SetpointAO=$xSetpoint($direction),$secondChannelBPM($sector):ms:y:SetpointAO=$ySetpoint($direction)  \
                 } result ] {
            return -code error "ExecuteNext: $result"
        }
    } else {
        if [catch {exec cavput -pend=30 -list=$firstChannelBPM($sector):ms:x:SetpointAO=$xSetpoint(${direction}.us),$firstChannelBPM($sector):ms:y:SetpointAO=$ySetpoint(${direction}.us),$secondChannelBPM($sector):ms:x:SetpointAO=$xSetpoint(${direction}.ds),$secondChannelBPM($sector):ms:y:SetpointAO=$ySetpoint(${direction}.ds) \
                 } result ] {
            return -code error "ExecuteNext: $result"
        }
    }
    # give a 1 second chance for the setpoints to be loaded in the ioc, and the
    # errors to be calculated before checking the error.
    after 1000
    eval $statusCallback {"Waiting for sddscontrollaw to adjust the orbit in ID${sector} for $direction test."}
    if [catch {WaitForControllaw -sector $sector -statusCallback $statusCallback \
                 -ultimateCallback "ExecuteNext -sector $sector -IsDigital $IsDigital"} result] {
        return -code error "ExecuteNext: $result"
    }
}


proc ControllawInfoButton {args} {
    if [catch {exec medm -x -attach -macro "RCPV=S:RC:OrbitControlLawXC" ./sr/psApp/APSRunControlSingle.adl &
        exec medm -x -attach -macro "RCPV=S:RC:OrbitControlLawYC" ./sr/psApp/APSRunControlSingle.adl & 
        exec medm -x -attach -macro "RCPV=DP:HOCtestRC" ./sr/psApp/APSRunControlSingle.adl &
        exec medm -x -attach -macro "RCPV=DP:VOCtestRC" ./sr/psApp/APSRunControlSingle.adl & \
             } result ] {
        return -code error  $result
    }
    return
}

proc StartControllaw {args} {
    global BPLDList firstChannelBPM secondChannelBPM

    set matrixDir(h) /home/helios/oagData/sr/orbitControllaw/lattices/default/h.BPLDVerification
    set matrixDir(v) /home/helios/oagData/sr/orbitControllaw/lattices/default/v.BPLDVerification

    set statusCallback puts
    set restartOnly 0
    APSStrictParseArguments {statusCallback restartOnly}

    # check presence of orbit control files
    foreach plane {h v} {
        foreach file {irm definitions.msAve tests} {
            set fullFile $matrixDir($plane)/$file
            if ![file exists $fullFile] {
                return -code error "StartControllaw: Can't find file $fullFile"
            } 
        }
    }
    # check presence of beam
    if [catch {exec cavget -pend=30 -list=S-DCCT:CurrentM \
             } current] {
        return -code error "StartControllaw: $current"
    }
    if [expr abs($current) < 0.05] {
        return -code error "StartControllaw: not enough current in ring."
    }

    if [catch {APSSRSetIOCAveraging -num2Ave 5 -filterCoeff 1} result ] {
        return -code error "StartControllaw: $result"
    }
    set bpmList ""
    foreach sector $BPLDList {
        lappend bpmList $firstChannelBPM($sector) $secondChannelBPM($sector)
    }
    if [catch {exec cavput -pend=30 -list=[join $bpmList ,] \
                 -list=:ConvertBO=Linear} result ] {
        return -code error "StartControllaw: $result"
    }

# reset trips if any
    if [catch {resetBPLDTrips} result] {
        return -code error "StartControllaw: problem with resetBPLDTrips: $result"
    }
    set gain 0.5
    set interval 0.75
    set steps 10000
    if {!$restartOnly} {
        CheckRunControls -statusCallback $statusCallback
    }

    set options "-actuator=ControlName \
         -deltaLimit=value=4 \
         -gain=$gain -interval=$interval \
         -steps=$steps -verb"
    set oldDir [pwd]
    cd $matrixDir(h)
    if {!$restartOnly ||
        [expr $restartOnly && ![exec cavget -pend=30 -list=S:RC:OrbitControlLawXC.RUN]]} {
        APSExecLog .controllawX -width 95 \
          -unixCommand "sddscontrollaw irm \
         -test=tests -controlQuantityDefinitions=definitions.msAve \
         -runControlPV=string=S:RC:OrbitControlLawXC \
         -runControlDescription=string=BPLDtripLimits \
         $options"
    }
    cd $matrixDir(v)
    if {!$restartOnly ||
        [expr $restartOnly && ![exec cavget -pend=30 -list=S:RC:OrbitControlLawYC.RUN]]} {
        APSExecLog .controllawY -width 95 \
          -unixCommand "sddscontrollaw irm \
         -test=tests -controlQuantityDefinitions=definitions.msAve \
         -runControlPV=string=S:RC:OrbitControlLawYC \
         -runControlDescription=string=BPLDtripLimits \
         $options"
    }
    cd $oldDir
    return
}

proc StopControllaw {args} {
    global BPLDList firstChannelBPM secondChannelBPM
    # stop controllaw
    if [catch {exec cavput -pend=30 -list=S:RC:OrbitControlLawXC,S:RC:OrbitControlLawYC -list=.ABRT=1} result] {
        return -code error "StopControllaw: $result"
    }
    set bpmList ""
    foreach sector $BPLDList {
        lappend bpmList $firstChannelBPM($sector) $secondChannelBPM($sector)
    }
    
    if [catch {exec cavput -pend=30 -pend=30 -list=[join $bpmList ,] \
                 -list=:ConvertBO=Poly} result ] {
        return -code error "StopControllaw: $result"
    }
    return
}

proc resetBPLDTrips {} {
    global BPLDList IsDigitalList firstChannelBPM secondChannelBPM

    if [catch {exec cavput -pend=10 \
                 -list=S -list=[join $BPLDList ,] -list=BPLD:ResetBO=1 -blunderAhead \
             } result] {
#        return -code error "resetBPLDTrips: $result"
    }
    set BP0List ""
    set AP0List ""
    foreach sector $BPLDList IsDigital $IsDigitalList {
        if {!$IsDigital} continue
        lappend BP0List $firstChannelBPM($sector)
        lappend AP0List $secondChannelBPM($sector)
    }
    if [llength $BP0List] {
        if [catch {exec cavput -pend=30 -list=[join $BP0List ,] \
                     -list=:dbpld:xresetBO=1 -blunderAhead
            exec cavput -pend=30 -list=[join $AP0List ,] \
                     -list=:dbpld:xresetBO=1 -blunderAhead \
                 } result] {
            return -code error "resetBPLDTrips: $result"
        }
    }
}

proc WaitForControllaw {args} {
    global tolerance timeLimit firstChannelBPM secondChannelBPM
    set sector ""
    set statusCallback puts
    set ultimateCallback APSNoOp
    APSStrictParseArguments {sector statusCallback ultimateCallback}

    set pvType msAve
# stop controllaw when the orbit is within a certain window
# or when the main mps is tripped
    APSExecLog .cawait -name "Waiting for sddscontrollaw to converge" \
      -width 95 \
      -unixCommand "cawait \
         -waitFor=$firstChannelBPM($sector):${pvType}:x:ErrorCC,lowerLimit=-$tolerance,upperLimit=$tolerance \
         -waitFor=$firstChannelBPM($sector):${pvType}:y:ErrorCC,lowerLimit=-$tolerance,upperLimit=$tolerance \
         -and \
         -waitFor=$secondChannelBPM($sector):${pvType}:x:ErrorCC,lowerLimit=-$tolerance,upperLimit=$tolerance \
         -and \
         -waitFor=$secondChannelBPM($sector):${pvType}:y:ErrorCC,lowerLimit=-$tolerance,upperLimit=$tolerance \
         -and \
         -waitFor=S-DCCT:CurrentM,below=0.05 \
         -or \
         -interval=1 -timelimit=$timeLimit \
         -emit=event=0,timeout=1" \
      -callback $ultimateCallback
    return 0
}

proc DisableScanButtons {args} {
    global BPLDList scanButtonWidget 
    foreach sector $BPLDList {
        APSDisableButton $scanButtonWidget($sector)
    }
    return
}

proc EnableScanButtons {args} {
    global BPLDList scanButtonWidget 
    foreach sector $BPLDList {
        APSEnableButton $scanButtonWidget($sector)
    }
    return
}


proc CheckRunControls {args} {
    # turns off orbit correction, RT feedback and offset compensation
    APSStrictParseArguments {statusCallback}
    # abort and clear run control slot just in case
    if [catch {
        exec cavput -pend=30 -list=S:RC:OrbitControlLawXC,S:RC:OrbitControlLawYC -list=.ABRT=1 
        exec cavput -pend=30 -list=DP: -list=H,V -list=OCtestRC -list=.ABRT=1 \
      } result] {
        return -code error "CheckRunControls: problem with cavput: $result"
    }
    # give 2 seconds to clear. Original 0.5 s wasn't enough.
    after 2000
    if [catch {
        exec cavput -pend=30 -list=S:RC:OrbitControlLawXC,S:RC:OrbitControlLawYC -list=.CLR=1 
        exec cavput -pend=30 -list=DP: -list=H,V -list=OCtestRC  -list=.CLR=1 \
      } result] {
        return -code error "CheckRunControls: problem with cavput: $result"
    }
    if [catch {exec cavget -pend=30 -list=S:BPMOffsetAdjustmentsRC.RUN} offsetAdjustment] {
        return -code error "CheckRunControls: problem with cavput: $offsetAdjustment"
    }
    # check offset compensation
    if $offsetAdjustment {
        if [catch {exec cavput -pend=30 -list=S:BPMOffsetAdjustmentsRC.ABRT=1} result] {
            return -code error "CheckRunControls: problem with cavput: $result"
        }
        eval $statusCallback {"Offset adjustment run control aborted."}
    }
    # check RT feedback
    if [catch {exec cavget -pend=30 -list=SRFB:GBL: -list=Hor,Vert -list=:CloseLoopBO \
             } closedLoops] {
        return -code error "CheckRunControls: problem with cavput: $closedLoops"
    }
    if {[expr [string match [lindex $closedLoops 0] Close] || \
           [string match [lindex $closedLoops 1] Close]] } {
        if [catch {exec cavput -pend=30 -list=SRFB:GBL: -list=Hor,Vert -list=:CloseLoopBO=1 \
                 } result] {
            return -code error "CheckRunControls: problem with cavput: $result"
        }
        eval $statusCallback {"Opening RT feedback loops."}
    }
    return
}

