
proc APSSRTakeLifetimeDataSeries {args} {
    set buttonWidget ""
    set minCurrentChange 0.2
    set maxCurrentChange 1
    set maxTime 300
    set minTime 10
    set timeInterval 0.5
    set takeTunes 1
    set takeOrbit 1
    set takeBunchPattern 1
    set takeSRVSA 1
    set rootname ""
    set comment ""
    set sets 1
    set timeBetweenSets 60
    set abortVariable ""
    set statusCallback APSNoOp
    set plot 0
    set PVToVary ""
    set PVToVaryStart 0
    set PVToVaryEnd 0
    set geometricVariation 0
    set postChangePause 0
    APSStrictParseArguments {buttonWidget minCurrentChange maxCurrentChange \
                               maxTime minTime timeInterval geometricVariation \
                               takeTunes takeOrbit takeBunchPattern \
                               rootname comment sets takeSRVSA \
                               timeBetweenSets abortVariable statusCallback plot \
                               PVToVary PVToVaryStart PVToVaryEnd geometricVariation postChangePause}

    if {$maxTime<$minTime || $timeInterval<0 || ![string length $rootname] || \
        $minCurrentChange>$maxCurrentChange || \
        $sets<1 || $timeBetweenSets<0} {
        return -code error "APSSRTakeLifetimeDataSeries: bad argument values"
    }
    
    if [string length $buttonWidget] {
        APSDisableButton $buttonWidget
    }
    if [string length $abortVariable] {
        global $abortVariable
        set $abortVariable 0
    }

    set fileIndex 0
    set existingFileList [lsort [glob -nocomplain $rootname.\[0-9\]\[0-9\]\[0-9\].monData]]
    if [llength $existingFileList] {
        # get the last generation number 
        scan [lindex $existingFileList end] $rootname.%ld.monData fileIndex
        incr fileIndex
    }

    if [string length $PVToVary] {
        if $geometricVariation {
            if {[expr $PVToVaryStart*$PVToVaryEnd<=0]} {
                return -code error "APSSRTakeLifetimeDataSeries: for geometric variation, both start and end values must be non-zero and have the same sign"
            }
            if [expr $sets<2] {
                return -code error "APSSRTakeLifetimeDataSeries: for geometric variation, must request at least two sets"
            }
            set PVFactor [expr pow((1.0*$PVToVaryEnd)/$PVToVaryStart, 1./($sets-1.0))]
            set PVDelta 0
        } else {
            set PVDelta 0
            set PVFactor 1
            if $sets>1 {
                set PVDelta [expr ($PVToVaryEnd-$PVToVaryStart)/($sets-1.0)]
            } 
        }
        set PVValue $PVToVaryStart
        switch $PVToVary {
            RfVoltageS36S37 {
                if [catch {exec cavget -list=SRF:S -list=36,37 -list=:cavTotalGV:SetptAO} resultList] {
                    return -code error "$resultList"
                }
                set totalVoltage 0
                foreach result $resultList {
                    set totalVoltage [expr $totalVoltage+$result]
                }
                set PVOrigValue [expr $totalVoltage/2.0]
            }
            RfVoltage {
                if [catch {exec cavget -list=SRF:S -list=36,37,40 -list=:cavTotalGV:SetptAO} resultList] {
                    return -code error "$resultList"
                }
                set totalVoltage 0
                foreach result $resultList {
                    set totalVoltage [expr $totalVoltage+$result]
                }
                set PVOrigValue [expr $totalVoltage/3.0]
            }
            default {
                if [catch {exec cavget -list=$PVToVary} PVOrigValue] {
                    if [string length $buttonWidget] {
                        APSEnableButton $buttonWidget
                    }
                    return -code error "APSSRTakeLifetimeDataSeries: $PVOrigValue"
                }
            }
        }
    }
    for {set dataset 1} {$dataset<=$sets} {incr dataset; incr fileIndex} {
        if [string length $PVToVary] {
            switch $PVToVary {
                RfVoltage {
                    exec cavput -list=SRF:S -list=36,37,40 -list=:cavTotalGV:SetptAO=[expr $PVValue/3.0]
                }
                RfVoltageS36S37 {
                    exec cavput -list=SRF:S -list=36,37 -list=:cavTotalGV:SetptAO=[expr $PVValue/2.0]
                }
                default {
                    if [catch {exec cavput -list=$PVToVary=$PVValue} result] {
                        if [string length $buttonWidget] {
                            APSEnableButton $buttonWidget
                        }
                        catch {exec cavput -list=$PVToVary=$PVOrigValue}
                        return -code error "APSSRTakeLifetimeDataSeries: $result"
                    }
                }
            }
            set PVValueNow $PVValue
            if $geometricVariation {
                set PVValue [expr $PVToVaryStart*pow($PVFactor, $dataset)]
            } else {
                set PVValue [expr $PVValue+$PVDelta]
            }
            after [expr int($postChangePause*1000)]
        }
        set basename [format $rootname.%03ld $fileIndex]
        eval $statusCallback {"Taking set $dataset in files $basename.*"}
        if [catch {APSSRTakeLifetimeData -rootname $basename \
                     -minTime $minTime -maxTime $maxTime \
                     -minCurrentChange $minCurrentChange \
                     -maxCurrentChange $maxCurrentChange \
                     -takeTunes $takeTunes -takeOrbit $takeOrbit \
                     -takeBunchPattern $takeBunchPattern \
                     -timeInterval $timeInterval \
                     -abortVariable $abortVariable \
                     -PVToVary $PVToVary \
                     -statusCallback $statusCallback \
                     -plot $plot -comment $comment} result] {
            if [string length $buttonWidget] {
                APSEnableButton $buttonWidget
            }
            catch {exec cavput -list=$PVToVary=$PVOrigValue}
            return -code error "APSSRTakeLifetimeDataSeries: $result"
        }
        eval $statusCallback {"Done with set $dataset"}
        if [string length $PVToVary] {
            if [catch {exec sddsprocess $basename.monData -nowarning -define=column,$PVToVary,$PVValueNow} result] {
                eval $statusCallback {"Warning: problem setting $PVToVary in output file $basename.monData: $result."}
            } else {
                catch {file delete -force $basename.monData~}
            }
            if [catch {exec sddsprocess $basename.ltfit -nowarning -define=param,$PVToVary,$PVValueNow} result] {
                eval $statusCallback {"Warning: problem setting $PVToVary in output file $basename.ltfit: $result."}
            } else {
                catch {file delete -force $basename.ltfit~}
            }
        }
        if {$timeBetweenSets && $dataset!=$sets} {
            eval $statusCallback {"Waiting $timeBetweenSets seconds"}
            APSWaitWithUpdate -waitSeconds $timeBetweenSets -updateInterval 1 \
              -abortVariable $abortVariable
        }
        if {[string length $abortVariable] && [set $abortVariable]} {
            break
        }
    }
    if {[string length $abortVariable] && [set $abortVariable]} {
        eval $statusCallback {"Measurement aborted."}
    } else {
        eval $statusCallback {"Measurement done."}
    }
    if [string length $buttonWidget] {
        APSEnableButton $buttonWidget
    }
    if [string length $PVToVary] {
        if [string compare $PVToVary RfVoltage]==0 {
            exec cavput -list=SRF:S -list=36,37,40 -list=:cavTotalGV:SetptAO=$PVOrigValue
        } else {
            catch {exec cavput -list=$PVToVary=$PVOrigValue -ramp=steps=10,pause=1}
        }
    }
}

