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

APSStandardSetup

set CVSRevisionAuthor "Version 1.0, H. Shang"
# Needed for APSParseArguments
set args $argv
set knobFile ""
set usage "pvgroupKnobs \[-knobFile <filename>]"
if [APSStrictParseArguments {knobFile}] {
    puts stderr "$usage"
    exit 1
}

proc SetStatus {text} {
    global status
    set status "$text"
    update
}

proc openConfigFile {fileName} {   
    global knobList knob knobFile scrollWidget offsetPVList
    
    if [llength $knobList] {
	#delete previous knobs
	foreach knobName $knobList {
	    catch {destroy $knob($knobName.widget)}
	}
    }
    set pars [exec sddsquery -par $knobFile]
    if [lsearch -exact $pars "Mode"]>=0 {
	set modeExist 1
    } else {
	set modeExist 0
    }
    set knobList [exec sdds2stream -par=ControlName $knobFile]
    set stepList [exec sdds2stream -par=StepSize $knobFile]
    set gainList [exec sdds2stream -par=Gain $knobFile]
    set descList [exec sdds2stream -par=KnobDescription $knobFile]
    set statusPVList [exec sdds2stream -par=SetStatusPV $knobFile]
    set offsetPVList [exec sdds2stream -par=SetRbvPV $knobFile]
    if $modeExist {
	set modeList [exec sdds2stream -par=Mode $knobFile]
    }
    set tmpRoot /tmp/[APSTmpString]
    set i 0
    foreach knobName $knobList step $stepList gain $gainList desc $descList statusPV $statusPVList {
	set knob($knobName.name) $knobName
	set knob($knobName.gain) [format %0.3f $gain]
	set knob($knobName.offset) ? 
	set knob($knobName.stepSize) $step
	set knob($knobName.desc) $desc
        set knob($knobName.statusPV) $statusPV
	if $modeExist {
	    set mode [lindex $modeList $i] 
	    set knob($knobName.mode) $mode
	    if {$mode=="differential" || $mode=="offset"} {
		SetStatus "Warning for $knobName pvgroupKnobs does not work with $mode mode!"
	    }
	    incr i
	}
	knobFrame $knobName $scrollWidget.frame.canvas.frame
	if [catch {exec sddsprocess $knobFile -match=par,ControlName=$knobName $tmpRoot.$knobName} result] {
	    return -code error "Error processing knob data: $result"
	}
	set knob($knobName.file) $tmpRoot.$knobName
	set knob($knobName.PVList) [join [exec sdds2stream -col=ControlName $tmpRoot.$knobName]]
	set knob($knobName.weightList) [join [exec sdds2stream -col=Weight $tmpRoot.$knobName]]
	APSAddToTmpFileList -ID knob -fileList $tmpRoot.$knobName
    }
    set numVisible [llength $knobList]
    if {$numVisible > 8} {
	set numVisible 8
    }
    APSScrollAdjust $scrollWidget -numVisible $numVisible   
    return $knobList
}

proc knobTick {knobName direction} {
    global knob
    if [catch {exec pvinfo $knobName} result] {
	SetStatus "$knobName does not exist, creating it now."
	CreateKnobPVs
    }
    EstablishMonitorsKnobPVs
    pv getw knob($knobName.offset)
    update
    if {$direction=="zero"} {
        set delta [expr -1.0*$knob($knobName.offset)]
    } else {
	set sign 1.0
	if {$direction=="down"} {
	    set sign -1.0
	}
	set delta [expr $knob($knobName.stepSize)*$sign*$knob($knobName.gain)]
    }
    SetStatus "delta = [format %g $delta]"
    set updating 1
    while {$updating} {
        set updating 0
        if [catch {exec cavget -list=$knob($knobName.statusPV)} status] {
            SetStatus "Error getting status for $knobName: $status"
            return
        }
        if [string match *updating* $status] {
            SetStatus "Knob still updating"
            set updating 1
        } else {
	    set updating 0
	    SetStatus "knob change status: $status"
	    break
	}
    }
    SetStatus "knob status: >$status<"
    switch -glob $status {
        "*pegged (positive)*" {
            if {$direction=="up"} {
                SetStatus "Can't change $knobName up: $status"
                return
            }
        }
        "*pegged (negative)*" {
            if {$direction=="down"} {
                SetStatus "Can't change $knobName down: $status"
                return
            }
        }
    }
    if [catch {exec pvaput -pvaName $knobName -value $delta -mode add} result] {
	SetStatus "Error changing $knobName: $result"
	return
    }
    #set knob($knobName.offset) [format %0.3f [expr $knob($knobName.offset) + $delta]]
    set updating 1
    while {$updating} {
        set updating 0
        if [catch {exec cavget -list=$knob($knobName.statusPV)} status] {
            SetStatus "Error getting status for $knobName: $status"
            return
        }
        if [string match *updating* $status] {
            SetStatus "Knob still updating"
            set updating 1
	    after 1000
        } else {
	    SetStatus "$status"
	    if [string match *good* $status] {
		SetStatus "$knobName change successfully done."
	    } else {
		SetStatus "$knobName change had problem: $status"
	    }
	    break
	}
    }
    pv getw knob($knobName.offset)
   # puts $knob($knobName.offset)
    update
}

