
proc APSCheckPVConnections {args} {
    set pvList ""
    set pendIOTime 10
    if [APSStrictParseArguments {pvList pendIOTime}] {
        return -code error "APSCheckPVConnections: bad arguments: $args"
    }
    if [catch {exec cavget -list=[join $pvList ,] -pend=10} result] {
        return -code error "APSCheckPVConnections: $result"
    }
    set problemList ""
    foreach pv $pvList value $result {
        if [string compare $value ?]==0 {
            lappend problemList $pv
        }
    }
    return $problemList
}


proc APSBunchTrainConnect {args} {
    set checkPVs 0
    if {[APSStrictParseArguments {checkPVs}]} {
        return -code error "APSBunchTrainConnect: problem with arguments: $args"
    }
    global apsBunchTrainConnected apsBunchTrainPvList apsBunchTrainTclVarList debugMode

    if ![info exists apsBunchTrainConnected] {
        set apsBunchTrainConnected 0
    }

    set debugMode 0

    if {$apsBunchTrainConnected && !$checkPVs} return

    set apsBunchTrainPvList \
      [list Mt:Ddg3chan4.GATE Mt:Ddg3chan0.GATE Mt:Ddg3chan1.GATE \
         It:Bs:BunchSelect_lo Mt:S:BunchSelect_lo PAR:RF1:bucketNumber \
         S-DCCT:CurrentM BTS:CM:q S:MPS:readyCC \
         L:LR:shutter4C \
         L1:RG1:KIK:chargeTrigC L1:RG2:KIK:chargeTrigC L1:RFG:RF:SW2:positionMI \
         Mt:SRinjectMultiEStopBO Mt:SRinjectMultiBO Mt:SRinjectStatMBBI \
         Mt:SRinjectNumBunchesAO Mt:SRinjectDoneBO \
         S28B:P1:Ave:sum \
         S28B:P1:ms.XAVS P:DG1:bDelayAO]

    if $checkPVs {
        if [catch {APSCheckPVConnections -pvList $apsBunchTrainPvList -pendIOTime 10} result] {
            return -code error "APSBunchTrainConnect: $result"
        }
        if [llength $result] {
            return -code error "APSBunchTrainConnect: problems with PVs: $result"
        }
        return
    }

    set apsBunchTrainTclVarList \
      {apsBunchTrainIKTrig apsBunchTrainIS1Trig apsBunchTrainIS2Trig \
         apsBunchTrainBsBucketSel apsBunchTrainSrBucketSel apsBunchPARBucket \
         S35BeamCurrent BTSCharge mpsStatus \
         apsPCGunTrigger \
         apsRF1GunKickerTrigger apsRF2GunKickerTrigger RFGunSelection \
         apsBunchTrainMBStop apsBunchTrainMBInject apsBunchTrainMBStatus \
         apsNumToInject apsInjectionDone \
         apsBPMGroupSum \
         apsBPMGroupSumStatus parBunchCleaningDelay}

    set kicker [exec cavget -list=L1:RFG:RF:SW2:positionMI]
    
    eval global $apsBunchTrainTclVarList
    
    if !$debugMode {
        if {[pv linkw $apsBunchTrainTclVarList $apsBunchTrainPvList]!=0} {
            return -code error "APSBunchTrainConnect: error connecting to PVs $apsBunchTrainPvList"
        }
        if [pv getw $apsBunchTrainTclVarList] {
            return -code error "APSBunchTrainConnect: error reading PVs $apsBunchTrainPvList"
        }
        set RFGunKickerHVPV L1:RG2:KIK:OnBO
        if [string compare $RFGunSelection "RG1"]==0 {
            set RFGunKickerHVPV L1:RG1:KIK:OnBO
        }
        if [pv linkw apsRFGunKickerHV $RFGunKickerHVPV] {
            return -code error "APSBunchTrainConnect: error connecting to PV $RFGunKickerHVPV"
        }
        global apsRFGunKickerHV
        lappend apsBunchTrainTclVarList apsRFGunKickerHV
        lappend apsBunchTrainPvList $RFGunKickerHVPV
    } else {
        if {[pv linkw S35BeamCurrent S-DCCT:CurrentM]!=0} {
            return -code error
        }
    }
    
    set apsBunchTrainConnected 1
    return -code ok
}

