proc APSSetMainStatus {text} {
    global mainStatus
    set mainStatus $text
    update 
}

proc APSAffix {list args} {
    set prefix ""
    set suffix ""
    APSStrictParseArguments {prefix suffix}
    set result ""
    foreach item $list {
	lappend result ${prefix}${item}${suffix}
    }
    return  $result
}

proc APSControlLawMakeWidgetGroup {widget args} {
    global groupDef
    global groupTitle
    global created
    
    APSStrictParseArguments {parent group}
    
    set w $parent$widget
    
    APSSetMainStatus "Doing frames for $groupTitle($group)..."
    APSFrame $widget -parent $parent -label $groupTitle($group) \
      -contextHelp "Frame for controllaw script group $groupTitle($group)" 
    $w.frame configure -relief flat
    APSControlLawMakeFullWidgetGroup .full -parent $w.frame -group $group
    APSUnmap $w.frame.full
    APSControlLawMakeBriefWidgetGroup .brief -parent $w.frame -group $group
    
    set created($w) 1
}

proc APSControlLawMakeBriefWidgetGroup {widget args} {
    global groupDef
    global groupTitle
    global created

    APSStrictParseArguments {parent group}

    set w $parent$widget
    set members $w.frame.members
    set option $w.frame.option
    
    if {[info exists created($w)] && $created($w)} {
        APSRemap $w
        return
    } else {
        set created($w) 1
    }
    APSFrame $widget -parent $parent -label "" \
      -contextHelp "Frame for controllaw script group $groupTitle($group)" 
    APSFrame .members -parent $w.frame -label "" \
      -contextHelp "Frame for controllaw script group $groupTitle($group)" 
    $members.frame configure -relief flat
    foreach member $groupDef($group) {
        APSControlLawMakeBriefWidget .$member -parent $members.frame -member $member
    }
    # option frame
    APSFrame .option -parent $w.frame -label ""  \
      -packOption {-side left} \
      -contextHelp "Option frame for controllaw script group $groupTitle($group)"
    $option.frame configure -relief flat
    APSButton .details -parent $option.frame -text "More details" -width "" \
      -command "APSUnmap $w; APSControlLawMakeFullWidgetGroup .full -parent $parent -group $group" \
        -contextHelp "Remaps the full $groupTitle($group) frames."
}

proc APSControlLawMakeFullWidgetGroup {widget args} {
    global groupDef
    global groupTitle
    global created
    
    APSStrictParseArguments {parent group}
    
    set w $parent$widget
    set members $w.frame.members
    set option $w.frame.option

    if {[info exists created($w)] && $created($w)} {
        APSRemap $w
        return
    } else {
        set created($w) 1
    }
    
    APSFrame $widget -parent $parent -label "" \
      -contextHelp "Frame for controllaw script group $groupTitle($group)" 
    APSFrame .members -parent $w.frame -label "" \
      -contextHelp "Frame for controllaw script group $groupTitle($group)" 
    $members.frame configure -relief flat
    foreach member $groupDef($group) {
        APSControlLawMakeWidget .$member -parent $members.frame -member $member
    }
    # option frame
    APSFrame .option -parent $w.frame -label "" \
      -packOption {-side left} \
      -contextHelp "Option frame for controllaw script group $groupTitle($group)"
    $option.frame configure -relief flat
    APSButton .less -parent $option.frame -text "Status only" -width "" \
      -command "APSUnmap $w; APSControlLawMakeBriefWidgetGroup .brief -parent $parent -group $group" \
      -contextHelp "Unmaps the full $groupTitle($group) frames."
}

