
proc APSMpRespMatrixDoExperiment {args} {
    global actuator

    set templateFile ""
    
    APSStrictParseArguments {actuatorList templateFile}
    
    if ![llength $actuatorList] {
        APSMpReturn error "error in APSMpRespMatrixDoExperiment: no actuators specified."
    }
    
    if ![string length $templateFile] {
        APSMpReturn error "error in APSMpRespMatrixDoExperiment: no templateFile given."
    }
    
    foreach actuator $actuatorList {
        set actuatorName $actuator
        set actuator [APSMpRespMatrixRemoveColons $actuator]
        global $actuator
        if {[file exists $actuatorName.sdds]} {
            file rename -force $actuatorName.sdds $actuatorName.sdds~ 
        }
        if ![array exists $actuator] {
            APSMpReturn error "error in APSMpRespMatrixDoExperiment: global array $actuator does not exist."
        }
        if ![info exists ${actuator}(halfRange)] {
            APSMpReturn error "error in APSMpRespMatrixDoExperiment: variable ${actuator}(halfRange) not defined."
        }
        if ![info exists ${actuator}(points)] {
            APSMpReturn error "error in APSMpRespMatrixDoExperiment: variable ${actuator}(points) not defined."
        }
        if ![info exists ${actuator}(postChange)] {
            APSMpReturn error "error in APSMpRespMatrixDoExperiment: variable ${actuator}(postChange) not defined."
        }
        if ![info exists ${actuator}(intermeasurement)] {
            APSMpReturn error "error in APSMpRespMatrixDoExperiment: variable ${actuator}(intermeasurement) not defined."
        }
        set bigUglyOption -replace=$actuatorName,$${actuator}(PV),$${actuator}(halfRange),$${actuator}(points),$${actuator}(postChange),$${actuator}(intermeasurement),$${actuator}(average)
        eval exec replaceText $templateFile $actuatorName.exp \
          -orig=<actuator>,<PV>,<halfRange>,<points>,<postChange>,<intermeasurement>,<average> \
          [subst $bigUglyOption]
        set ExperimentDone 0
        catch {file delete $actuatorName.def $actuatorName.sdds}
        APSExecLog .exp$actuator  -unixCommand "sddsexperiment $actuatorName.exp -verb -summ "\
          -callback {global ExperimentDone; set ExperimentDone 1} 
        .exp$actuator.userFrame.text.text configure -width 95
        tkwait variable ExperimentDone
    }
}

proc APSMpRespMatrixCalcSlopes {args} {
    
    APSStrictParseArguments {actuatorList}
    
    foreach actuator $actuatorList {
        catch {file delete $actuator.slopes}
        if [file exists $actuator.sdds] {
            if [catch {exec sddsconvert $actuator.sdds -pipe=out -dele=col,*Time*,StDev*,Sigma*,*sum \
                         | sddsslopes -pipe=in $actuator.slopes \
                         -independentVariable=$actuator -exclude=$actuator -asci} result] {
                return -code error "APSMpRespMatrixCalcSlopes: $result"
            }
            if ![file exists $actuator.slopes] {
                return -code error "APSMpRespMatrixCalcSlopes: $actuator.slopes not created."
            }
        } else {
            return -code error "APSMpRespMatrixCalcSlopes: Not found: $actuator.sdds"
        }
    }
}

proc APSMpRespMatrixFormMatrix {args} {

    set actuatorList {}
    set badBpms ""
    set badActuators ""
    set outFile ""
    set beamlineFile ""
    set filterColumns "*:*P*"
    # the following is an illegal name for an SDDS data element, so it is safe
    # to use as a -delete=column default.
    set excludeColumns "1DUMMY"

    APSStrictParseArguments {actuatorList badBpms badActuators outFile beamlineFile filterColumns excludeColumns}
    if ![string length $excludeColumns] {
        set excludeColumns "1DUMMY"
    }

    if ![llength $actuatorList] {
        APSMpReturn error "error in formMatrix: no actuators specified"
    }

    if ![string length $outFile] {
        APSMpReturn error "error in formMatrix: no outFile given"
    }

    set fileList [eval glob [APSAffix $actuatorList -suffix .slopes]]
   
    eval exec sddscombine $fileList $outFile.trans -merge -overWrite
    eval file delete $fileList
    
    if [llength $badBpms] {
        exec sddsconvert $outFile.trans -delete=col,[join ${badbpms} ","]
    }

    if [file exists ${beamlineFile}] {
        exec sddsconvert $outFile.trans -pipe=out \
          -rename=col,IndependentVariable=ActuatorNames \
          -dele=col,*Intercept -edit=col,*Slope,%/Slope// \
          | sddsconvert -pipe -retain=col,ActuatorNames,$filterColumns \
          -delete=col,$excludeColumns \
          | sddstranspose -pipe -oldcolumn=BPMNames \
          | forceCausality -pipe=in $outFile -beamline=${beamlineFile}
    } else {
        exec sddsconvert $outFile.trans -pipe=out \
          -rename=col,IndependentVariable=ActuatorNames \
          -dele=col,*Intercept -edit=col,*Slope,"%/Slope//" \
          | sddsconvert -pipe -retain=col,ActuatorNames,$filterColumns \
          -delete=col,$excludeColumns \
          | sddstranspose -pipe=in $outFile -oldcolumn=BPMNames
    }
    file delete $outFile.trans
    if [llength $badActuators] {
        exec sddsconvert $outFile -delete=col,[join ${badbpms} ","]
    }
}

proc APSMpRespMatrixCalcInverse {args} {
    set minimum 1e-2

    APSStrictParseArguments {inFile outFile minimum}

    exec sddspseudoinverse $inFile $outFile \
      -minimum=$minimum -oldColumnNames=ActuatorNames -asci 
}