proc APSTopupConnect {args} {
    set checkPVs 0
    if {[APSStrictParseArguments {checkPVs}]} {
        return -code error "APSTopupConnect:problem with arguments: $args"
    }

    set apsTopupTclVarList \
      {apsTopupEnabled apsTopupTarget apsTopupTolerance apsTopupInterval apsTopupCountdown \
         apsTopupDeltaI apsTopupTime2WarnInjector apsTopupTime2WarnCalc \
         apsBoosterEKexcluded apsTopupInterleaving apsTopupRG2AlphaMagnet
      }
    
    set apsTopupPvList \
      {Mt:TopUpAutoEnableC Mt:TopUpCurrentTargetP Mt:TopUpCurrentToleranceP Mt:TopUpIntervalP Mt:TopUpTime2Inject \
         Mt:TopUpCurrentDeltaIM.VAL Mt:TopUpTime2WarnInjector.VAL Mt:TopUpTime2WarnCalc.VAL \
         Mt:SRinjectMultiExcludBsEKBO.VAL LI:iLeavePermitCC L1:RG2:LFA:CurrentAO
      }

    if $checkPVs {
        if [catch {APSCheckPVConnections -pvList $apsTopupPvList -pendIOTime 10} result] {
            return -code error "APSTopupConnect: $result"
        }
        if [llength $result] {
            return -code error "APSTopupConnect: problem with PVs: $result"
        }
        return
    }

    eval global $apsTopupTclVarList
    if {[pv linkw $apsTopupTclVarList $apsTopupPvList]!=0} {
        return -code error "APSTopupConnect: error connecting to PVs $apsTopupPvList"
    }
    pv getw $$apsTopupTclVarList
    set apsTopupConnected 1
    return -code ok
}

proc APSTopupReadbackConnect {args} {
    set checkPVs 0
    if [APSStrictParseArguments {checkPVs}] {
        return -code error "APSTopupReadbackConnect: bad arguments: $args"
    }

    set apsTopupPvList \
      {Mt:TopUpCurrentTargetP Mt:TopUpCurrentToleranceP Mt:TopUpIntervalP \
         Mt:TopUpWarnTimeP.VAL Mt:TopUpWarnTime2InjectorP.VAL \
         Mt:SRinjectNumBunchesAO.VAL S:IS1:VoltageSetSendAO It:L:bunch_countCC
      }
    # this global list variable is used in SRBunchTrain application
    # in procedures realted to readbacks of top-up related PVs.
    # This variable is not to be confused with the local
    # variable in the procedure APSTopupConnect above.
    global apsTopupTclVarList
    set apsTopupTclVarList \
      {apsTopupTargetReadback apsTopupToleranceReadback apsTopupIntervalReadback \
         apsTopupWarningTimeReadback apsTopupInjectorWarningTimeReadback \
         apsTopupBunchesReadback SIS1SetpointReadback apsLinacBunchesReadback
      }
    
    if $checkPVs {
        if [catch {APSCheckPVConnections -pvList $apsTopupPvList -pendIOTime 10} result] {
            return -code error "APSTopupReadbackConnect: $result"
        }
        if [llength $result] {
            return -code error "APSTopupReadbackConnect: problems with PVs: $result"
        }
        return
    }

    eval global $apsTopupTclVarList
    if {[pv linkw $apsTopupTclVarList $apsTopupPvList]!=0} {
        return -code error "APSTopupReadbackConnect: error connecting to PVs $apsTopupPvList"
    }
    if {[pv getw $apsTopupTclVarList] != 0} {
        return -code error "APSTopupReadbackConnect: $errorCode"
    }
    if {[pv umon $apsTopupTclVarList RefreshTopupReadbacks] != 0} {
        return -code error "APSTopupReadbackConnect: $errorCode"
    }
        set apsTopupConnected 1
    return -code ok
}