proc APSControlLawMakeWidget {widget args} {
    global status suspend abort rcPV
    global directory title rcDesc rcPV Frame
    global connected
    global arguments
    global debug

    APSStrictParseArguments {parent member}
    APSFrame $widget -parent $parent -label $title($member) -orientation horizontal \
      -contextHelp "Frame for $title($member) controllaw."
    set w $parent$widget.frame
    # this global variable should only be defined in procedure APSControlLawMakeWidget
    # and not in APSControlLawMakeBriefWidget, since array Frame is used to keep track
    # of the frames associated with buttons.
    set Frame($member) $w

    frame  $w.status
    pack $w.status -side top
    APSLabeledOutput .status -parent $w.status -label "Status: " \
      -width 50 -textVariable status($member) \
      -contextHelp "Status output line."

    # make a frame for special entries, e.g. energy, interval, etc.
    frame $w.entries
    pack $w.entries -side top
    # get labeled entry instruction from script ${w}Entries
    if {[file executable [set file $directory($member)/${member}Entries]]} {
        catch {exec $file -widget $w.entries -get defaults} DefaultCommands
        foreach command $DefaultCommands {
            # add to arguments list and globalize
            regexp {(.*)\((.*)\)} [lindex $command 1] {} variable
            if {[lsearch -exact $arguments $variable] == -1} {
                set arguments [concat $arguments $variable]
            }
            if {![llength [info vars $variable]]} {
                global $variable
            }
            eval  [subst $command]
        }
        # this overrides the defaults for the energy parameter, if an array member is specified in the calling script.
        APSControlLawSetEnergy -member $member
        catch { exec $file -widget $w.entries -get instructions} EntryCommands
        foreach command $EntryCommands {
            eval [subst $command] \
              -contextHelp {{Allows one to change argument values before starting a controllaw script}}
        }
    } else {
        APSInfoWindow .alert -name "Warning" \
          -infoMessage "Can't find or execute file $file for parameter entries generation. No parameter boxes generated."\
          -modal 1
    }
    #extra code for LINAC controllaws
    if {($directory($member) == "LINAC") && (($member == "xLinac") || ($member == "yLinac"))} {
        global linacgun        
        set linacgun RG2
        APSSetMainStatus "Doing frames for linac control from RG2..."
        set linkname [file link /home/helios/oagData/controllaw/LINAC/xLinac.def]
        if {$linkname == "/home/helios/oagData/controllaw/LINAC/RG1/xLinac.def"} {
            set linacgun RG1
        }
        set nindex [lsearch -exact [split $w "."] $member]
        if {$member == "xLinac"} {
            set ww [join [lreplace [split $w "."] $nindex $nindex "yLinac"] "."]
        } else {
            set ww [join [lreplace [split $w "."] $nindex $nindex "xLinac"] "."]
        }
        frame $w.state
        pack $w.state -side top
        radiobutton $w.state.rg1 -text "RG1" -variable linacgun -value RG1 -command {
            set linkname [file link /home/helios/oagData/controllaw/LINAC/xLinac.def]
            if {$linkname == "/home/helios/oagData/controllaw/LINAC/RG1/xLinac.def"} {
                return
            }
            APSSetMainStatus "Doing frames for linac control from RG1..."
            APSControlLawAbortButton  -parent .userFrame.linac.frame.full.frame.members.frame.xLinac.frame -member xLinac
            APSControlLawAbortButton  -parent .userFrame.linac.frame.full.frame.members.frame.yLinac.frame -member yLinac
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/xLinac.def /home/helios/oagData/controllaw/LINAC/xLinac.def
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/xLinac.matrix /home/helios/oagData/controllaw/LINAC/xLinac.matrix
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/xLinac.matrix.resp /home/helios/oagData/controllaw/LINAC/xLinac.matrix.resp
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/xLinac.tests /home/helios/oagData/controllaw/LINAC/xLinac.tests
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/yLinac.def /home/helios/oagData/controllaw/LINAC/yLinac.def
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/yLinac.matrix /home/helios/oagData/controllaw/LINAC/yLinac.matrix
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/yLinac.matrix.resp /home/helios/oagData/controllaw/LINAC/yLinac.matrix.resp
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/yLinac.tests /home/helios/oagData/controllaw/LINAC/yLinac.tests
        }
        radiobutton $w.state.rg2 -text "RG2" -variable linacgun -value RG2 -command {
            set linkname [file link /home/helios/oagData/controllaw/LINAC/xLinac.def]
            if {$linkname == "/home/helios/oagData/controllaw/LINAC/RG2/xLinac.def"} {
                return
            }
            APSSetMainStatus "Doing frames for linac control from RG2..."
            APSControlLawAbortButton  -parent .userFrame.linac.frame.full.frame.members.frame.xLinac.frame -member xLinac
            APSControlLawAbortButton  -parent .userFrame.linac.frame.full.frame.members.frame.yLinac.frame -member yLinac
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/xLinac.def /home/helios/oagData/controllaw/LINAC/xLinac.def
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/xLinac.matrix /home/helios/oagData/controllaw/LINAC/xLinac.matrix
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/xLinac.matrix.resp /home/helios/oagData/controllaw/LINAC/xLinac.matrix.resp
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/xLinac.tests /home/helios/oagData/controllaw/LINAC/xLinac.tests
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/yLinac.def /home/helios/oagData/controllaw/LINAC/yLinac.def
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/yLinac.matrix /home/helios/oagData/controllaw/LINAC/yLinac.matrix
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/yLinac.matrix.resp /home/helios/oagData/controllaw/LINAC/yLinac.matrix.resp
            exec ln -fs /home/helios/oagData/controllaw/LINAC/${linacgun}/yLinac.tests /home/helios/oagData/controllaw/LINAC/yLinac.tests
        }
        pack $w.state.rg1 $w.state.rg2 -side left
        if {$member == "xLinac"} {
            after 30000 CheckLinacLinks .userFrame.linac.frame.full.frame.members.frame.xLinac.frame.state .userFrame.linac.frame.full.frame.members.frame.yLinac.frame.state
        }
    }
    # define buttons:
    # start resume suspend abort clear info
    ############################################
    
    APSButton .start -parent $w -text "Start" -width "" \
      -command "APSControlLawStartButton -parent $w -member $member" \
      -contextHelp "Starts script for $title($member)"
    if {$member=="longBooster"} {
        APSButton .start1 -parent $w -text "Full Start" -width "" \
          -command "APSControlLawStartButton -parent $w -member $member -full 1" \
          -contextHelp "Starts script for $title($member) with checking the par phase test limits"
    }
    APSButton .resume -parent $w -text "Resume" -width "" \
      -command "APSControlLawResumeButton -parent $w -member $member" \
      -contextHelp "Resumes associated run control for $title($member) after it was suspended"
    APSButton .suspend -parent $w -text "Suspend" -width "" \
      -command "APSControlLawSuspendButton -parent $w -member $member" \
      -contextHelp "Suspends associated run control for $title($member) after it was started or resumed"
    APSButton .abort -parent $w -text "Abort" -width "" \
      -command "APSControlLawAbortButton  -parent $w -member $member" \
      -contextHelp "Abort associated run control for $title($member)"
    APSButton .clear -parent $w -text "Clear" -width "" \
      -command "APSControlLawClearButton  -parent $w -member $member" \
      -contextHelp "Clear associated run control for $title($member)"
    APSButton .info -parent $w -text "Info" -width "" \
      -command "APSControlLawInfoButton  -parent $w -member $member" \
      -contextHelp "Brings up medm and tcl windows for more information on runcontrol."
    APSButton .testValues -parent $w -text "TestValues" -width "" \
      -command "APSControlLawTestValuesButton -parent $w -member $member" \
      -contextHelp "Brings up a window displaying values of tests. Useful when sddscontrollaw issues a out-of-range status."
    APSButton .matrix -parent $w -text "Matrix" -width "" \
      -command "APSControlLawMatrixButton -parent $w -member $member" \
      -contextHelp "Brings up a window displaying the controllaw matrix. Useful to see what PVs are controlled by this controllaw."
    if {$member=="xPAR" || $member=="yPAR"} {
        APSButton .adt -parent $w -text "BPM Error ADT" -width "" \
          -command "exec adt -f /home/helios/oagData/ADTFiles/par/par.bpm.with.error.pv &" \
          -contextHelp "bring up par bpm error adt"
    }
    # check to see if controllaw with the same description $rcDesc($member) is already running.
    set PV $rcPV($member)
    # array "connected" is the inverse associative array of rcPV
    set connected($PV) $member
    if {[APSControlLawIsSlotTimedOut -PV $PV]} {
        APSDisableButton $w.start.button
        if [winfo exist $w.start1.button] {
            APSDisableButton $w.start1.button
        }
        APSDisableButton $w.resume.button
        APSDisableButton $w.suspend.button
        APSDisableButton $w.abort.button
        APSEnableButton $w.clear.button
        #        exec medm -x -attach -macro RCPV=$PV ./sr/psApp/APSRunControlSingle.adl & 
        APSInfoWindow .alert -name "Warning" -infoMessage "PV slot $PV is timed out. In order for sddscontrollaw to execute, this slot must be cleared. Press the corresponding CLEAR button." -modal 0
    } elseif {![APSControlLawIsSlotUsed -PV $PV]} {
        APSEnableButton $w.start.button
        if [winfo exist $w.start1.button] {
            APSEnableButton $w.start1.button
        }
        APSDisableButton $w.resume.button
        APSDisableButton $w.suspend.button
        APSDisableButton $w.abort.button
        APSDisableButton $w.clear.button
    } else {
        #check the description if it contains using info,
        if [catch {exec cavget -list=$PV.DESC -pend=10} desc] {
        } else {
            set use [lindex [split $desc "-"] 1]
            #H.Shang, add checking is subdirectory $use exist, do not set it to usig if it does not exist 
            if {[string length $use] && [file exist $directory($member)/$use]} {
                set using($member) $use
            }
        }
        # Disable entry boxes and refresh buttons because controllaw for that rc slot is running on some other
        # screen probably.
        foreach arg $arguments {
            if {[info exists ${arg}($member)]} {
                catch { $w.entries.$arg.entry configure -state readonly }
            }
        }
    }
    APSControlLawStartMonitors -PV $PV
    APSControlLawRefreshArrays -PV $PV
    APSControlLawRefreshButtons -PV $PV
}