proc ChangeGain {knobName direction} {
    global knob
    set factor 10.0
    if {$direction=="down"} {
	set factor 0.1
    }
    set knob($knobName.gain) [format %0.3f [expr $knob($knobName.gain)*$factor]]
}

proc knobFrame {knobName parent} {
    global knob
    set kWidget .[APSUniqueName k]
    set knob($knobName.widget) $parent$kWidget
   
    # Set up framing
    APSFrame $kWidget -parent $parent
    APSFrameGrid .fg -parent $parent$kWidget.frame -yList \
      {top bottom}
    set top $parent$kWidget.frame.fg.top
    set bottom $parent$kWidget.frame.fg.bottom

    # Build top frame
    APSLabeledOutput .knob -parent $top -label Knob: -width 28 \
	-textVariable knob($knobName.name) -packOption "-side left" -contextHelp \
      "This is the composite knob name as given in the config file."
    APSLabeledOutput .desc -parent $top -label Desc: -width 38 \
	-textVariable  knob($knobName.desc) -packOption "-side left" -contextHelp \
      "This is the KnobDescription parameter from the config file."
    
    # Build bottom frame
    APSButton .tickDown -parent $bottom -text "<-" -command \
      "knobTick $knobName down" -contextHelp "each click decreases the knob values by given step size" -fastClick 1
    set knob($knobName.downButton) $bottom.tickDown.button

    APSButton .zero -parent $bottom -text "0" -command \
        "knobTick $knobName zero" -contextHelp "set the knob back to the starting value" -fastClick 1
    set knob($knobName.zeroButton) $bottom.zero.button
    
    APSButton .tickUp -parent $bottom -text "->" -command \
      "knobTick $knobName up" -contextHelp "each click increase the knob values by given step size" -fastClick 1
    set knob($knobName.upButton) $bottom.tickUp.button
    
    APSLabeledOutput .offset -parent $bottom -label Offset: -width 12 \
      -textVariable knob($knobName.offset) -packOption "-side left" -contextHelp \
      "Displays running sum of knob changes."
    

    APSLabeledOutput .gain -parent $bottom -label Gain: -width 8 \
      -textVariable knob($knobName.gain) -packOption "-side left" -contextHelp \
      "This value is multiplied against the weight vector (W) and then added or subtracted from the current knob vector (V)."

    APSButton .gaindown -parent $bottom -text "/10" -command "ChangeGain $knobName down"
    APSButton .gainup -parent $bottom -text "*10" -command "ChangeGain $knobName up"
    APSButton .info -parent $bottom -text "Info..." -command "knobInfo -knobName $knobName"
    tkwait visibility $bottom.gainup
    #tkwait visibility $bottom.gain
}

set EstablishMonitorsKnobPVsDone 0
proc EstablishMonitorsKnobPVs {} {
    global knobFile offsetPVList knob knobList EstablishMonitorsKnobPVsDone
    if !$EstablishMonitorsKnobPVsDone {
	set knob(linkVarList) ""
        foreach knobName $knobList offsetPV $offsetPVList  {
            pv linkw knob($knobName.offset) $offsetPV
	    lappend knob(linkVarList)  knob($knobName.offset)
           # pv getw knob($knobName.offset)
        }
	after 1000
	pv getw $knob(linkVarList)
        SetStatus "Started monitors on knob PVs"
        set EstablishMonitorsKnobPVsDone 1
    }
}

proc CreateKnobPVs {args} {
    global knobFile offsetPVList knob knobList
    if [string length $knobFile] {
	set tmpRoot /tmp/[APSTmpString]
	set pages [exec sdds2stream -npages=bar $knobFile]
	for {set page 1} {$page<=$pages} {incr page} {
	    exec sddsconvert $knobFile $tmpRoot.$page -from=$page -to=$page
	    set knobName [exec sdds2stream -par=ControlName $tmpRoot.$page]
	    exec daq-pv-group create --input-sdds-file $tmpRoot.$page --runtime 0 --group-update-period 0.5 \
		--group-channel $knobName &
	    APSAddToTmpFileList -ID pvgroup -fileList $tmpRoot.$page
	}
	after 5000
	APSDisableButton .userFrame.create.button
	APSEnableButton .userFrame.kill.button
        EstablishMonitorsKnobPVs
	SetStatus "knob pvs created."
    }
}

proc KillKnobPVs {args} {
    
    global knobFile knob knobList offsetPVList  EstablishMonitorsKnobPVsDone
    if [string length $knobFile] {
	pv unlink $knob(linkVarList)
        exec kill_pv_group -configFile $knobFile
	after 5000
	catch {exec pkill daq-pv-group}
        foreach knobName $knobList {
            set knob($knobName.offset) ?
        }
	SetStatus "knob pvs killed."
	APSEnableButton .userFrame.create.button
	APSDisableButton .userFrame.kill.button
	set  EstablishMonitorsKnobPVsDone 0
    }
}