proc TerminateInjection {args} {
    set keepWarm 0
    set manageLinacBunches 0
    APSStrictParseArguments {keepWarm manageLinacBunches}

    global KickersOn BunchDone apsBunchTrainTclVarList debugMode apsBunchTrainStopMode errorCode injectionAbort
    global apsLinacBunches
    eval global $apsBunchTrainTclVarList
    set injectionAbort 0
    set apsBunchTrainIKTrig 0
    set apsBunchTrainBsBucketSel 0
    set apsBunchTrainSrBucketSel 0
    set apsBunchPARBucket 0
    set apsBunchTrainIS2Trig 0
    set apsBunchTrainMBStop 1
    if !$keepWarm {
        set apsBunchTrainIS1Trig 0
        set apsPCGunTrigger 0
        set apsRF1GunKickerTrigger 0
        set apsRF2GunKickerTrigger 0
        set apsRFGunKickerHV 1
    } else {
        # leave the thick septum, gun, and pulsed coil on
        set apsBunchTrainIS1Trig 1
        set apsPCGunTrigger 1
        set apsRF1GunKickerTrigger 1
        set apsRF1GunKickerTrigger 1
        set apsRFGunKickerHV 1
    }
    if !$debugMode {
        if [pv putw apsBunchTrainMBStop] {
            return -code error "pv putw error in TerminateInjection: $errorCode"
        }
        if [pv putw {apsBunchTrainIKTrig apsBunchTrainBsBucketSel \
                       apsBunchTrainSrBucketSel \
                       apsBunchPARBucket apsBunchTrainIS1Trig apsBunchTrainIS2Trig \
                       apsBunchTrainMBStop}] {
            return -code error "pv putw error in TerminateInjection: $errorCode"
        }
    } else {
        puts stderr "Pretending to terminate injection."
    }
    set KickersOn 0
    set BunchDone 1
    # reset the longer averaging of the sum signal.
    #if [catch {exec cavput -list=S18B:P5:num2ave_ao=20} result] {
    #    APSSetVarAndUpdate bunchTrainStatus "$result"
    #    return 0
    #}
    catch {exec caput S:BPMOffsetAdjustmentsRC.SUSP 0} 
    # reset the original number of linac pulses
    if {$manageLinacBunches && \
          [catch {SetLinacBunches -number $apsLinacBunches } returnValue]} {
        APSSetVarAndUpdate bunchTrainStatus "$returnValue"
        return 0
    }
}

proc PauseInjection {} {
    global KickersOn BunchDone apsBunchTrainTclVarList 
    eval global $apsBunchTrainTclVarList
    set apsBunchTrainIKTrig 0
    set KickersOn 0
    set BunchDone 1
}

proc DoInjection {bucket dwell} {
    global KickersOn BunchDone apsBunchTrainTclVarList debugMode
    eval global $apsBunchTrainTclVarList

    APSBunchTrainConnect

    set apsBunchPARBucket [expr $bucket%36]
    # change bucket selection PVs.

    if !$debugMode {
        set apsBunchTrainBsBucketSel $bucket
        set apsBunchTrainSrBucketSel $bucket
        if [pv putw {apsBunchPARBucket apsBunchTrainBsBucketSel apsBunchTrainSrBucketSel}] {
            global errorCode
            return -code error "pv putw error in DoInjection: $errorCode"
        } 
    } else {
        puts stderr "Pretending to change bucket selection."
    }


    set dwell [expr int($dwell*1e3)]
    if {!$KickersOn} {
        set apsBunchTrainIKTrig 1
        if !$debugMode {
            if [pv putw apsBunchTrainIKTrig] {
                global errorCode
                return -code error "pv putw error in DoInjection: $errorCode"
            }
        } else {
            puts stderr "Pretending to turn on kickers."
        }

        set KickersOn 1
        after 500
    }
    # schedule turn-off of triggers after dwell seconds
    set BunchDone 0
    after $dwell {set BunchDone 1}
}

