#!/bin/sh
# \
exec oagwish -f "$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

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

set abortRequested 0
set debug 0

set statusText ""
proc SetStatus {text} {
    global statusText
    set statusText "[clock format [clock seconds] -format %T]: $text"
    update
}

set pattern0 0
set pattern1 0,2,4,6
set longTermLimit 8
set interval0 10
set interval1 10
set timeLimit 1
set countDown ?
set efficiency0 ?
set efficiency1 ?
set averageCurrent ?
set dumpPattern0 No
set toggleRCList ""
set logRoot ""
set plot 0

proc APSPrepareLinacRunPatternSwitchingLogFile {args} {
    set logRoot ""
    APSStrictParseArguments {logRoot}

    set filename $logRoot-[clock format [clock seconds] -format %Y-%m-%d-%T]
    set fdl [open $filename a]
    puts $fdl "SDDS1"
    puts $fdl "&column name=Time type=long &end"
    puts $fdl "&column name=LTP2PTBEfficiency0 units=% type=float &end"
    puts $fdl "&column name=LTP2PTBEfficiency1 units=% type=float &end"
    puts $fdl "&column name=LTPAverageCurrent units=nA type=float &end"
    puts $fdl "&data mode=ascii no_row_counts=1 &end"
    flush $fdl
    return [list $filename $fdl]
}