proc CheckLinacLinks {w1 w2} {
    global linacgun
    if {[catch {file link /home/helios/oagData/controllaw/LINAC/xLinac.def} linkname]} {
        return
    }
    if {$linkname == "/home/helios/oagData/controllaw/LINAC/RG1/xLinac.def"} {
        if {$linacgun == "RG2"} {
            set linacgun RG1
        }
    } elseif {$linkname == "/home/helios/oagData/controllaw/LINAC/RG2/xLinac.def"} {
        if {$linacgun == "RG1"} {
            set linacgun RG2
        }
    }
    after 30000 CheckLinacLinks $w1 $w2
}

proc APSControlLawMakeBriefWidget {widget args} {
    global status suspend abort rcPV
    global directory title rcDesc Frame
    global arguments
    global debug

    APSStrictParseArguments {parent member}
    APSFrame $widget -parent $parent -label $title($member) -orientation horizontal \
      -contextHelp "Frame for $title($member) controllaw"
    set w $parent$widget.frame

    frame $w.status
    pack $w.status -side top
    APSLabeledOutput .status -parent $w.status -label "Status: " \
      -width 50 -textVariable status($member) \
      -contextHelp "Status output line."
}

# returns 1 is slot is used, i.e. the description field is non-zero
proc APSControlLawIsSlotUsed {args} {
    global debug

    APSStrictParseArguments {PV}

    # simply check the length of the description field
    if {[string length [set result [exec cavget -list=$PV -list=.DESC -noquotes] ] ]} {
        return 1
    } else {
        return 0
    }
}