proc knobInfo {args} {
    set knobName ""
    APSParseArguments {knobName}
    global knobFile knob
    if ![string length $knobFile] {
	SetStatus "knobFile not provided."
	return
    }
    set infoWindow ".k${knobName}Info"
    set exists [winfo exists $infoWindow]
    if {$exists} {
        raise $infoWindow
    } else {
	set tmpRoot /tmp/[APSTmpString]
	if [catch {exec sddscasr -save $knob($knobName.file) $tmpRoot.current} result] {
	    return -code error "Error reading knob pv values: $result"
	}
	set valList [join [exec sdds2stream -col=ValueString $tmpRoot.current]]
	APSAddToTmpFileList -ID knob -fileList $tmpRoot.current
        APSWindow $infoWindow -name "KnobInfo: $knobName"
        label $infoWindow.userFrame.comp -text Components -bg Grey 
        pack $infoWindow.userFrame.comp -side top -fill x
        label $infoWindow.userFrame.tabt -text "        PV                 Weight          Current Value"
        pack $infoWindow.userFrame.tabt -side top
        set widgetList {}
        set nameList $knob($knobName.PVList)
        regsub -all \\. $nameList "" nameList0
        set weightList $knob($knobName.weightList)
        foreach item $nameList0 {
            lappend widgetList "k$item"
        }
        APSScroll .si -parent $infoWindow.userFrame
        set scrollParent $infoWindow.userFrame.si.frame.canvas.frame
        set i 0
        foreach item $nameList val $valList {
            global $item
            set widgetPart [lindex $nameList0 $i]
            set formattedItem "formatted$item"
            global $formattedItem
            set $formattedItem [format "%6e" $val]
            APSFrameGrid .fg$widgetPart -parent $scrollParent -xList {pv w v}
            set weight [lindex $weightList $i]
            set widgetName [lindex $widgetList $i]
            label $scrollParent.fg$widgetPart.pv.$widgetName -text $item -width 29
            pack $scrollParent.fg$widgetPart.pv.$widgetName -side top
            label $scrollParent.fg$widgetPart.w.$widgetName -text \
              [format "%6e" $weight] -width 12
            pack $scrollParent.fg$widgetPart.w.$widgetName -side top
            label $scrollParent.fg$widgetPart.v.$widgetName -textvariable \
              $formattedItem -width 24
            pack $scrollParent.fg$widgetPart.v.$widgetName -side top
            #	    tkwait visibility $scrollParent.fg$widgetPart.v.$widgetName
            incr i
        }
        tkwait visibility $scrollParent
        APSScrollAdjust $infoWindow.userFrame.si -numVisible 15
    }
}

set configFileDir .
proc openDialog {args} {
    global knobFile
    global configFileDir
    if [string length $knobFile] {
	set configFileDir [file dir $knobFile]
    }
    set knobFile \
	[APSFileSelectDialog .openDialog -listDir $configFileDir \
	     -contextHelp "Select a knob configuration file" \
	     -title "Select Knob Configuration File:"]
    if [file exist $knobFile] {
	openConfigFile $knobFile
    }
}

#
# Set up graphical interface for tclKnobs
#
set status ""
set knobList ""
APSApplication . -name "pvgroupKnobs" -version $CVSRevisionAuthor \
  -overview "Assigns weighted vectors of process variables to tcl buttons. Weights and pv names are specified via an SDDS knobconfig file." \
  -contextHelp "Assigns weighted vectors of process variables to tcl buttons."

# Add Open... entry to File menu
.menu.file.menu insert 1 command -label "Open..." -command openDialog

APSScrolledStatus .ss -parent .userFrame -textVariable status \
  -packOption "-side top -fill x" -height 4 -width 60 \
  -contextHelp "Provides execution status and operation hints."
SetStatus "Working..."

APSScroll .sk -parent .userFrame
set scrollWidget .userFrame.sk


# Set ourself up as an RPC server at a well known port
tkwait visibility $scrollWidget


# Open up config file if given on command line
set createNeeded 1
if {[string length $knobFile]} {
    if ![file exists $knobFile] {
        SetStatus "Not found: $knobFile"
    } else {
        openConfigFile $knobFile
        #check if knob PV exist, if not add buttons to create and kill them
        set error 0
        
        set createNeeded 0
        foreach pv $knobList {
            if [catch {exec pvinfo $pv} result] {
                SetStatus "knob $pv does not exist, adding buttons to create/kill knobs."
                set createNeeded 1
                break
            }
        }
    }
} else {
    set status "Select a knob configuration from File/Open menu."
}

if !$createNeeded {
    APSButton .create -parent .userFrame -text "Create Knob PVs" -command CreateKnobPVs
    APSButton .kill -parent .userFrame -text "Kill Knob PVs" -command KillKnobPVs
    APSDisableButton .userFrame.create.button
    EstablishMonitorsKnobPVs
} else {
    APSButton .create -parent .userFrame -text "Create Knob PVs" -command CreateKnobPVs
    APSButton .kill -parent .userFrame -text "Kill Knob PVs" -command KillKnobPVs
    APSDisableButton .userFrame.kill.button
}

SetStatus "Ready."
