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

    if [catch {APSBunchTrainConnect}] {
        return -code error
    }
    eval global $apsBunchTrainTclVarList

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

    if {[string length $pattern]>$interval} {
        return -code error "Multiplet pattern is longer than the interval between multiplets."
    }
    if {![string length $pattern] && $interval<$multiplet} {
        return -code error "The number of bunches in a multiplet is greater than the spacing."
    }
    if {$start<0} { set start 0}
    set start [expr $start%1295]
    set interval [expr $interval%1296]
    if {$interval<=0} { set 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}
    if {$stopAt>=95} {
        set stopMode Estop
    }

    if [catch {exec cavput -pend=10 -list=Mt:SR \
                 -list=injectCurrentLimitAO=$stopAt,injectMaxCyclesAO=$cycles,injectModeBO=Single,CurrentLimitModeBO=$stopMode } result] {
        return  -code error "DoIOCBunchTrainInjection: Error setting IOC parameters: $result"
    }

    if [catch {APSSRSetBucketPattern -start $start \
                 -pattern $pattern -count $number -multiplet $multiplet \
                 -interval $interval} result] {
        return  -code error "DoIOCBunchTrainInjection: Error setting bucket pattern: $result"
    }

    if {$useSum!="Sum"} {
        if [pv getw S35BeamCurrent] {
            eval $callback {"$errorCode"}
        } else {
            set S35BeamCurrent $S35BeamCurrent
            if {$S35BeamCurrent>$stopAt} {
                return
            }
        }
    } else {
        if [pv getw {apsBPMGroupSumStatus apsBPMGroupSum}] {
            eval $callback {"$errorCode"}
        } else {
            set apsBPMGroupSumStatus $apsBPMGroupSumStatus
            if [string compare $apsBPMGroupSumStatus Old]==0 {
                eval $callback {"S8B:P5:sum is not updating---can't do fill-on-fill."}
                return -code error
            }
            set apsBPMGroupSum $apsBPMGroupSum
            if {$apsBPMGroupSum>$stopAtSum} {
                return
            }
        }
    }
        
    set bucket $start
    set BunchDone 0
    set KickersOn 0
    set loopTime 200

    # turn on septum discharge and gun/pulsed-coil and allow warmup time
    # reduced warm-up time to 1 sec.
    if !$debugMode {
        set apsPCGunTrigger 1
        set apsRFGunKickerTrigger 1
        if [pv putw {apsBunchTrainIS1Trig \
                       apsRFGunKickerTrigger \
                       apsPCGunTrigger}] {
            return -code error "DoIOCBunchTrainInjection: $errorCode"
        }
        after 1000
    } else {
        puts stderr "Pretending to turn on septa."
    }

    set apsBunchTrainMBInject On
    if [pv putw apsBunchTrainMBInject] {
        return  -code error "DoIOCBunchTrainInjection: $errorCode"
    }
    after 1000

    set updateInterval [expr int(2000.0/$loopTime+1)]
    set updateCount $updateInterval
    set returnValue 0
    while {!$InjectionAbort} {
        after $loopTime
        if [pv getw {apsBunchTrainMBStatus S35BeamCurrent mpsStatus}] {
            eval $callback {"$errorCode"}
        }
        set apsBunchTrainMBStatus $apsBunchTrainMBStatus
        set S35BeamCurrent $S35BeamCurrent
        set mpsStatus $mpsStatus
        if {[string match $apsBunchTrainMBStatus "Ready"]} {
            eval $callback {"Injection externally terminated: Value of Mt:SRinjectStatMBBI is $apsBunchTrainMBStatus."}
            break
        }
        if !$mpsStatus {
            eval $callback {"Injection aborted due to MPS trip."}
            set InjectionAbort 1
            break
        }
        if {$S35BeamCurrent>$stopAt} {
            eval $callback {"Current target reached."}
            break
        }
        if {$useSum=="Sum"} {
            if [pv getw apsBPMGroupSum] {
                eval $callback {"$errorCode"}
            } else {
                set apsBPMGroupSum $apsBPMGroupSum
                if {$apsBPMGroupSum>$stopAtSum} {
                    eval $callback {"Sum signal target reached."}
                    break
                } else {
                    eval $callback {"Sum signal target not reached. Continuing..."}
                }
            }
        }
            
        if !$updateCount {
            if {$useSum!="Sum"} {
                eval $callback {"Now at [format %.2f $S35BeamCurrent] mA"}
            } else {
                eval $callback {"Now at [format %.2f $S35BeamCurrent] mA (sum of [format %.1f $apsBPMGroupSum])"}
            }
            set updateCount $updateInterval
        } else {
            incr updateCount -1
        }
    }
    if !$InjectionAbort {
        set returnValue 1
    }
    TerminateInjection -keepWarm $keepWarm -manageLinacBunches $manageLinacBunches
    return $returnValue
}