proc APSLinacRunPatternSwitching {args} {
    set pattern0 ""
    set pattern1 ""
    set longTermLimit 8
    set interval0 10
    set interval1 10
    set timeLimit 1
    set dumpPattern0 No
    set logRoot ""
    set plot 0
    set toggleRCList ""
    APSStrictParseArguments {pattern0 pattern1 longTermLimit interval0 interval1 timeLimit dumpPattern0 logRoot plot toggleRCList}

    if {[string length $logRoot] && ![file exists [file dirname $logRoot]]} {
        bell
        SetStatus "Log file directory doesn't exist"
        return
    }

    set runControlPV APS:RunControlSlot4RC
    set status [APSRunControlInit -pv $runControlPV -description "Modulate Linac Bunch Pattern" -timeout 10000]
    if [string compare $status "RUNCONTROL_OK"] {
        bell
        SetStatus "Run control problem: $status"
        return 
    }

    # Global variables related to PVs. Should give these apsLinac... names
    global bunchEnabled bunchVarList injectorCycleRate LTPCharge PTBCharge 
    global BSPChargeEnable PSPChargeEnable Efficiency0 Efficiency1 LTPAverageCurrent
    global apsLinacPatternSwitchingConnected

    if $apsLinacPatternSwitchingConnected==0 {
        SetStatus "PV connection problem. Try restarting this application."
        return
    }

    # Global variables related to updating the GUI. Should move these to a service routine
    global efficiency0 efficiency1 countDown averageCurrent abortRequested 
    set efficiency0 ?
    set efficiency1 ?
    set countDown ?
    set averageCurrent ?

    # parse and validate the first pattern
    set patternList(0) ""
    foreach item [split $pattern0 " ,"] {
        # Pattern is a list of even integers: [0, 58]
        if {[scan $item "%d" index]!=1 || [expr $index<0] || [expr $index>60] || [expr $index%2==1]} {
            SetStatus "Error: pattern $pattern0 has invalid number $item"
            SetStatus "Use comma- or space-separated list of even numbers on [0,58]"
            return 
        }
        if [lsearch -exact $patternList(0) $item]==-1 {
            lappend patternList(0) $item
        } else {
            SetStatus "Error: duplicate bunch in pattern 0: $item"
            return
        }
    }
    # parse and validate the second pattern
    set patternList(1) ""
    foreach item [split $pattern1 " ,"] {
        # Pattern is a list of even integers: [0, 58]
        if {[scan $item "%d" index]!=1 || [expr $index<0] || [expr $index>60] || [expr $index%2==1]} {
            SetStatus "Error: pattern $pattern1 has invalid number $item"
            SetStatus "Use comma- or space-separated list of even numbers on [0,58]"
            return 
        }
        if [lsearch -exact $patternList(1) $item]==-1 {
            lappend patternList(1) $item
        } else {
            SetStatus "Error: duplicate bunch in pattern 1: $item"
            return
        }
    }

    # Read the injector cycle rate
    if {[pv getw injectorCycleRate]!=0 || [scan $injectorCycleRate %ld initialCycleRate]!=1} {
        SetStatus "Error: problem reading injector cycle rate."
        return
    }
    # Compute the number of pulses per second on average
    set interval0 [expr int($interval0)]
    set interval1 [expr int($interval1)]
    if [expr ([llength $patternList(0)]*$interval0+[llength $patternList(1)]*$interval1)*$initialCycleRate/($interval0+$interval1*1.0)>$longTermLimit] {
        SetStatus "Error: too many pulses enabled for long term limit of $longTermLimit pulses/second"
        return
    }
    if {[expr $interval0<2] || [expr $interval1]<2} {
        SetStatus "Error: intervals must be at least 2 seconds"
        return
    }
    set interval(0) $interval0
    set interval(1) $interval1

    set logFile ""
    set fdl ""
    if [string length $logRoot] {
        set list1 [APSPrepareLinacRunPatternSwitchingLogFile -logRoot $logRoot]
        set logFile [lindex $list1 0]
        set fdl [lindex $list1 1]
        SetStatus "Using log file $logFile"
    }

    set nRCRecords 0
    if [string length $toggleRCList] {
        set RCPVLlist ""
        set RCVarList ""
        set index 0
        foreach item [split $toggleRCList " ,"] {
            lappend RCPVList $item.SUSP
            lappend RCVarList rcControl($index)
            incr index
        }
        if [pv linkw $RCVarList $RCPVList]!=0 {
            bell
            SetStatus "Warning: Problem linking to run control PVs."
            bell
            SetStatus "Ensure that records exist. Don't include field in PV name."
        } else {
            set nRCRecords $index
        }
    }

    set endTime [expr [clock seconds] + $timeLimit*3600]
    set abortRequested 0
    # The state variable toggles between 0 and 1, corresponding to the pattern we are using
    set state 0
    set LTPTotalSum 0
    set LTPTotalCount 0
    while {[expr [clock seconds]<$endTime] && !$abortRequested} {
        catch {APSRunControlPing} status
        if [string compare $status "RUNCONTROL_OK"] {
            bell
            SetStatus "Terminating due to run control: $status"
            set abortRequested 1
            break
        }
        SetStatus "Switching to pattern $state: $patternList($state)"
        set BSPChargeEnable 1
        set PSPChargeEnable 1
        if [string compare $dumpPattern0 "No"] {
            SetStatus "Changing septum state"
            if [string compare $dumpPattern0 "Booster"]==0 {
                set BSPChargeEnable $state
            } else {
                set PSPChargeEnable $state
            }
        }
        if [pv putw {PSPChargeEnable BSPChargeEnable}]!=0 {
            SetStatus "Error: problem settiing PAR/Booster septum charge enable PV."
            set abortRequested 1
            break
        }
        if $nRCRecords {
            SetStatus "Toggling related run control records"
            for {set i 0} {$i<$nRCRecords} {incr i} {
                set rcControl($i) $state
            }
            if [pv putw $RCVarList]!=0 {
                bell
                SetStatus "Warning: problem manipulating run control PVs from your list."
            }
        }
        # Check whether the injector cycle rate has been changed. If so, we abort
        if {[pv getw injectorCycleRate]!=0 || [scan $injectorCycleRate %ld cycleRate]!=1} {
            SetStatus "Error: problem reading injector cycle rate."
            set abortRequested 1
            break
        }
        if [expr $initialCycleRate!=$cycleRate] {
            SetStatus "Error: injector cycle rate changed from $initialCycleRate Hz to $cycleRate Hz"
            set abortRequested 1
            break
        }
        # Set bunch enables to 0 for all slots, just to be sure
        for {set b 0} {$b<60} {incr b 2} {
            set bunchEnabled($b) 0
        }
        # Set the requested bunch slots to on
        foreach b $patternList($state) {
            set bunchEnabled($b) 1
        }
        # Write the pattern
        SetStatus "Writing timing pattern"
        if {[pv putw $bunchVarList]!=0} {
            SetStatus "Error: channel access error setting bunch pattern."
            set abortRequested 1
            break
        }
        SetStatus "Done writing timing pattern"
        # Wait for indicated number of seconds. Take LTP and PTB data to allow computing the 
        # efficiency while we wait
        set secondsLeft $interval($state)
        set samples 0
        set PTBSum 0
        set LTPSum 0
        # Number of samples to skip to avoid end effects messing up the numbers.
        set holdOff [expr int(3*$initialCycleRate+0.5)]
        SetStatus "Beginning countdown, hold off of $holdOff"
        while {[expr $secondsLeft>0]} {
            if $abortRequested break
            catch {APSRunControlPing} status
            if [string compare $status "RUNCONTROL_OK"] {
                bell
                SetStatus "Terminating due to run control: $status"
                set abortRequested 1
                break
            }
            set countDown $secondsLeft 
            update
            after 1000
            # This is used for the 
            if [pv getw {PTBCharge LTPCharge}]!=0 {
                SetStatus "Warning: problem acquiring data from PTB and LTB CMs!"
                bell
            }
            set LTPTotalSum [expr $LTPTotalSum+$LTPCharge]
            incr LTPTotalCount
            if !$holdOff {
                # Hold off taking efficiency data to avoid end effects
                set LTPSum [expr $LTPSum+$LTPCharge]
                set PTBSum [expr $PTBSum+$PTBCharge]
                incr samples
            } else {
                incr holdOff -1
            }
            incr secondsLeft -1
        }
        SetStatus "Finishing with pattern $state"
        if {$samples!=0 && [expr $LTPSum>0]} {
            set efficiency [expr 100*$PTBSum/$LTPSum]
            if $state {
                set efficiency1 [format %.2f $efficiency]
                set Efficiency1 $efficiency1
            } else {
                set efficiency0 [format %.2f $efficiency]
                set Efficiency0 $efficiency0
            }
        } else {
            if $state {
                set efficiency1 ?
                set Efficiency1 0
            } else {
                set efficiency0 ?
                set Efficiency0 0
            }
        }
        if [expr $LTPTotalCount!=0] {
            set averageCurrent [expr $LTPTotalSum/(1.0*$LTPTotalCount)*$initialCycleRate]
            set LTPAverageCurrent $averageCurrent
            if [expr $averageCurrent>8] {
                bell
                SetStatus "Average current $averageCurrent exceeds 8 nA"
            }
        } else {
            set averageCurrent ?
            set LTPAverageCurrent 0
        }
        if [pv putw {Efficiency0 Efficiency1 LTPAverageCurrent}]!=0 {
            SetStatus "Problem seting values for the sddspcas PVs"
        }
        # Prepare to go to the next state
        set state [expr !$state]
        set countDown 0
        update
        if {$state==0 && [string length $logFile]} {
            SetStatus "Writing to log file"
            puts $fdl "[clock seconds] $efficiency0 $efficiency1 $averageCurrent"
            flush $fdl
            if $plot {
                exec sddsplot "-device=motif,-movie true -keep 1" -repeat=timeout=120 -separate=1 -join=x -ticks=xtime -layout=1,3 \
                  -ylabel=scale=0.75 -column=Time,LTP2PTBEfficiency0 -column=Time,LTP2PTBEfficiency1 \
                    -column=Time,LTPAverageCurrent $logFile &
                set plot 0
            }
        }
    }
    if $abortRequested {
        SetStatus "Stopped by user request or run control"
    } else {
        SetStatus "Stopped since time interval expired."
    }

    if $nRCRecords {
        for {set i 0} {$i<$nRCRecords} {incr i} {
            set rcControl($i) 0
        }
        if [pv putw $RCVarList]!=0 {
            bell
            SetStatus "Warning: problem manipulating run control PVs from your list."
        }
    }

    APSRunControlExit

    APSLinacResetBunchPattern

    SetButtonState -mode stop
}