proc DoBunchTrainInjection {args} {
    global BunchDone InjectionAbort KickersOn apsBunchTrainTclVarList
    global S35BeamCurrent errorCode apsBunchTrainConnected debugMode

    catch {APSBunchTrainConnect}
    eval global $apsBunchTrainTclVarList

    set start 0
    set interval 1
    set number 1
    set dwell 10
    set callback ""
    set stopAt 0
    set stopAtSum 0
    set useSum DCCT
    set cycles 1
    set multiplet 1
    set keepWarm 0
    set manageLinacBunches 0
#    puts stderr "DoBunchTrainInjection: $args"
    APSStrictParseArguments {start interval number dwell callback fast stopAt stopAtSum \
                         useSum cycles multiplet keepWarm manageLinacBunches}

    if {$start<0} { set start 0}
    set start [expr $start%1295]
    if {$interval<0} { set interval 0}
    set interval [expr $interval%1296]
    if {$dwell<0} { set dwell 1 }
    if {$stopAt<0} { set stopAt 0 }
    if {$stopAtSum<0} { set stopAtSum 0 }
    if {$cycles<=0} {set cycles 1}
    if {$multiplet<=0} {set multiplet 1}

    set bucket $start
    set BunchDone 0
    set KickersOn 0
    set loopTime 200

    # turn on septum discharge and gun/pulsed-coil and allow warmup time
    if !$debugMode {
        set apsBunchTrainIS1Trig 1
        set apsBunchTrainIS2Trig 1
        set apsPCGunTrigger 1
        set apsRF1GunKickerTrigger 1
        set apsRF2GunKickerTrigger 1
        set apsRFGunKickerHV 1
        if [pv putw {apsBunchTrainIS1Trig apsBunchTrainIS2Trig \
                       apsRF1GunKickerTrigger apsRF2GunKickerTrigger apsRFGunKickerHV \
                       apsPCGunTrigger}] {
            global errorCode
            return -code error "pv putw error in DoInjection: $errorCode"
        }
    } else {
        puts stderr "Pretending to turn on septa."
    }
    after 3500

    set number0 $number
    set bucket0 $bucket
    set multiplet0 $multiplet
    set currentAchieved 0
    while {!$InjectionAbort && $cycles>0} {
        set multiplet $multiplet0
        while {$multiplet>0} {
            set number $number0
            set bucket [expr $bucket0+($multiplet0-$multiplet)]
            while {[expr $number > 0] } {
                if {$callback!=""} { $callback $bucket }
                DoInjection $bucket $dwell
                while {!$BunchDone} {
                    if [pv getw mpsStatus] {
                        return -code error
                    }
                    set mpsStatus $mpsStatus
                    if !$mpsStatus {
                        set InjectionAbort 1
                        if {$callback!=""} { $callback "aborted due to MPS trip"}
                        TerminateInjection -keepWarm $keepWarm -manageLinacBunches $manageLinacBunches
                        return 0
                    }
                    if {$useSum!="Sum"} {
                        if $stopAt {
                            if [pv getw S35BeamCurrent] {
                                return -code error
                            } else {
                                set S35BeamCurrent $S35BeamCurrent
                                set current [expr abs($S35BeamCurrent)]
                                if {$stopAt<=$current} {
                                    set currentAchieved 1
                                    set InjectionAbort 1
                                }
                            }
                        } else {
                            if [pv getw apsBPMGroupSum] {
                                return -code error
                            } else {
                                set apsBPMGroupSum $apsBPMGroupSum
                                if {$stopAtSum<=$apsBPMGroupSum} {
                                    set currentAchieved 1
                                    set InjectionAbort 1
                                }
                            }
                        }
                    }
                    if {$InjectionAbort} {
                        TerminateInjection -keepWarm $keepWarm -manageLinacBunches $manageLinacBunches
                        return 0
                    }
                    update
                    after $loopTime
                }
                if {$InjectionAbort} {
                    TerminateInjection -keepWarm $keepWarm -manageLinacBunches $manageLinacBunches
                    return 0
                }
                set bucket [expr ($bucket+$interval)%1296]
                set number [expr $number - 1]
            }
            incr multiplet -1
        }
        incr cycles -1
    }
    set returnValue 1
    if {$InjectionAbort} {
        set returnValue 0
    }
    TerminateInjection -keepWarm $keepWarm -manageLinacBunches $manageLinacBunches
    return $returnValue
}

proc GetLinacBunches {} {
    if [catch {exec cavget -list=It:L:bunch_countCC \
             } result] {
        return -code error $result
    }
    return $result
}

proc SetLinacBunches {args} {
    set number 0
    APSStrictParseArguments {number}
    if {$number <0 } {set number 0}
    if {$number > 15} {set number 15}
    set endBunch [expr $number * 2 - 2]
    set endBunchp2 [expr $number * 2 ]
    if [catch {exec cavput -list=It:L:B \
                 -range=begin=0,end=$endBunch,interval=2,format=%02ld \
                 -list=_enb_bo=On \
             } result] {
        return -code error $result
    }
    if [catch {exec cavput -list=It:L:B \
                 -range=begin=$endBunchp2,end=28,interval=2,format=%02ld \
                 -list=_enb_bo=Off \
             } result] {
        return -code error $result
    }
}

set InjectionAbort 0
proc AbortInjection {} {
    global InjectionAbort
    set InjectionAbort 1
}


proc APSPARCleaning {args} {
    set state 0
    APSStrictParseArguments {state}
    if $state {
        if [catch {exec APScavput -list=P:Ds345:AmplitudeRmsC=0.23 \
                 } result] {
            return -code error "APSPARCleaning: $result"
        }
    } else {
        if [catch {exec APScavput -list=P:Ds345:AmplitudeRmsC=0.04 \
                 } result] {
            return -code error "APSPARCleaning: $result"
        }
    }
}