proc APSSRSetBucketPattern {args} {
    if [catch {exec cavget -list=Mt:SRinjectMaxBunchLO -pend=5} injectionCountLimit] {
        return -code error "APSSRSetBucketPattern(1): $injectionCountLimit"
    }

    set start 0
    set pattern ""
    set interval 36
    set count 60
    set multiplet 1
    
    if {[APSStrictParseArguments {start interval count multiplet pattern}]} {
        return -code error "APSSRSetBucketPattern(2): unknown argument given"
    }
    if {$count<1 || (![string length $pattern] && $multiplet<1)} {
        return -code error "APSSRSetBucketPattern(3): bad arguments (count=$count, multiplet=$multiplet, pattern=$pattern)"
    }
    set patternList ""
    set multipletSlots $multiplet
    if {[set stringLength [string length $pattern]]} {
        set multiplet 0
        set multipletSlots 0
        for {set i 0} {$i<$stringLength} {incr i} {
            set bit [string index $pattern $i]
            if {[string compare $bit 0]==0} {
                lappend patternList 0
                incr multipletSlots 
            }
            if {[string compare $bit 1]==0} {
                lappend patternList 1
                incr multiplet
                incr multipletSlots 
            }
        }
        if {$multiplet==0 || $multipletSlots==0} {
            return -code error "APSSRSetBucketPattern(4): multiplet pattern string is invalid: $pattern"
        }
    } 
    if {$multiplet>$interval} {
        return -code error "APSSRSetBucketPattern(5): multiplet length greater than interval"
    }
    if {[expr $count*$multiplet]>$injectionCountLimit} {
        return -code error "APSSRSetBucketPattern(6): (multiplet length)*count is larger than $injectionCountLimit"
    }

    # set bunch count
    # zero out the pattern just to be pretty
    if [catch {exec cavput -list=Mt:SRinjectNumBunchesAO=[expr $count*$multiplet] -pend=15
        exec cavput -list=Mt:SRbunch=0 -pend=15 \
                 -range=begin=0,end=[expr $injectionCountLimit-1],format=%03ld} result] {
        return -code error "APSSRSetBucketPattern(7): $result"
    }
    set valueList ""
    set bunch 0
    for {set offset 0} {$offset<$multipletSlots} {incr offset} {
        set count0 $count
        set bucket $start
        if [llength $patternList] {
            set bit [lindex $patternList $offset]
        } else {
            set bit 1
        }
        while {$count0} {
            set mbucket [expr ($bucket+$offset)%1296]
            if $bit {
                lappend valueList [format %03ld $bunch]=$mbucket
                incr bunch 1
            }
            set bucket [expr $bucket+$interval]
            incr count0 -1
        }
    }
    # send the pattern
    if [catch {exec cavput -list=Mt:SRbunch -list=[join $valueList ,] -pend=15} \
          result] {
        return -code error "APSSRSetBucketPattern(8): $result"
    }
}