proc APSLinacResetBunchPattern {} {
    global bunchEnabled bunchVarList

    # Ensure we have only 1 bunch enabled before returning
    for {set b 0} {$b<60} {incr b 2} {
        set bunchEnabled($b) 0
    }
    set bunchEnabled(0) 1
    if {[pv putw $bunchVarList]!=0} {
        SetStatus "Error: channel access error setting bunch pattern."
        return 
    }
}

set apsLinacPatternSwitchingInitialized 0
set apsLinacPatternSwitchingConnected 0
proc APSLinacPatternSwitchingInit {} {
    global apsLinacPatternSwitchingInitialized debug apsLinacPatternSwitchingConnected 

    if !$apsLinacPatternSwitchingInitialized  {
        global bunchEnabled bunchVarList injectorCycleRate LTPCharge PTBCharge 
        global BSPChargeEnable PSPChargeEnable Efficiency0 Efficiency1 AverageCurrent

        set pcasTmpFile /tmp/[APSTmpString]
        exec sddsmakedataset $pcasTmpFile \
            -column=ControlName,type=string -data=MLBP:LTP2PTBEfficiency0,MLBP:LTP2PTBEfficiency1,MLBP:LTPAverageCurrent \
            -column=ReadbackUnits,type=string -data=%,%,nA 
        exec sddspcas $pcasTmpFile -runControlPV=string=APS:RunControlSlot3RC,pingTimeout=10 -standAlone \
            "-runControlDescription=string=sddspcas for modulateLinacBunchPattern" &
        SetStatus "Creating PVs MLBP:LTP2PTBEfficiency0,MLBP:LTP2PTBEfficiency1,MLBP:LTPAverageCurrent"

        set apsLinacPatternSwitchingConnected 0
        # Connect to 30 PVs for enabling even-numbered bunches
        set PVList ""
        set bunchVarList ""
        for {set b 0} {$b<60} {incr b 2} {
            set bunchEnabled($b) 0
            lappend PVList It:L:B[format %02d $b]_enb_bo.VAL
            lappend bunchVarList bunchEnabled($b)
        }
        if {[pv linkw $bunchVarList $PVList]!=0} {
            SetStatus "Problem connecting to bunch pattern PVs!"
            return
        }
        # Connect to injector cycle rate and LTP/PTB charge readings
        if {[pv linkw injectorCycleRate It:L:1HzNot2Hz_bo.VAL]!=0} {
            SetStatus "Problem connecting to injector rate PV!"
            return
        }
        if {[pv linkw {LTPCharge PTBCharge} {LTP:FCM:qTotalAI PTB:CM:qTotalAI}]} {
            SetStatus "Problem connecting to LTP and PTB charge PVs!"
            return
        }
        if {[pv linkw {BSPChargeEnable PSPChargeEnable} {It:Ddg3chan2.GATE It:Par:PSPchargeBO.VAL}]} {
            SetStatus "Problem connecting to Booster kicker charge enable PV!"
            return
        }
        if {[pv linkw {Efficiency0 Efficiency1 LTPAverageCurrent} {MLBP:LTP2PTBEfficiency0 MLBP:LTP2PTBEfficiency1 MLBP:LTPAverageCurrent}]} {
            SetStatus "Problem connecting to sddspcas PVs"
            return
        }
        set apsLinacPatternSwitchingInitialized 1
        set apsLinacPatternSwitchingConnected 1
        SetStatus "Connected to PVs."
    }

}