proc APSSRTakeLifetimeData {args} {
    set rootname ""
    set minCurrentChange 0.2
    set maxCurrentChange 1
    set maxTime 300
    set minTime 10
    set timeInterval 0.5
    set takeTunes 1
    set takeOrbit 1
    set takeBunchPattern 1
    set takeSRVSA 0
    set abortVariable ""
    set plot 0
    set statusCallback APSNoOp
    set PVToVary ""
    set comment ""
    APSStrictParseArguments {rootname minCurrentChange maxCurrentChange maxTime minTime \
                               timeInterval takeTunes takeOrbit takeBunchPattern \
                               abortVariable statusCallback plot comment takeSRVSA PVToVary}
    set inputDir /home/helios/oagData/sr/sddsmonitorFiles/lifetimeDataCollection

    if [string length $abortVariable] {
        global $abortVariable 
    } else {
        set abortVariable abortDummy
        set abortDummy 0
    }

    if {$maxTime<$minTime || $timeInterval<0 || ![string length $rootname] || \
        $minCurrentChange>$maxCurrentChange} {
        return -code error "APSSRTakeLifetimeData: bad argument values"
    }

    if {[pv linkw SDCCT S-DCCT:CurrentM]!=0} {
        return -code error "APSSRTakeLifetimeData: Unable to connect to DCCT"
    }

    set timeStart [clock seconds]
    set timeEnd [expr $timeStart+$maxTime]
    set timeMustReach [expr $timeStart+$minTime]
    global SDCCT
    if [pv getw SDCCT] {
        return -code error "APSSRTakeLifetimeData: Unable to read DCCT"
    }
    set currentStart [set SDCCT $SDCCT]
    set currentEnd [expr $currentStart - $maxCurrentChange]
    if {$currentEnd<0} {
        set currentEnd 0
    }
    set currentMustReach [expr $currentStart - $minCurrentChange]
    if {$currentMustReach<0} {
        set currentMustReach 0
    }
    set smPID [exec sddsmonitor $inputDir/lifetime.mon "-comment=Comment,$comment" \
                 $rootname.monData -interval=$timeInterval,s \
                 -time=$maxTime,s -precision=single -enforceTimeLimit &]
    if $takeTunes {
        #if [catch {APSMpMeasureTunes -ring SR -averagingSeconds -1} result] {
        #    return -code error "APSSRTakeLifetimeData: Unable to trigger tune measurement: $result"
        #}
    }
    if $takeOrbit {
        if [catch {exec sddsmonitor -step=1 $inputDir/orbit.mon "-comment=Comment,$comment" \
                     $rootname.orbit -precision=single} result] {
            return -code error "APSSRTakeLifetimeData: Unable to read orbit: $result"
        }
    }
    if $takeBunchPattern {
        if ![catch {APSSRReadBunchCM -output $rootname.bpat} result] {
            if $plot {
                exec sddsplot -column=Bucket,BunchCurrent $rootname.bpat &
            }
        }
    }
    while {![set $abortVariable]} {
        set timeNow [clock seconds]
        if [pv getw SDCCT] {
            catch {exec kill -9 $smPID}
            return -code error "APSSRTakeLifetimeData: Unable to read DCCT"
        }
        set current [set SDCCT $SDCCT]
        eval $statusCallback {"[format %.2f $current] ([format %.2f $currentMustReach]) mA after [format %.0f [expr $timeNow-$timeStart]]s"}
        if {$current<$currentEnd} {
            eval $statusCallback {"Maximum desired current loss seen."}
            break
        }
        if {$timeNow>$timeEnd} {
            eval $statusCallback {"Maximum desired elapsed time has passed."}
            break
        }
        if {$timeNow>$timeMustReach && $current<$currentMustReach} {
            eval $statusCallback {"Minimum desired current loss and duration reached."}
            break
        }
        after 1000
    }
    catch {exec kill -9 $smPID}
    if $takeTunes {
        eval $statusCallback {"Can't collect tune data at present"}
        #if [catch {APSMpMeasureTunes -ring SR -averagingSeconds -1 \
            #         -useExistingData 1 -plotData $plot \
            #         -outputFile $rootname.tunes} result] {
        #return -code error "APSSRTakeLifetimeData: Unable to get tune data: $result"
        #}
    }
    if $takeSRVSA {
        eval $statusCallback {"No longer collecting VSA data"}
        #if [catch {exec hpVecTrace -hpvecsr a $rootname.vsa} result] {
        #    return -code error "APSSRTakeLifetimeData: Unable to get VSA data: $result"
        #}
    }
    if [catch {APSSRProcessLifetimeData -input $rootname.monData -output $rootname.ltfit} result] {
        return -code error "APSSRTakeLifetimeData: $result"
    }
    if $plot {
        exec sddsplot -column=TimeElapsed,lnSDCCT $rootname.ltfit \
            -graph=dot -topline=@LifetimeLabel -title=@Comment \
            -column=TimeElapsed,lnSDCCTFit -graph=line,type=1 $rootname.ltfit &
    }
}