proc APSLevelBunchCharge {args} {
    set desiredCurrent 102
    set bpmGroupCurrent [expr 102./23]
    set bpmGroupBuckets 1
    set otherBuckets 22
    set deficitThreshold 0.1
    set keepWarm 0
    set manageLinacBunches 0
    set cycles 1
    set BBCMAverages 10
    set statusCallback APSNoOp
    set doubleFirst 1
    set uniformCurrent 0
    APSStrictParseArguments {desiredCurrent bpmGroupCurrent keepWarm \
                               bpmGroupBuckets otherBuckets deficitThreshold \
                               manageLinacBunches cycles BBCMAverages statusCallback \
                               doubleFirst uniformCurrent}

    if [catch {APSBunchTrainConnect}] {
        return -code error
    }
    global InjectionAbort errorCode apsBunchTrainTclVarList
    eval global $apsBunchTrainTclVarList
    set InjectionAbort 0

    # turn on injector
    TogglePulsedMagnetEnables -location GunToBoosterExt
    after 2000
    if $uniformCurrent {
        set bpmGroupCurrent [expr ($bpmGroupBuckets*$desiredCurrent)/(1.0*$bpmGroupBuckets+$otherBuckets)]
    }

    while {$cycles && !$InjectionAbort} {
        incr cycles -1
        # check beam current against target
        if [pv getw S35BeamCurrent] {
            return -code error "DoIOCBucketFilling: $errorCode"
        } else {
            set S35BeamCurrent $S35BeamCurrent
            if {$S35BeamCurrent>$desiredCurrent} {
                eval $statusCallback {"Target current reached."}
                break
            }
        }

        # read the BBCM to get bucket levels
        eval $statusCallback {"Reading BB CM..."}
        set tmpRoot /tmp/[APSTmpString]
        APSAddToTmpFileList -ID LevelBunchCharge -fileList "$tmpRoot.1 $tmpRoot.bpm $tmpRoot.other"
        if [catch {APSSRReadBunchCM -output $tmpRoot.1 -average $BBCMAverages -useFillPattern 1} result] {
            return -code error "DoIOCBucketFilling: $result"
        }
        
        eval $statusCallback {"Analyzing..."}
        # Figure out which buckets need more charge
        set bpmGroupBunchCurrent [expr (1.0*$bpmGroupCurrent)/$bpmGroupBuckets]
        set otherBunchCurrent [expr (1.0*$desiredCurrent-$bpmGroupCurrent)/$otherBuckets]
        if [catch {exec sddsprocess $tmpRoot.1 $tmpRoot.bpm -nowarning \
                     -filter=col,Bucket,-0.5,[expr $bpmGroupBuckets-0.5] \
                     "-define=column,DesiredCurrent,$bpmGroupBunchCurrent Mt:S:FillPatternWFMean *,units=mA"
            exec sddsprocess $tmpRoot.1 $tmpRoot.other -nowarning \
                     -filter=col,Bucket,-0.5,[expr $bpmGroupBuckets-0.5],! \
                     "-define=column,DesiredCurrent,$otherBunchCurrent Mt:S:FillPatternWFMean *,units=mA"
            exec sddscombine -merge $tmpRoot.bpm $tmpRoot.other -pipe=out \
                     | sddsprocess -pipe -nowarning \
                     "-define=parameter,TotalDeficit,$desiredCurrent S35DCCTMean -,units=mA" \
                     -filter=parameter,TotalDeficit,$deficitThreshold,305 \
                     "-define=column,Deficit,DesiredCurrent BunchCurrent -" \
                     -filter=column,Deficit,$deficitThreshold,100 \
                     | sddssort -pipe -column=Deficit,decr \
                     | sddsprocess -pipe -fclip=0,0.5 -nowarning \
                     | sdds2stream -pipe -column=Bucket} bucketList0] {
            return -code error "DoIOCBucketFilling: $bucketList0"
        }
        set bucketList ""
        foreach item $bucketList0 {
            lappend bucketList [string trim $item]
        }
        if [llength $bucketList]==0 {
            eval $statusCallback {"No buckets within threshold"}
            break
        }

        if $InjectionAbort {
            eval $statusCallback {"Injection aborted."}
            break
        }
        eval $statusCallback {"Filling buckets: $bucketList"}
        if [catch {DoIOCBucketFilling -bucketList $bucketList -stopAt $desiredCurrent \
                     -manageLinacBunches $manageLinacBunches -statusCallback $statusCallback \
                     -cycles $cycles -doubleFirst $doubleFirst} result] {
            return -code error "DoIOCBucketFilling: $result"
        }
        if !$result break
    }
    APSDeleteTmpFileList -ID LevelBunchCharge
    TerminateInjection -keepWarm 0 -manageLinacBunches $manageLinacBunches
    eval $statusCallback {"Done leveling."}
}