# returns 1 is slot is used, i.e. the description field is non-zero
proc APSControlLawIsSlotTimedOut {args} {
    global debug

    APSStrictParseArguments {PV}

    # simply check that the timeout PV is off or on.
    if {![string compare [set result [exec cavget -list=$PV -list=.HBTO -noquotes] ] "Timeout" ]} {
        return 1
    } else {
        return 0
    }
}

# starts monitoring PV related to the slot PV argument.
proc APSControlLawStartMonitors {args} {
    global errorCode
    global slotStatus slotSuspend slotAbort slotTimeOut slotDesc slotRunning
    global debug

    APSStrictParseArguments {PV}

    if { [string length $PV] } {
        # start monitors on status, suspend, abort, TimeOut, and Run variables
        if {[pv linkw \
               [list slotStatus($PV) slotSuspend($PV) slotAbort($PV) slotTimeOut($PV) slotDesc($PV) slotRunning($PV)] \
               [list $PV.MSG $PV.SUSP $PV.ABRT $PV.HBTO $PV.DESC $PV.RUN]] != 0} {
            APSAlertBox .alert -errorMessage "$errorCode"
            exit
        }
        if {[pv umon [list slotSuspend($PV) slotAbort($PV) slotTimeOut($PV) slotDesc($PV) slotRunning($PV)]\
               [list APSControlLawRefreshArraysAndButtons -PV $PV] ] != 0} {
            APSAlertBox .alert -errorMessage "$errorCode"
            exit
        }
        if {[pv umon [list slotStatus($PV)] [list APSControlLawRefreshArrays -PV $PV]] != 0} {
            APSAlertBox .alert -errorMessage "$errorCode"
            exit
        }
        if {$debug} {
            puts stderr [pv info [list slotStatus($PV) slotSuspend($PV) slotAbort($PV) \
                                    slotTimeOut($PV) slotDesc($PV)] name]
        }
    } else {
        APSAlertBox .alert -errorMessage "Zero-length string PV passed to APSControlLawStartMonitors procedure."
    }
}


proc APSControlLawRefreshArraysAndButtons {args} {
    global debug

    if {$debug} {
        puts stderr "In APSRefreshArraysAndButtons: $args"
    }
    eval APSControlLawRefreshArrays $args
    eval APSControlLawRefreshButtons $args
}

proc APSControlLawRefreshArrays {args} {
    global slotStatus slotSuspend slotAbort slotTimeOut connected slotRunning
    global status suspend abort timeOut
    global debug

    APSStrictParseArguments {PV}

    set member $connected($PV)
    if {$debug} {
        puts stderr "In APSControlLawRefreshArrays, PV:$PV member:$member"
    }
    set status($member) $slotStatus($PV)
    set suspend($member) $slotSuspend($PV)
    set abort($member) $slotAbort($PV)
    set timeOut($member) $slotTimeOut($PV)
    set running($member) $slotRunning($PV)
}