proc APSMpRespMatrixSetParameterDefaults {args} {
    global parameterList

    APSParseArguments parameterList
    APSParseArguments $parameterList
    foreach Parameter $parameterList {
        if [info exists $Parameter] {
            global ${Parameter}Default
            set ${Parameter}Default [subst $$Parameter]
        }
    }
}

proc APSMpRespMatrixRemoveColons {word} {
    return "[join [split $word :] {}]"
}

proc APSMpRespMatrixSetActuatorParameters {args} {
    global parameterList

    APSParseArguments {actuator}
    
    global [set actuator [APSMpRespMatrixRemoveColons $actuator]]

    foreach Parameter $parameterList {
        global ${Parameter}Default
        if {[info exists ${Parameter}Default] && 
            ![info exists ${actuator}($Parameter)]} {
            set ${actuator}($Parameter) [subst $${Parameter}Default]
        }
    }

    APSParseArguments $parameterList
    foreach Parameter $parameterList {
        if {![info exists $Parameter]} {
            set $Parameter [subst $${Parameter}Default]
        }
        set ${actuator}($Parameter) [subst $${Parameter}]
    }
}

proc APSMpRespMatrixSetActuatorParametersByDialog {widget args} {
    global parameterList
    set parent ""

    APSStrictParseArguments {parent actuator}
    
    global [set actuator [APSMpRespMatrixRemoveColons $actuator]]

    APSDialogBox $widget -parent $parent -name "Set parameters" -contextHelp "Allows one to make changes to the experiment parameters."
    label $parent$widget.userFrame.label -text "Enter parameters for sddsexperiment on $actuator."
    pack $parent$widget.userFrame.label

    foreach Parameter $parameterList {
        global ${Parameter}Default
        if {[info exists ${actuator}($Parameter)]} {
            APSLabeledEntry .le$Parameter -parent $parent$widget.userFrame \
              -label $Parameter -textVariable ${actuator}($Parameter)
        }
    }
    APSEnableButton  $parent$widget.buttonRow.ok.button
    tkwait window $parent$widget
}

proc APSMpRespMatrixInstallDialog {widget args} {
    set parent ""
    set fileName ""
    set installName ""
    set installDir ""
    APSStrictParseArguments {parent fileName installName installDir}

    APSDialogBox $widget -parent $parent -name "Install matrix" \
      -contextHelp "Allows one to install new matrix."
    global comment
    set comment ""
    label $parent$widget.userFrame.label -text "Enter comment for new matrix file"
    pack $parent$widget.userFrame.label
    APSLabeledEntry .installfilename -parent $parent$widget.userFrame \
              -label "Install Filename" -textVariable fileName -width 60

    APSLabeledEntry .installdir -parent $parent$widget.userFrame \
              -label "Install Directory" -textVariable installDir -width 60

    APSLabeledEntry .installname -parent $parent$widget.userFrame \
              -label "Install Name" -textVariable installName -width 60

    APSLabeledEntry .comment -parent $parent$widget.userFrame \
              -label Comment -textVariable comment -width 60

    APSButton .install -parent $parent$widget.buttonRow -text Install \
      -command "APSMpRespMatrixInstall  -fileName $fileName \
      -installName $installName -installDir $installDir -comment \"$comment\"; destroy $parent$widget" \
        -contextHelp "Installs the newly measured matrix for controllaw usage."

    # why is "ok" button disabled?
    APSDisableButton $parent$widget.buttonRow.ok.button
    tkwait window $parent$widget
}

proc APSMpRespMatrixInstall {args} {
    global status
    set installDir .
    set installName ""
    set fileName ""
    set comment ""
    APSStrictParseArguments {fileName installName installDir comment}
    if ![string length $installName] {
        APSMpReturn error "No installName provided."
    }

    #determine last version of file in the install directory
    set saveDir [pwd]
    cd $installDir
    set newFilename [lindex [APSMpSerialNumbers $installName] 1]

    if {[file exists $installName] && [catch {file delete $installName} result]} {
        APSMpReturn error "APSMpRespMatrixInstallDialog: $result"
    }
    if [catch {exec sddsprocess measurement/$fileName -nowarnings \
                 "-print=para,Comment,$comment" \
             } result ] {
        APSMpReturn error "APSMpRespMatrixInstallDialog: $result"
    }
    catch {file delete measurement/${fileName}~}
    file copy measurement/$fileName $newFilename
    exec ln -s $newFilename $installName
    APSInfoWindow .info -infoMessage "New file $installName installed." -modal 1
    cd $saveDir
}

#
# Given full path to a file, search for next free serial number,
# and return two things. First, the latest found serial number
# (or -1 if none found). Secondly, a new path to next serial numbered
# file.
#
proc APSMpSerialNumbers {path} {
    set dir [file dirname $path]
    set file [file tail $path]
    set separator -
    set digits 4
    set fileList [glob -nocomplain ${path}${separator}0*]

    set maxSerial [format "%0${digits}d" 0]
    foreach f $fileList {
        set serialNo -1
        regexp "\[$separator\](\[0-9\]+)" $f match serialNo
        if {$serialNo > $maxSerial} {
            set maxSerial $serialNo
        }
    }
    if {![string compare $maxSerial "0000"]} {
        set firstReturn -1
        set secondReturn "${path}${separator}[format "%0${digits}d" 1]"
    } else {
        set firstReturn ${path}${separator}$maxSerial
        set nextSerial [format "%0${digits}d" [expr $maxSerial + 1]]
        set secondReturn ${path}${separator}$nextSerial
    }
    return [list $firstReturn $secondReturn]
}