proc APSSRProcessLifetimeData {args} {
    set input ""
    set output ""
    set PVToVary "" 
    APSStrictParseArguments {input output PVToVary}

    if ![file exists $input] {
        return -code error "APSSRProcessLifetimeData: not found: $input"
    }
    set tmpFile /tmp/[APSTmpString]
    if ![string length $PVToVary] {
        if [catch {exec sddsprocess $input $tmpFile \
                     -process=Time,first,Time0 \
                     "-define=column,TimeElapsed,Time Time0 -,units=s" \
                     "-define=column,lnSDCCT,SDCCT ln" \
                     "-redefine=column,SRBucketsFilled,SRBucketsFilled 0 > ? SRBucketsFilled : 1 $ " \
                     "-define=column,BunchCurrent,SDCCT SRBucketsFilled /,units=mA" \
                     -process=SDCCT,ave,SDCCTMean \
                     -process=BunchCurrent,ave,BunchCurrentMean \
                     -process=*HCollimator*,ave,%sMean \
                     -process=*Voltage*,ave,%sMean 
            exec sddsconvert $tmpFile -pipe=out -retain=column,TimeElapsed,lnSDCCT \
                       | sddspfit -pipe -generate \
                       -terms=2 -columns=TimeElapsed,lnSDCCT \
                       | sddsxref -pipe $tmpFile -leave=* \
                       -transfer=param,SDCCTMean,Comment,BunchCurrentMean,*HCollimator*Mean \
                       | sddsprocess -pipe=in $output \
                       "-define=parameter,Rate,Slope -3600 *,units=1/h" \
                       "-define=parameter,RateSigma,SlopeSigma 3600 *,units=1/h" \
                       "-define=parameter,Lifetime,Slope rec chs 3600 /,units=h" \
                       "-define=parameter,LifetimeSigma,SlopeSigma Slope sqr / 3600 /,units=h" \
                       "-define=parameter,LifetimeTimesCurrent,Lifetime SDCCTMean *,units=mA-h" \
                       "-define=parameter,LifetimeTimesCurrentSigma,LifetimeSigma SDCCTMean *,units=mA-h" \
                       "-print=parameter,LifetimeLabel,%.3f +/- %.3f h   %.3f +/- %.3f mA-h,Lifetime,LifetimeSigma,LifetimeTimesCurrent,LifetimeTimesCurrentSigma"} result] {
            catch {exec rm $tmpFile}
            return -code error "APSSRProcessLifetimeData: Lifetime data processing error: $result"
        }
    } else {
        if [catch {exec sddsprocess $input $tmpFile \
                       -process=Time,first,Time0 \
                       -process=$PVToVary,first,$PVToVary \
                       "-define=column,TimeElapsed,Time Time0 -,units=s" \
                       "-define=column,lnSDCCT,SDCCT ln" \
                       "-redefine=column,SRBucketsFilled,SRBucketsFilled 0 > ? SRBucketsFilled : 1 $ " \
                       "-define=column,BunchCurrent,SDCCT SRBucketsFilled /,units=mA" \
                       -process=SDCCT,ave,SDCCTMean \
                       -process=BunchCurrent,ave,BunchCurrentMean \
                       -process=*HCollimator*,ave,%sMean \
                       -process=*Voltage*,ave,%sMean 
            exec sddsconvert $tmpFile -pipe=out -retain=column,TimeElapsed,lnSDCCT \
                       | sddspfit -pipe -generate \
                       -terms=2 -columns=TimeElapsed,lnSDCCT \
                       | sddsxref -pipe $tmpFile -leave=* \
                       -transfer=param,SDCCTMean,Comment,BunchCurrentMean,*Mean,$PVToVary \
                       | sddsprocess -pipe=in $output \
                       "-define=parameter,Rate,Slope -3600 *,units=1/h" \
                       "-define=parameter,RateSigma,SlopeSigma 3600 *,units=1/h" \
                       "-define=parameter,Lifetime,Slope rec chs 3600 /,units=h" \
                       "-define=parameter,LifetimeSigma,SlopeSigma Slope sqr / 3600 /,units=h" \
                       "-define=parameter,LifetimeTimesCurrent,Lifetime SDCCTMean *,units=mA-h" \
                       "-define=parameter,LifetimeTimesCurrentSigma,LifetimeSigma SDCCTMean *,units=mA-h" \
                       "-print=parameter,LifetimeLabel,%.3f +/- %.3f h   %.3f +/- %.3f mA-h,Lifetime,LifetimeSigma,LifetimeTimesCurrent,LifetimeTimesCurrentSigma"} result] {
            catch {exec rm $tmpFile}
            return -code error "APSSRProcessLifetimeData: Lifetime data processing error: $result"
        }
    }
}