proc APSControlLawStartButton {args} {
    global directory using title rcDesc rcPV
    global status suspend abort timeOut
    global connected
    global arguments
    global debug
    set full 0
    APSStrictParseArguments {parent member full}

    set pwd [pwd]
    cd $directory($member)
    if $full {
        if [catch {CheckLongBoosterTestValues -member $member -subDir $using($member) } result] {
            APSInfoWindow .alert -name "Warning" -infoMessage "Error in checking test limits: $result" -modal 1
            return
        }
    }
    # Make a string of parameters, such as "-energy 400 -interval 3", to pass to script ${member}Options.
    # This script returns appropriate command line options for the sddscontrollaw command.
    # The values of parameters come from the input boxes defined by APSLabeledInput.
    # The inputs are to be disabled if sddscontrollaw is running.
    set parameters {}
    foreach arg $arguments {
        global $arg
        if {[info exists ${arg}($member)]} {
            if $debug {
                puts stderr "APSControlLawStartButton: -$arg  [subst $${arg}($member)]"
            }
            set parameters "$parameters -$arg  [subst $${arg}($member)]"
            catch {$parent.entries.$arg.entry configure -state readonly }
        }
    }
    if {![array exists rcPV] || ![info exists rcPV($member)]} {
        APSInfoWindwo .alert -name "Warning" -infoMessage "Something wrong with rcPV or rcPV($member)." -modal 1
        return
    }
    set PV $rcPV($member)
    
    set parameters "$parameters -rcPV $PV"
    set option {}
    if {[file executable [set file ${member}Options]]} {
        # eval is important here otherwise $parameter will become a list of one element.
        if $debug {
            puts stderr "APSControlLawStartButton: eval exec $file $parameters"
        }
        catch {eval exec ./$file $parameters} options
        if $debug {
            puts stderr "APSControlLawStartButton: options: $options"
        }
    } else {
        APSInfoWindow .alert -name "Warning" -infoMessage "Can't find or execute file $file for generating the sddscontrollaw options." -modal 1
    }  
    #####################
    # run sddscontrollaw
    ####################
    #add abort and clear runcontrol before start it
    if [catch {exec cavput -list=$rcPV($member).ABRT=1} result] {
	return -code error "Error aborting $rcPV($member) before starting it: $result"
    }
    after 1000
    if [catch {exec cawait -waitfor=$rcPV($member).RUN,equal=0 -timeLimit=30} result] {
	return -code error "Error waiting for  $rcPV($member) to abort before starting it: $result"
    }
    #for booster xBooster single turn controllaw, needs restore the single turn bpm config and setpoint
    if {$member=="xBooster" && $using($member)=="singleTurn"} {
        if $debug {
            puts stderr "APSControlLawStartButton: setup booster single turn bpm..."
        }
        if [catch {APSSetupBoosterSingleTurnBPM} result] {
            return -code error "Error setup booster single turn bpm: $result"
        }
    }

    if {$debug} {
        puts stderr "APSControlLawStartButton: options: $options"
        APSExecLog .${member}controllaw   -width 95 -lineLimit 1024 \
          -unixCommand "sddscontrollaw $options -verbose"
    } else {
        if [ catch {
            eval exec sddscontrollaw $options >& /dev/null & 
        } result ] {
            APSAlertBox .alert -errorMessage "error with sddscontrollaw: $result"
            return
        }
    }
    cd $pwd

    # check whether PV slot is activated. 
    set Tries 0
    set MaxTries 4
    while {[expr $Tries < $MaxTries]} {
        after 1000
        if {[string length [exec cavget -list=$PV -list=.PID] ]} {
            break
        }
        incr Tries
    }
    if {[expr $Tries == $MaxTries]} {
        APSAlertBox .alert -errorMessage "Tried $MaxTries times to check whether the reserved run control PV slot $PV with no success.\n\ncontrollaw on $PV may have bombed somehow. The state of buttons may be in error.\n\nFor help call Louis Emery x7756 or E-mail page 7756@aps.anl.gov"
        return
    }
    #check if controllaw is running
    after 3000
    if [catch {exec cavget -list=$rcPV($member).RUN -pend=10} running] {
        APSAlertBox .alert -errorMessage "Error reading $rcPV($member).RUN: $running"
        return
    }
    if [ catch {exec logMessage  -sourceId=controllawAudit\
                  -tag=Instance $PV -tag=Action Start \
              } result ] {
        APSAlertBox .alert -errorMessage "error with logMessage: $result"
        return
    }
    
    if !$running {
        APSAlertBox .alert -errorMessage "$member controllaw is not running, something is wrong, use debug mode to check the errors!"
        return
    }
    APSControlLawStartMonitors -PV $PV
    APSControlLawRefreshArrays -PV $PV
    # change state of buttons. Don't use APSControlLawRefreshButtons here.
    
    APSDisableButton $parent.start.button
    if [winfo exist $parent.start1.button] {
        APSDisableButton $parent.start1.button
    }
    APSDisableButton $parent.resume.button
    APSEnableButton $parent.suspend.button
    APSEnableButton $parent.abort.button
    APSDisableButton $parent.clear.button
    if [file exists $directory($member)/$using($member)/$member.start] {
        exec $directory($member)/$using($member)/$member.start
    }
}

proc APSControlLawResumeButton {args} {
    global rcPV directory using
    global status
    global debug

    APSStrictParseArguments {parent member}

    if {$debug} {
        puts stderr "APSControlLawResumeButton: Pressed resume for $member in $rcPV($member)."
    }
    set status($member) "Resumed"
    exec caput $rcPV($member).SUSP 0
    if $debug {
        puts stderr "APSControlLawResumeButton: caput -s $rcPV($member).ALRM NO_ALARM"
    }
    exec caput -s $rcPV($member).ALRM NO_ALARM
    if [ catch {exec logMessage -sourceId=controllawAudit\
                  -tag=Instance $rcPV($member) -tag=Action Resume \
              } result ] {
        APSAlertBox .alert -errorMessage "error with logMessage: $result"
        return
    }
    if [file exists $directory($member)/$using($member)/$member.resume] {
        exec $directory($member)/$using($member)/$member.resume
    }
}