proc DoIOCBucketFilling {args} {
    global InjectionAbort apsBunchTrainTclVarList
    global errorCode 

    if [catch {APSBunchTrainConnect} result] {
        return -code error "DoIOCBucketFilling: $result"
    }
    eval global $apsBunchTrainTclVarList

    set bucketList ""
    set statusCallback APSNoOp
    set stopAt 0
    set stopMode Estop
    set manageLinacBunches 0
    set doubleFirst 1
    APSStrictParseArguments {bucketList statusCallback stopAt stopMode manageLinacBunches doubleFirst}

    if {$stopAt<0} { set stopAt 0 }
    if {$stopAt>=95} {
        set stopMode Estop
    }
    if ![llength $bucketList] return

    set loopTime 200

    if $doubleFirst {
        # double-up the first bucket as it never gets in the first time
        set bucketList [concat [lindex $bucketList 0]  $bucketList ]
    }
    # set bunch count and zero out the pattern (just to be pretty)
    if [catch {exec cavput -pend=15 \
                 -list=Mt:SRinjectNumBunchesAO=[llength $bucketList],Mt:SRinjectZeroBucketsBO=1} result] {
        return -code error "DoIOCBucketFilling: $result"
    }
    # set the bucket pattern
    set offset 0
    foreach item $bucketList {
        lappend optionList [format %03ld $offset]=[expr $item%1296]
        incr offset
    }
    if [catch {exec cavput -list=Mt:SRbunch -list=[join $optionList ,]} result] {
        return -code error "DoIOCBucketFilling: $result"
    }

    # set stop value and mode, set for 1 cycle
    if [catch {exec cavput -pend=10 -list=Mt:SR \
                 -list=injectCurrentLimitAO=$stopAt,injectMaxCyclesAO=1,injectModeBO=Single,CurrentLimitModeBO=$stopMode } result] {
        return  -code error "DoIOCBucketFilling: Error setting IOC parameters: $result"
    }
    
    # start injection
    set apsBunchTrainMBInject On
    if [pv putw apsBunchTrainMBInject] {
        return  -code error "DoIOCBucketFilling: $errorCode"
    }
    after 1000

    # loop to watch injection and respond to abort request
    set updateInterval [expr int(2000.0/$loopTime+1)]
    set updateCount $updateInterval
    while {!$InjectionAbort} {
        after $loopTime
        if [pv getw {apsBunchTrainMBStatus S35BeamCurrent mpsStatus}] {
            eval $statusCallback {"$errorCode"}
            continue
        }
        set apsBunchTrainMBStatus $apsBunchTrainMBStatus
        set S35BeamCurrent $S35BeamCurrent
        set mpsStatus $mpsStatus
        if {[string compare $apsBunchTrainMBStatus "Injecting"]} {
            eval $statusCallback {"Injection cycle completed."}
            TerminateInjection -keepWarm 0 -manageLinacBunches $manageLinacBunches
            return 1
        }
        if !$mpsStatus {
            eval $statusCallback {"Injection aborted due to MPS trip."}
            set InjectionAbort 1
            TerminateInjection -keepWarm 0 -manageLinacBunches $manageLinacBunches
            return 0
        }
        if {$S35BeamCurrent>$stopAt} {
            eval $statusCallback {"Current target reached."}
            TerminateInjection -keepWarm 0 -manageLinacBunches $manageLinacBunches
            return 1
        }
        if !$updateCount {
            eval $statusCallback {"Now at [format %.2f $S35BeamCurrent] mA (sum of [format %.1f $apsBPMGroupSum])"}
            set updateCount $updateInterval
        } else {
            incr updateCount -1
        }
    }

    TerminateInjection -keepWarm 0 -manageLinacBunches $manageLinacBunches
    return 0
}