APSApplication . -name modulateLinacBunchPattern -version $CVSRevisionAuthor \
  -overview {This application modulates the linac bunch pattern between two states with a user-defined duty factor.}

set statusText "Ready."
APSScrolledStatus .record -parent .userFrame -width 100 -height 5 -textVariable statusText -lineLimit 1000 

APSLabeledEntry .pattern0 -parent .userFrame \
  -textVariable pattern0 -width 40 \
  -label "Bunch pattern 0 (enter even numbers starting at 0): " \
  -contextHelp "Enter a comma- or space-separated list of even numbers for the bunches to activate in state 0."

APSLabeledEntry .interval0  -parent .userFrame \
  -textVariable interval0 \
  -label "Time to spend in pattern 0 before switching (s): " \
  -contextHelp "Enter the time in seconds to spend in state 0 before switching to state 1."

APSRadioButtonFrame .dumpPattern0 -parent .userFrame \
    -label "Dump pattern 0? " \
    -orientation horizontal -variable dumpPattern0 -valueList "No PAR Booster" -buttonList [list No "At PAR Septum" "At Booster Septum"] \
    -contextHelp "Dump pattern 0 beam at the PAR or booster septum?"

APSLabeledEntry .rcToggle -parent .userFrame \
    -textVariable toggleRCList -width 80 \
    -label "RC Records to toggle: " \
    -contextHelp "Enter a comma- or space-separated list of run control records. These will be suspended in pattern 1 and resumed in pattern 0. Don't include the field in the record name (e.g., .RUN)."