proc APSControlLawSuspendButton {args} {
    global rcPV directory using
    global debug

    APSStrictParseArguments {parent member}
    
    if {$debug} {
        puts stderr "Pressed suspend for $member in $rcPV($member)."
    }
    set status($member) "Suspended"
    exec caput $rcPV($member).SUSP 1
    if $debug {
        puts stderr "APSControlLawSuspendButton: caput -s $rcPV($member).ALRM MINOR"
    }
    exec caput -s $rcPV($member).ALRM MINOR
    if [ catch {exec logMessage  -sourceId=controllawAudit\
                  -tag=Instance $rcPV($member) -tag=Action Suspend \
              } result ] {
        APSAlertBox .alert -errorMessage "error with logMessage: $result"
        return
    }    
    if [file exists $directory($member)/$using($member)/$member.suspend] {
        exec $directory($member)/$using($member)/$member.suspend
    }
}

proc APSControlLawAbortButton {args} {
    global rcPV abort suspend status timeOut directory using
    global connected
    global arguments
    global debug

    APSStrictParseArguments {parent member}

    set status($member) "Aborted"
    exec caput $rcPV($member).ABRT 1
    # this sets the alarm state to NO_ALARM
    if $debug {
        puts stderr APSControlLawAbortButton: "caput -s $rcPV($member).ALRM NO_ALARM"
    }
    exec caput -s $rcPV($member).ALRM NO_ALARM
    if [ catch {exec logMessage -sourceId=controllawAudit\
                  -tag=Instance $rcPV($member) -tag=Action Abort \
              } result ] {
        APSAlertBox .alert -errorMessage "error with logMessage: $result"
        return
    }
    if [file exists $directory($member)/$using($member)/${member}.abort] {
        exec $directory($member)/$using($member)/${member}.abort
    }
}

proc APSControlLawClearButton {args} {
    global rcPV abort suspend status timeOut
    global connected
    global arguments
    global debug

    APSStrictParseArguments {parent member}

    set status($member) "Cleared"
    exec caput $rcPV($member).ABRT 1
    exec caput $rcPV($member).CLR 1
    if $debug {
        puts stderr "APSControlLawClearButton: caput -s $rcPV($member).ALRM NO_ALARM"
    }
    exec caput -s $rcPV($member).ALRM NO_ALARM
}

proc APSControlLawRefreshButtons {args} {
    global suspend status abort timeOut
    global slotSuspend slotStatus slotAbort slotTimeOut slotDesc connected slotRunning
    global arguments
    global debug
    global Frame

    APSStrictParseArguments {PV}

    if {$debug} {
        puts stderr "APSControlLawRefreshButtons: PV:$PV connected to: $connected($PV)"
    }
    set member $connected($PV)
    set w $Frame($member)
    if {$debug} {
        puts stderr "button parent widget:$w"
        puts stderr "slotAbort($PV):$slotAbort($PV)"
        puts stderr "slotSuspend($PV):$slotSuspend($PV)"
        puts stderr "slotTimeOut($PV):$slotTimeOut($PV)"
        puts stderr "slotDesc($PV):$slotDesc($PV)"
	puts stderr "slotRunning($PV):$slotRunning($PV)"
    }
    # possible combinations of array values
    #       suspend         abort      timeOut
    # ----------------------------------------
    #       resume          run
    #       suspend         run
    #       resume/resume   abort         0      ! in case of abort, disable all but start, and skip the other tests
    #       resume/resume   run/abort     1      ! enable clear buuton only 
    if {![string compare $slotTimeOut($PV) "Timeout"]} {
        APSDisableButton $w.start.button
        if [winfo exist $w.start1.button] {
            APSDisableButton $w.start1.button
        }
        APSDisableButton $w.resume.button
        APSDisableButton $w.suspend.button
        APSDisableButton $w.abort.button
        APSEnableButton $w.clear.button
        set status($member) "Timed out. Press clear"
    } elseif {![string compare $slotAbort($PV) "Abort"] || ![string length $slotDesc($PV)] || !$slotRunning($PV) } {
        if {$debug} {
            puts stderr "APSEnableButton $w.start.button"
            puts stderr "APSDisableButton $w.resume.button"
            puts stderr "APSDisableButton $w.suspend.button"
            puts stderr "APSDisableButton $w.abort.button"
            puts stderr "APSDisableButton $w.clear.button"
        }
        APSEnableButton $w.start.button
        if [winfo exist $w.start1.button] {
            APSEnableButton $w.start1.button
        }
        APSDisableButton $w.resume.button
        APSDisableButton $w.suspend.button
        APSDisableButton $w.abort.button
        APSDisableButton $w.clear.button
        set status($member) "Aborted or cleared. Press Start."
        # enable entry boxes
        foreach arg $arguments {
            global $arg
            if {[info exists ${arg}($member)]} {
                catch { $w.entries.$arg.entry configure -state normal}
            }
        }
    } elseif {![string compare $slotSuspend($PV) "Resume"]} {
        APSDisableButton $w.start.button
        if [winfo exist $w.start1.button] {
            APSDisableButton $w.start1.button
        }
        APSDisableButton $w.resume.button
        APSEnableButton $w.suspend.button
        APSEnableButton $w.abort.button
        APSDisableButton $w.clear.button
        set connected($PV) $member
    } elseif {![string compare $slotSuspend($PV) "Suspend"]} {
        APSDisableButton $w.start.button
        if [winfo exist $w.start1.button] {
            APSDisableButton $w.start1.button
        }
        APSEnableButton $w.resume.button
        APSDisableButton $w.suspend.button
        APSEnableButton $w.abort.button
        APSDisableButton $w.clear.button
        set connected($PV) $member
    }
}