proc RestartIOCBunchTrainInjection {args} {
    set stopAt 102.0
    set useSum DCCT
    set stopAtSum 0
    set callback APSNoOp
    set keepWarm 0
    set manageLinacBunches 0
    APSStrictParseArguments {stopAt useSum stopAtSum callback keepWarm manageLinacBunches}
    global BunchDone InjectionAbort KickersOn apsBunchTrainTclVarList
    global errorCode apsBunchTrainConnected debugMode S35BeamCurrent 
    global apsBunchTrainStopMode
    eval global $apsBunchTrainTclVarList

    set loopTime 200

    if [catch {exec cavget -list=Mt:SRlastInjectedBunchM -pend=5} lastBunch] {
        return -code error "RestartIOCBunchTrainInjection: $lastBunch"
    }
    if $lastBunch==0 return
    if [catch {exec cavget -list=Mt:SRbunch -range=begin=0,end=1295,format=%03d -pend=30} bucketList] {
        return -code error "RestartIOCBunchTrainInjection: $bucketList"
    }
    if [catch {exec cavget -list=Mt:SRinjectNumBunchesAO.VAL} listLength] {
        return -code error "RestartIOCBunchTrainInjection: $listLength"
    }
    set bucketList [lrange $bucketList 0 [expr $listLength-1]]
    if [set lastIndex [lsearch -exact $bucketList $lastBunch]]==-1 {
        return -code error "RestartIOCBunchTrainInjection: last bunch $lastBunch is not in the IOC's bucket list!"
    }
    set index [expr $lastIndex+1]
    set newIndex 0
    set newList ""
    for {set index [expr $lastIndex+1]} {$index<[llength $bucketList]} {incr index} {
        lappend newList [format %03d $newIndex]=[lindex $bucketList $index]
        incr newIndex
    }
    for {set index 0} {$index<=$lastIndex} {incr index} {
        lappend newList [format %03d $newIndex]=[lindex $bucketList $index]
        incr newIndex
    }
    if [catch {exec cavput -list=Mt:SRbunch -list=[join $newList ,] -pend=30} result] {
        return -code error "RestartIOCBunchTrainInjection: $result"
    }

    set apsBunchTrainIS1Trig 1
    set apsPCGunTrigger 1
    set apsRFGunKickerTrigger 1
    if [pv putw {apsBunchTrainIS1Trig \
                   apsRFGunKickerTrigger \
                   apsPCGunTrigger}] {
        return -code error "DoIOCBunchTrainInjection: $errorCode"
    }
    after 1000

    set apsBunchTrainMBInject On
    if [pv putw apsBunchTrainMBInject] {
        return  -code error "DoIOCBunchTrainInjection: $errorCode"
    }
    after 1000

    set updateInterval [expr int(2000.0/$loopTime+1)]
    set updateCount $updateInterval
    set returnValue 0
    while {!$InjectionAbort} {
        after $loopTime
        update
        if [pv getw {apsBunchTrainMBStatus S35BeamCurrent mpsStatus}] {
            eval $callback {"$errorCode"}
        }
        set apsBunchTrainMBStatus $apsBunchTrainMBStatus
        set S35BeamCurrent $S35BeamCurrent
        set mpsStatus $mpsStatus
        if {[string match $apsBunchTrainMBStatus "Ready"]} {
            eval $callback {"Injection externally terminated: Value of Mt:SRinjectStatMBBI is $apsBunchTrainMBStatus."}
            break
        }
        if !$mpsStatus {
            eval $callback {"Injection aborted due to MPS trip."}
            set InjectionAbort 1
            break
        }
        if {$S35BeamCurrent>$stopAt} {
            eval $callback {"Current target reached."}
            break
        }
        if {$useSum=="Sum"} {
            if [pv getw apsBPMGroupSum] {
                eval $callback {"$errorCode"}
            } else {
                set apsBPMGroupSum $apsBPMGroupSum
                if {$apsBPMGroupSum>$stopAtSum} {
                    eval $callback {"Sum signal target reached."}
                    break
                } else {
                    eval $callback {"Sum signal target not reached. Continuing..."}
                }
            }
        }
            
        if !$updateCount {
            if {$useSum!="Sum"} {
                eval $callback {"Now at [format %.2f $S35BeamCurrent] mA"}
            } else {
                eval $callback {"Now at [format %.2f $S35BeamCurrent] mA (sum of [format %.1f $apsBPMGroupSum])"}
            }
            set updateCount $updateInterval
        } else {
            incr updateCount -1
        }
    }
    if !$InjectionAbort {
        set returnValue 1
    }
    TerminateInjection -keepWarm $keepWarm -manageLinacBunches $manageLinacBunches
    return $returnValue
}