APSLabeledEntry .pattern1 -parent .userFrame \
  -textVariable pattern1 -width 40 \
  -label "Bunch pattern 1 (enter even numbers starting at 0): " \
  -contextHelp "Enter a comma- or space-separated list of even numbers for the bunches to activate in state 1."

APSLabeledEntry .interval1  -parent .userFrame \
  -textVariable interval1 \
  -label "Time to spend in pattern 1 before switching (s): " \
  -contextHelp "Enter the time in seconds to spend in state 1 before switching to state 0."

APSLabeledEntry .longTermLimit  -parent .userFrame \
  -textVariable longTermLimit \
  -label "Long term limit in pulses/sec: " \
  -contextHelp "Enter the long term limit in pulses per second. Script won't run if you attempt to exceed this."

APSLabeledEntry .timelim  -parent .userFrame \
  -textVariable timeLimit \
  -label "Time limit for continuous switching (h): " \
  -contextHelp "Enter the time limit in hours for continuous switching. After this time has expired, the system reverts to a safe state."

proc SetButtonState {args} {
    set mode inactive
    APSStrictParseArguments {mode}
    switch $mode {
        run {
            APSDisableWidget .userFrame.ops.run
            APSEnableWidget .userFrame.ops.stop
        }
        stop {
            APSDisableWidget .userFrame.ops.stop
            APSEnableWidget .userFrame.ops.run
        }
    }
    update
}

APSLabeledEntry .logroot -parent .userFrame \
    -textVariable logRoot -width 80 \
    -label "Logfile root: " \
    -contextHelp "Enter a root name (including directory) for a log file."

frame .userFrame.ops
pack .userFrame.ops -side top
APSButton .run -parent .userFrame.ops \
  -text RUN \
  -command {SetButtonState -mode run; APSLinacRunPatternSwitching -pattern0 $pattern0 -pattern1 $pattern1 -longTermLimit $longTermLimit \
    -interval0 $interval0 -interval1 $interval1 -timeLimit $timeLimit -dumpPattern0 $dumpPattern0 -logRoot $logRoot -plot 1 \
    -toggleRCList $toggleRCList; SetButtonState -mode stop} \
  -contextHelp "Begins switching."

APSButton .stop -parent .userFrame.ops \
  -text STOP \
  -command {set abortRequested 1} \
  -contextHelp "Stops switching."

APSLabeledEntry .countdown -parent .userFrame \
    -textVariable countDown -label "Time to next switch (s): " 

APSLabeledEntry .efficiency0 -parent .userFrame \
    -textVariable efficiency0 -label "Efficiency for pattern 0 from last interval (%):  " 

APSLabeledEntry .efficiency1 -parent .userFrame \
    -textVariable efficiency1 -label "Efficiency for pattern 1 from last interval (%):  " 

APSLabeledEntry .averageCurrent -parent .userFrame \
    -textVariable averageCurrent -label "Average current (nA): " 

SetButtonState -mode stop

APSLinacPatternSwitchingInit 

dp_atexit append APSLinacResetBunchPattern