proc APSControlLawInfoButton {args} {
    global arguments
    global directory
    global rcPV
    global HomeDir
    foreach arg $arguments {
        global $arg
    }
    APSStrictParseArguments {parent member}
    if {![array exists rcPV] || ![info exists rcPV($member)]} {
        APSInfoWindow .infoError -name "InfoError" -infoMessage "$member controllaw not assigned to any slot yet."
        return
    }
    if ![string length $rcPV($member)] {
        APSInfoWindow .infoError -name "InfoError" \
          -infoMessage "Empty string for runcontrol PV slot. No info available." -modal 1
        return
    }
    exec medm -x -attach -macro "RCPV=$rcPV($member)" ./sr/psApp/APSRunControlSingle.adl &
    return
    # The Info program uses rsh.
    # There are problems with using rsh currently.
    if {[file exists [set file $HomeDir/Info]] } {
        APSExecLog .${member}Info -unixCommand "$file -slot $rcPV($member)" \
          -width 80
    } else {
        APSInfoWindow .infoError -name "InfoError" \
          -infoMessage "Can't find file $file. No info available" -modal 1
        return
    }
}

proc APSControlLawTestValuesButton {args} {
    global arguments
    global directory
    global rcPV
    global HomeDir
    global using
    foreach arg $arguments {
        global $arg
    }

    APSStrictParseArguments {parent member}
    if [catch {exec cavget -list=Mt:TopUpAutoEnableC.VAL -num } topup] {
        return -code error  "Unable to read topup status pv (Mt:TopUpAutoEnableC.VAL): $topup"
    }

    if [file isdirectory $HomeDir/$directory($member)/$using($member)] {
        set dir $HomeDir/$directory($member)/$using($member)
        #set testFile $HomeDir/$directory($member)/$using($member)/${member}.tests
    } else {
        set dir $HomeDir/$directory($member)
        #set testFile $HomeDir/$directory($member)/${member}.tests
    } 
    if {$topup && [file exist $dir/${member}-topup.tests]} {
        set testFile $dir/${member}-topup.tests
    } elseif {!$topup && [file exist $dir/${member}-nontopup.tests]} {
        set testFile $dir/${member}-nontopup.tests
    } else {
        set testFile $dir/${member}.tests
    }
    set command \
      "sddssnapshot $testFile -pipe=output | sddsprocess -pipe \"-def=col,InBounds,MinimumValue Value < pop pop ? Value MaximumValue < pop pop ? 1 : 0 $ : 0 $,type=long\" | sddssort -pipe -col=InBounds -col=ControlName | sddsprintout -pipe=in -col=ControlName,format=%28s -col=(MinimumValue,Value,MaximumValue,InBounds)"
    APSExecLog .${member}TestInfo -unixCommand $command -width 88
    APSWaitWithUpdate -waitSeconds 1
    set data [.${member}TestInfo.userFrame.text.text get 1.0 end]
    set indexList ""
    set n 1
    foreach line [split $data \n] {
        if {[lindex $line end] == 0} {
            append indexList "$n.0 [expr {$n + 1}].0 "
        }
        incr n
    }
    if {[llength $indexList]} {
        eval .${member}TestInfo.userFrame.text.text tag add tag1 $indexList
    }
    .${member}TestInfo.userFrame.text.text tag configure tag1 -foreground red -background white
    
}

proc APSControlLawMatrixButton {args} {
    global arguments
    global directory
    global rcPV
    global HomeDir
    global using
    global debug
    foreach arg $arguments {
        global $arg
    }

    APSStrictParseArguments {parent member}
    if $debug {
        puts stderr "APSControlLawMatrixButton: HomeDir/directory(member)/using(member) $HomeDir/$directory($member)/$using($member)"
    }
    if [file isdirectory $HomeDir/$directory($member)/$using($member)] {
        set dir $HomeDir/$directory($member)/$using($member)
    } else {
        set dir $HomeDir/$directory($member)
    } 
    if $debug {
        puts stderr "APSControlLawMatrixButton:  sddsedit $dir/${member}.matrix"
    }
    catch {exec sddsedit $dir/${member}.matrix &}
}

proc APSControlLawSetEnergy {args} {
    global energy
    global BrhoPV
    global using
    APSStrictParseArguments {member}
    
    if {[info exists BrhoPV($member)] && [info exists energy($member)]} {
        if {[pv linkw energy($member) $BrhoPV($member) ]!= 0} {
            APSAlertBox .alert -errorMessage "$errorCode"
            exit
        }
        if {[pv getw energy($member) ] != 0} {
            APSAlertBox .alert -errorMessage "$errorCode"
            exit
        }
        set energy($member) [expr floor($energy($member) * 300 + 0.5 )]
    }
    if {$member=="rf3637Err" || $member=="rf3840Err"} {
        set using($member) [file tail [file readlink /home/helios/oagData/controllaw/SRgapVoltage/public/mode]]
    }
}

