
proc APScaWaveHelp {} {
    return "APScaWave -pvList <list> -variableList <list> -amplitudeList <list> \[-period <seconds>\] \[-decayConstant 1/periods\] \[-periods <number>\] \[-stepsPerPeriod <number>\] \[-dryRun 1\] \[-postStepCallback <procName>\] \[-abortVariable <variableName>\] \[-function {sin | cos | twCenter | random}\] \[-resetOnAbort 1\]"
}

proc APScaWave {args} {
    global errorCode
    set pvList {}
    set variableList {}
    set amplitudeList {}
    set period 60
    set decayConstant 0
    set periods 1
    set stepsPerPeriod 4
    set dryRun 0
    set postStepCallback ""
    set abortVariable ""
    set function sin
    set resetOnAbort 0
    set integerList {}
    APSStrictParseArguments {pvList variableList amplitudeList period decayConstant periods \
                     stepsPerPeriod dryRun postStepCallback abortVariable function \
                     resetOnAbort integerList}
    # check arguments for reasonable values and consistency
    set pvNames [llength $pvList]
    if {!$pvNames} {
        return -code error -errorinfo "No pvNames for APScaWave" 
    }
    if {$stepsPerPeriod<1} {
        return -code error -errorinfo "-stepsPerPeriod less than 1 for APScaWave" 
    }
    if {$period<1} {
        return -code error -errorinfo "-period is less than 1 for APScaWave" 
    }
    if $decayConstant<0 {
        return -code error -errorinfo "-decayConstant is less than 0 for APScaWave"
    }
    if {$periods<1} {
        return -code error -errorinfo "-periods is less than 1 for APScaWave" 
    }
    if {$pvNames!=[llength $amplitudeList] || $pvNames!=[llength $variableList]} {
        return -code error -errorinfo "amplitudeList, pvList, and/or variableList are of different lengths" 
    }
    foreach elem $variableList {
        global $elem
    }
    if $dryRun {
        puts stderr "Waving..."
    }
    # link to the process variables
    if {[pv linkw $variableList $pvList]!=0} {
        return -code error "Error finding PVs: $errorCode"
    }
    if ![llength $integerList] {
        set integerList [APSReplicateItem -item 0 -number [llength $pvList]]
    } else {
        if [llength  $integerList]!=[llength $pvList] {
            return -code error -errorinfo "integerList is wrong length"
        }
    }
    # read and store initial values
    set beginList {}
    for {set index 0} {$index<$pvNames} {incr index} {
        set variable [lindex $variableList $index]
        if [pv getw $variable]!=0 {
            return -code error "Error reading [lindex $pvList $index]: $errorCode"
        }
        lappend beginList [subst \$$variable]
    }
    set phase 0
    set pi [expr 4*atan(1.0)]
    set dPhase [expr 2*$pi/$stepsPerPeriod]
    set steps [expr $periods*$stepsPerPeriod]
    set pause [expr (1.0*$period)/$stepsPerPeriod]
    if [string length $abortVariable] {
        global $abortVariable
    }
    for {set step 0} {$step<=$steps} {incr step} {
        switch $function {
            sin {
                set factor [expr sin($phase)]
            }
            cos {
                set factor [expr cos($phase)]
            }
            random {
                if $step==0 {
                    set randomList [APSGetRandomNumbers -n [expr $steps+1] \
                                      -type uniform]
                }
                set factor [expr 2*([lindex $randomList $step]-0.5)]
            }
            twCenter {
                set normPhase [expr 2*$phase/$pi]
                set region [expr int($normPhase)%4]
                switch $region {
                    0 {
                        set factor [expr $normPhase-int($normPhase)]
                    }
                    1 {
                        set factor [expr 1-($normPhase-int($normPhase))]
                    }
                    2 {
                        set factor [expr -($normPhase-int($normPhase))]
                    }
                    3 {
                        set factor [expr ($normPhase-int($normPhase))-1]
                    }
                    default {
                        return -code error "APScaWave: Invalid region $region"
                    }
                }
            }
        }
        set factor [expr $factor*exp(-1.0*$step*$decayConstant/(1.0*$stepsPerPeriod))]
        for {set index 0} {$index<$pvNames} {incr index} {
            set value [expr [lindex $beginList $index]+[lindex $amplitudeList $index]*$factor]
            if [lindex $integerList $index] {
                set value [expr int($value)]
            }
            if !$dryRun {
                set [lindex $variableList $index] $value
                if [pv putw [lindex $variableList $index]]!=0 {
                    return -code error "APScaWave: Setting [lindex $pvList $index]: $errorCode"
                }
            } else {
                puts stderr "Pretending to set [lindex $pvList $index] to $value"
            }
        }
        if [string length $postStepCallback] {
            eval $postStepCallback $step $steps
        }
        if {[string length $abortVariable] && [subst \$$abortVariable]} {
            if !$resetOnAbort {
                return -code ok Aborted
            }
            for {set index 0} {$index<$pvNames} {incr index} {
                set [lindex $variableList $index] [lindex $beginList $index]
                if [pv putw [lindex $variableList $index]]!=0 {
                    return -code error "APScaWave: Setting [lindex $pvList $index]: $errorCode"
                }
            }
            return -code ok "Aborted"
        }
        set phase [expr $phase+$dPhase]
        set leftToWait [expr int($pause*1000)]
        while {$leftToWait>0} {
            if $leftToWait>1000 {
                after 1000
            } else {
                after $leftToWait
            }
            if [string length [info command update]] {
                update
            }
            if {[string length $abortVariable] && [subst \$$abortVariable]} {
                if !$resetOnAbort {
                    return -code ok "Aborted"
                }
                for {set index 0} {$index<$pvNames} {incr index} {
                    set [lindex $variableList $index] [lindex $beginList $index]
                    if [pv putw [lindex $variableList $index]]!=0 {
                        return -code error "APScaWave: Setting [lindex $pvList $index]: $errorCode"
                    }
                }
                return -code ok Aborted
            }
            incr leftToWait -1000
        }
    }
    return -code ok
}

proc APSGetRandomNumbersHelp {} {
    return "APSGetRandomNumbers -n <number> -type {uniform|gaussian}"
}

proc APSGetRandomNumbers {args} {
    set n 0
    set type uniform
    APSStrictParseArguments {n type}

    if !$n {
        return ""
    }
    switch $type {
        uniform {
            set function rnd
        }
        gaussian {
            set function grnd
        } 
        default {
            return -code error "APSGetRandomNumbers: unknown type: specify uniform or gaussian"
        }
    }
    if [catch {exec sddssequence -pipe=out -define=index,type=long -sequence=begin=1,end=$n,number=$n \
                 | sddsprocess -pipe "-define=column,RN,$function,type=double" \
                 | sdds2stream -pipe -column=RN} RNList] {
        return -code error "APSGetRandomNumbers: $RNList"
    }
    return $RNList
}