proc CheckLongBoosterTestValues {args} {
    set member ""
    set subDir .
    APSStrictParseArguments {member subDir}

    if {$member!="longBooster"} {
        return
    }
    if [catch {exec cavget -list=PAR:RF12:PhsShft2Ch2_3AO -printErrors -pend=10} parPhase] {
        return -code error "Something wrong with PAR:RF12:PhsShft2Ch2_3AO."
    }
    set low [format %.0f [expr $parPhase - 15]]
    set high [format %.0f [expr $parPhase + 15]]
    set tmpFile /tmp/[APSTmpString]
    set oldDir [pwd]
    cd $subDir
    if [catch {exec sddsprocess longBooster.tests $tmpFile -match=col,ControlName=PAR:RF12:PhsShft2Ch2_3AO} result] {
        return -code error "PAR:RF12:PhsShft2Ch2_3AO does not exist in longBooster.tests file."
    }
    set low1 [format %.0f [exec sdds2stream -col=MinimumValue $tmpFile]]
    set high1 [format %.0f [exec sdds2stream -col=MaximumValue $tmpFile]]
    if {[expr abs($low-$low1)]>5 || [expr abs($high-$high1)]>5} {
        if [APSYesNoPopUp "The current test limits of par phase (PAR:RF12:PhsShft2Ch2_3AO) is ($low1, $high1),\n while the correct value should be ($low, $high)\n, do you want to change the test limits of par phase?"] {
            set file [file readlink longBooster.tests]
            set newFile [APSNextGenerationedName -name $file -separator - -newFile 1]
            if [catch {exec sddsprocess $tmpFile $tmpFile.1 -redefine=col,MinimumValue,$low -redefine=col,MaximumValue,$high
                exec sddsprocess longBooster.tests $tmpFile.2 "-match=col,ControlName=PAR:RF12:PhsShft2Ch2_3AO,!"
                exec sddscombine $tmpFile.2 $tmpFile.1 -merge $newFile} result] {
                cd $oldDir
                return -code error $result
            }
            exec rm longBooster.tests
            exec ln -s $newFile longBooster.tests
        }
    } 
    cd $oldDir
    return
}

proc APSSetupBoosterSingleTurnBPM {} {
    #restore BBPM Waveform
    set dir /home/helios/oagData/SCR/snapshots/BBPMWaveform
    #set waveformSCR $dir/BBPMWaveform-singleTurnX.gz
    set waveformSCR $dir/BBPMWaveform-singleTurnInjAveX.gz
    set root [file tail  [file root [file readlink $waveformSCR]]]
    set tmpRoot /tmp/[APSTmpString]
    if [catch {exec sddscasr -logFile=$tmpRoot.wf -restore $waveformSCR -waveform=directory=$dir,onefile,rootname=$root,extension=.waveform.gz } result] {
        return -code error "Error restore single turn Booster bpm config: $result"
    }
    
    set fd [open $tmpRoot.wf "r"]
    set errorInRestore 0
    while {![eof $fd]} {
        set text [gets $fd]
        if {[string match -nocase "*error*" $text]} {
            set errorInRestore 1
            break
        }
    }
    close $fd
    if $errorInRestore {
        APSFileDisplayWindow [APSUniqueName .fileDisp] -fileName $tmpRoot.wf \
          -comment "Error output from sddscasr" -deleteOnClose 1 \
          -printCommand "enscript -r"
        return -code error "Error in restore booster single turn bpm config!"
    }
    #restore single turn setpoint
    if [catch {exec sddsprocess /home/helios/oagData/SCR/snapshots/Booster/Booster-SingleTurnPreferred.gz \
                 -pipe=out -match=col,ControlName=*singleTurn*setpointAO \
                 | sddscasr -pipe=in -restore -pend=30 -logFile=$tmpRoot.setpoint  } result] {
        return -code error "Error restore booster single turn bpm setpolint: $result"
    }
    set fd [open $tmpRoot.setpoint "r"]
    set errorInRestore 0
    while {![eof $fd]} {
        set text [gets $fd]
        if {[string match -nocase "*error*" $text]} {
            set errorInRestore 1
            break
        }
    }
    close $fd
    if $errorInRestore {
        APSFileDisplayWindow [APSUniqueName .fileDisp] -fileName $tmpRoot.setpoint \
          -comment "Error output from sddscasr" -deleteOnClose 1 \
          -printCommand "enscript -r"
        return -code error "Error in restore booster single turn bpm config!"
    }
    #arm singlue tur bpms
    if [catch {exec cavput -list=B4C8,B1C0,B1C2,B1C4,B1C6,B1C8 -list=:singleTurn:Enable=1 -pend=30} result] {
        return -code error "Error arm booster single turn bpms: $result"
    }
    file delete -force $tmpRoot.wf $tmpRoot.sestpoint
}
