#!/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)]

#
# $Log: not supported by cvs2svn $
# Revision 1.28  2012/04/06 16:33:15  soliday
# It now displays the time stamp down to .001 seconds.
#
# Revision 1.27  2011/01/17 20:04:41  soliday
# Fixed a problem loading files with corrupt ControlNameIndex values.
#
# Revision 1.26  2005/04/26 04:03:10  borland
# Added clock format argument to give 24-hour time.
#
# Revision 1.25  2005/04/26 03:49:35  borland
# Added -nowarning to an instance of sddsprocess.
#
# Revision 1.24  2005/01/20 22:02:24  soliday
# Added an Export function. The Export dialog allows the user to filter on
# time and output column names. These default to the time selected on the
# main PVHistoryTool screen and the selected PV names.
#
# Revision 1.23  2004/11/23 20:14:21  borland
# Added -nowarning to an instance of sddsprocess.
#
# Revision 1.22  2004/09/15 19:31:11  soliday
# The review and restore features now use the Include and Exclude PV name
# filters.
#
# Revision 1.21  2004/07/13 15:32:22  soliday
# Added a way to exclude PVs using lists of wildcard strings. Also the include
# filter now allows a list of comma and/or space separated strings.
#
# Revision 1.20  2004/02/12 18:03:25  soliday
# Fixed an error message if a user tries to display the PVs prior to selecting a system.
#
# Revision 1.19  2004/02/09 20:01:53  soliday
# Added the ability to configure this application with an SDDS file.
#
# Revision 1.18  2004/02/09 17:10:59  soliday
# Added the Strip Initial Values option so that only changes are shown.
#
# Revision 1.17  2004/01/28 17:33:35  soliday
# Fixed problem when the log file contains more than one page.
#
# Revision 1.16  2004/01/07 17:37:34  borland
# Added EPS system.
#
# Revision 1.15  2003/12/19 17:41:21  soliday
# Updated so that it tries to recover from corrupt files due to partially written
# files.
#
# Revision 1.14  2003/12/17 22:53:32  soliday
# Added the RunControl data logger.
#
# Revision 1.13  2003/11/21 22:48:44  soliday
# Added the BRF and SRF systems. I also changed it so that it does not
# load the Linac system by default.
#
# Revision 1.12  2003/11/14 05:35:49  soliday
# Added the tksddsplot option.
#
# Revision 1.11  2003/11/11 21:12:39  soliday
# Fixed bug when logonchange datalogger has been restarted during the day and
# the time filter only uses one file.
#
# Revision 1.10  2002/06/14 18:51:01  borland
# Added SBPMs system.
#
# Revision 1.9  2002/06/11 18:36:24  soliday
# Increased speed by a little bit.
#
# Revision 1.8  2002/05/31 18:21:51  soliday
# It no longer plots past the current time into the future.
#
# Revision 1.7  2002/05/30 19:34:48  soliday
# Reduced the amount of memory needed to run the program.
#
# Revision 1.6  2002/05/21 21:32:00  soliday
# Added the split plot option.
#
# Revision 1.5  2002/04/12 15:39:40  soliday
# I exteneded the plot out to include a point at the end of the time frame.
#
# Revision 1.4  2002/04/11 20:59:50  soliday
# Fixed some bugs if the input files have different PV names.
#
# Revision 1.3  2002/04/11 19:15:18  soliday
# Fixed some bugs and improved speed on large files.
#
# Revision 1.2  2002/04/09 20:17:13  soliday
# Increased the speed of execution on large files.
#
# Revision 1.1  2002/04/01 22:35:30  soliday
# First version.
#
#
#

set PVLabel "ControlName"

proc MakeDateTimeFrame {widget args} {
    set parent .
    APSStrictParseArguments {parent}
    set label "Date/Time Range of Interest"
    
    APSFrame $widget -parent $parent -label $label
    set w $parent$widget.frame
    
    APSDateTimeAdjEntry .startDate -parent $w \
      -yearVariable StartYear \
      -monthVariable StartMonth \
      -dayVariable StartDay \
      -hourVariable StartHour \
      -label "Starting date/time (year, month, day, hour): " -defaultHour 0 
    APSDateTimeAdjEntry .endDate -parent $w \
      -yearVariable EndYear \
      -monthVariable EndMonth \
      -dayVariable EndDay \
      -hourVariable EndHour \
      -label "Ending date/time (year, month, day, hour):   " -defaultHour 24 

    SetDateTimeToToday -rootname Start -hour 00:00:00
    SetDateTimeToToday -rootname End  -hour 23:59:59
}
proc SetDateTimeToToday {args} {
    set rootname ""
    set hour 0
    APSStrictParseArguments {rootname hour}

    global ${rootname} Month ${rootname}Year ${rootname}Day ${rootname}Hour
    
    APSDateBreakDown -dayVariable ${rootname}Day -yearVariable ${rootname}Year \
      -monthVariable ${rootname}Month -twoDigitYear 0 -leadingZeros 0
    set ${rootname}Hour $hour
}

proc ShowInfo {args} {
    APSSetVarAndUpdate status "Displaying info"

    global StartYear StartDay StartMonth StartHour 
    global EndYear EndDay EndMonth EndHour stripInitial
    
    if {[catch {GetTimeStamp -year $StartYear -month $StartMonth \
                  -day $StartDay -hour $StartHour -hourvar StartHour} startTime]} {
        APSSetVarAndUpdate status "$startTime for Starting date/time"
        return
    }
    if {[catch {GetTimeStamp -year $EndYear -month $EndMonth \
                  -day $EndDay -hour $EndHour -hourvar EndHour} endTime]} {
        APSSetVarAndUpdate status "$endTime for Ending date/time"
        return
    }
    if {($endTime < $startTime)} {
        APSSetVarAndUpdate status "Warning: Start date/time is after End date/time"
        return
    }


    global lb fileCount PVLabel
    set selIndex [$lb curselection]
    destroy .info
    APSDialogBox .info -name "PV History Display"
    destroy .info.buttonRow.cancel
    APSDialogBoxAddButton .email \
      -parent .info \
      -text "EMail" \
      -command "APSScrolledTextEMail -textWidget .info.userFrame.info.text"
    APSScrolledText .info \
      -parent .info.userFrame \
      -width 80 \
      -name "Logged PV Value Changes"
    .info.userFrame.info.text insert end "${PVLabel}\t\tValue\t\t\tTime\n"

    
    foreach index $selIndex {
        set valueList($index) ""
        set timeList($index) ""
    }
    global files
    for {set c 0} {$c < $fileCount} {incr c} {
        global pages$c
        set file [lindex $files $c]
        for {set p 0} {$p < [set pages$c]} {incr p} {
            global controlnames$c$p n_controlnames$c$p CN$c$p
            set page [expr {$p + 1}]
            set cIndexOptions ""
            set first 1
            foreach index $selIndex {
                set name [$lb get $index]
                set cIndex [lsearch -exact [set CN$c$p] $name]
                if {$cIndex < 0} continue
                set selIndexList($cIndex) $index
                
                if {$first} {
                    append cIndexOptions "ControlNameIndex,[expr {$cIndex - .1}],[expr {$cIndex + .1}]"
                    set first 0
                } else {
                    append cIndexOptions ",ControlNameIndex,[expr {$cIndex - .1}],[expr {$cIndex + .1}],|"
                }
            }
            if {$stripInitial} {
                set stripOptions "-filter=column,PreviousRow,-2.5,-.1.5,!"
            } else {
                set stripOptions ""
            }
            if {[llength $cIndexOptions]} {
                if {[catch {eval exec sddsconvert $file -pipe=out -fromPage=$page -toPage=$page \
                              | sddsprocess -nowarning -pipe -filter=column,$cIndexOptions \
                              -filter=column,Time,${startTime},$endTime $stripOptions \
                              | sdds2stream -pipe=in -col=ControlNameIndex,Value,Time} results]} {
                    APSSetVarAndUpdate status "error: $results"
                    bell
                    return
                }
                foreach "cIndex value sec" $results {
                    lappend valueList($selIndexList($cIndex)) $value
                    #		    lappend timeList($selIndexList($cIndex)) [expr round($sec)]
                    lappend timeList($selIndexList($cIndex)) $sec
                }
            }
        }
    }
    foreach index $selIndex {
        set name [$lb get $index]
        .info.userFrame.info.text insert end "$name\n"
        foreach value $valueList($index) sec $timeList($index) {
            .info.userFrame.info.text insert end "\t\t\t[format "%-20s" $value]"
            .info.userFrame.info.text insert end "\t[clock format [expr int($sec)] -format {%a %D %T}].[format %03d [expr round(($sec - int($sec)) * 1000)]]\n"
        }
    }
    
    APSSetVarAndUpdate status "Done displaying info"
}

proc PlotInfo {args} {
    APSSetVarAndUpdate status "Plotting data"

    global lb fileCount StartYear StartDay StartMonth StartHour EndYear EndDay EndMonth EndHour split stripInitial
    
    if {[catch {GetTimeStamp -year $StartYear -month $StartMonth \
                  -day $StartDay -hour $StartHour -hourvar StartHour} startTime]} {
        if {[catch {clock scan "${StartMonth}/${StartDay}/${StartYear}"} startTime]} {
            set startTime 0
        }
    }
    if {[catch {GetTimeStamp -year $EndYear -month $EndMonth \
                  -day $EndDay -hour $EndHour -hourvar EndHour} endTime]} {
        if {[catch {clock scan "${EndMonth}/${EndDay}/${EndYear} 23:59:59"} endTime]} {
            set endTime 1e100
        }
    }

    set selIndex [$lb curselection]
    foreach index $selIndex {
        set valueList($index) ""
        set timeList($index) ""
    }
    global files
    for {set c 0} {$c < $fileCount} {incr c} {
        global pages$c
        set file [lindex $files $c]
        for {set p 0} {$p < [set pages$c]} {incr p} {
            global controlnames$c$p n_controlnames$c$p CN$c$p
            set page [expr {$p + 1}]
            set cIndexOptions ""
            set first 1
            foreach index $selIndex {
                set name [$lb get $index]
                set cIndex [lsearch -exact [set CN$c$p] $name]
                if {$cIndex < 0} continue
                set selIndexList($cIndex) $index
                
                if {$first} {
                    append cIndexOptions "ControlNameIndex,[expr {$cIndex - .1}],[expr {$cIndex + .1}]"
                    set first 0
                } else {
                    append cIndexOptions ",ControlNameIndex,[expr {$cIndex - .1}],[expr {$cIndex + .1}],|"
                }
            }
            if {$stripInitial} {
                set stripOptions "-filter=column,PreviousRow,-2.5,-.1.5,!"
            } else {
                set stripOptions ""
            }
            if {[llength $cIndexOptions]} {
                if {[catch {eval exec sddsconvert $file -pipe=out -fromPage=$page -toPage=$page | sddsprocess -pipe -filter=column,$cIndexOptions -filter=column,Time,${startTime},$endTime $stripOptions -nowarning | sdds2stream -pipe=in -col=ControlNameIndex,Value,Time} results]} {
                    APSSetVarAndUpdate status "error: $results"
                    bell
                    return
                }
                foreach "cIndex value sec" $results {
                    lappend valueList($selIndexList($cIndex)) $value
                    lappend timeList($selIndexList($cIndex)) $sec
                }
            }
        }
    }
    set currTime [clock seconds]
    foreach index $selIndex {
        set name [$lb get $index]
        lappend outData(ArrayNames) $name Time_$name
        set outData(ArrayInfo.$name) "type SDDS_DOUBLE"
        set outData(ArrayInfo.Time_$name) "type SDDS_DOUBLE"
        append options "-arraynames=Time_$name,($name) "
        if {$split} {
            append options "-end "
        }
        set outData(Array.$name) ""
        set outData(Array.Time_$name) ""
        set n 0
        foreach value $valueList($index) sec $timeList($index) {
            if {$n > 0} {
                lappend outData(Array.$name) $n $preValue
                lappend outData(Array.Time_$name) $n $sec
                incr n
            }
            set preValue $value
            lappend outData(Array.$name) $n $value
            lappend outData(Array.Time_$name) $n $sec
            incr n
        }
        lappend outData(Array.$name) $n [lindex $valueList($index) end]
        if {$currTime < $endTime} {
            lappend outData(Array.Time_$name) $n $currTime
        } else {
            lappend outData(Array.Time_$name) $n $endTime
        }
        set outData(Array.$name) [list $outData(Array.$name)]
        set outData(Array.Time_$name) [list $outData(Array.Time_$name)]
    }
    global tmpfile tkplot
    if {$tkplot} {
        set command tksddsplot
    } else {
        set command sddsplot
    }
    if {[llength $selIndex]} {
        file delete $tmpfile
        sdds save $tmpfile outData
        eval exec $command -scales=${startTime},${endTime},0,0 -graph=line,vary -ticks=xtime -legend $tmpfile $options &
    }
    APSSetVarAndUpdate status "Done plotting data"
}

proc Show {args} {
    APSSetVarAndUpdate status "Displaying PVs"
    global controlnamesList filtered fileCount OAGGlobal PVLabel
    set controlnamesList ""
    set controlnamesListTmp ""
    if {$filtered} {
        global StartYear StartDay StartMonth StartHour 
        global EndYear EndDay EndMonth EndHour
        
        if {[catch {GetTimeStamp -year $StartYear -month $StartMonth \
                      -day $StartDay -hour $StartHour -hourvar StartHour} startTime]} {
            APSSetVarAndUpdate status "$startTime for Starting date/time"
            return
        }
        if {[catch {GetTimeStamp -year $EndYear -month $EndMonth \
                      -day $EndDay -hour $EndHour -hourvar EndHour} endTime]} {
            APSSetVarAndUpdate status "$endTime for Ending date/time"
            return
        }
        if {($endTime < $startTime)} {
            APSSetVarAndUpdate status "Warning: Start date/time is after End date/time"
            return
        }

        global files
        for {set c 0} {$c < $fileCount} {incr c} {
            global pages$c
            set file [lindex $files $c]
            for {set p 0} {$p < [set pages$c]} {incr p} {
                global controlnames$c$p n_controlnames$c$p
                set page [expr {$p + 1}]
                if {[set pages$c] == 1} {
                    if {[catch {exec sddsprocess $file -pipe=out "-filter=column,PreviousRow,-3,-.1,!" "-filter=column,Time,$startTime,$endTime" -nowarn | sdds2stream -pipe=in -column=ControlNameIndex} indexes]} {
                        APSSetVarAndUpdate status "Error: $indexes"
                        bell
                        return
                    }
                } else {
                    if {[catch {exec sddsconvert $file -pipe=out -fromPage=$page -toPage=$page -retain=column,ControlNameIndex,PreviousRow,Time | sddsprocess -pipe "-filter=column,PreviousRow,-3,-.1,!"  "-filter=column,Time,$startTime,$endTime" -nowarn | sdds2stream -pipe=in -column=ControlNameIndex} indexes]} {
                        APSSetVarAndUpdate status "Error: $indexes"
                        bell
                        return
                    }
                }
                array unset numbers
                foreach index $indexes {
                    if {($index < 0) || ([set n_controlnames$c$p] < $index)} { continue }
                    if {![info exists numbers($index)]} {
                        set numbers($index) 1
                        lappend controlnamesListTmp [set controlnames${c}${p}($index)]
                    }
                }
            }
        }
    } else {
        for {set c 0} {$c < $fileCount} {incr c} {
            global pages$c
            for {set p 0} {$p < [set pages$c]} {incr p} {
                global controlnames$c$p n_controlnames$c$p
                for {set i 0} {$i < [set n_controlnames$c$p]} {incr i} {
                    lappend controlnamesListTmp [set controlnames${c}${p}($i)]
                }
            }
        }
    }
    set controlnamesListTmp [lsort -dictionary -unique $controlnamesListTmp]
    global filterString excludeFilterString lastSystem categories beamlines
    set catOptions ""
    set beamOptions ""
    set first 1
    foreach category $categories {
        global $category
        if {[set $category]} {
            if {$first} {
                set catOptions "-match=column,Category=$category"
                set first 0
            } else {
                append catOptions ",Category=$category,|"
            }
        }
    }
    set first 1
    foreach beamline $beamlines {
        global $beamline
        if {[set $beamline]} {
            if {$first} {
                set beamOptions "-match=column,Beamline=$beamline"
                set first 0
            } else {
                append beamOptions ",Beamline=$beamline,|"
            }
        }
    }
    if {([llength $catOptions]) || ([llength $beamOptions])} {
        if {[catch {eval exec sddsprocess [file join $OAGGlobal(LogOnChangeDirectory) $lastSystem $lastSystem.loc] -pipe=out $catOptions $beamOptions | sdds2stream -pipe=in -column=${PVLabel}} names]} {
            APSSetVarAndUpdate status "error: $names"
            return
        }
        set tmp2 $controlnamesListTmp
        set controlnamesListTmp ""
        foreach tmp $tmp2 {
            if {[lsearch -exact $names $tmp] != -1} {
                lappend controlnamesListTmp $tmp
            }
        }
    }
    if {![llength $filterString]} {
        set filterString "*"
    }
    set filterString [split $filterString " ,"]
    set filterStringTmp ""
    foreach fs $filterString {
        if {[string length $fs]} {
            lappend filterStringTmp $fs
        }
    }
    set filterString $filterStringTmp

    set excludeFilterString [split $excludeFilterString " ,"]
    set excludeFilterStringTmp ""
    foreach fs $excludeFilterString {
        if {[string length $fs]} {
            lappend excludeFilterStringTmp $fs
        }
    }
    set excludeFilterString $excludeFilterStringTmp
    foreach tmp $controlnamesListTmp {
        set exclude 0
        foreach fs $excludeFilterString {
            if {[string match -nocase $fs $tmp]} {
                set exclude 1
                break
            }
        }
        if {$exclude} {
            continue
        }
        foreach fs $filterString {
            if {[string match -nocase $fs $tmp]} {
                lappend controlnamesList $tmp
            }
        }
    }
    APSSetVarAndUpdate status "Done displaying PVs"    
}

proc LoadFile {args} {
    global restorable OAGGlobal
    APSSetVarAndUpdate status "Loading files"

    if {![info exists restorable]} {
        APSSetVarAndUpdate status "You must select a system first"
        return
    }
    if {$restorable} {
        if {![winfo viewable .userFrame.top.right]} {
            pack .userFrame.top.right -side right -fill both -expand true
            pack configure .userFrame.top.left -fill none -expand false
        }
    } else {
        if {[winfo viewable .userFrame.top.right]} {
            pack forget .userFrame.top.right
            pack configure .userFrame.top.left -fill both -expand true
        }
    }

    set system ""
    set command Show
    APSStrictParseArguments {system command}
    global lastSystem files
    if {![llength $system]} {
        set system $lastSystem
    }
    set systemChanged 0
    if {$lastSystem != $system} {
        set systemChanged 1
    }
    set lastSystem $system
    global fileCount StartYear StartDay StartMonth EndYear EndDay EndMonth
    global previousfiles
    set files [APSFindFilesBetweenDates \
                 -rootname "${system}-" \
                 -directory $OAGGlobal(LogOnChangeDirectory)/$system \
                 -startDateList [APSFormatDate \
                                   -year $StartYear \
                                   -day $StartDay \
                                   -month $StartMonth \
                                   -dateFormat list] \
                 -endDateList [APSFormatDate \
                                 -year $EndYear \
                                 -day $EndDay \
                                 -month $EndMonth \
                                 -dateFormat list]]
    if {$files == $previousfiles} {
        APSSetVarAndUpdate status "Done loading files"
        eval $command
        return
    }
    set previousfiles $files
    set fileCount [llength $files]
    
    global PVLabel
    set PVLabel "ControlName"
    if {$restorable == 0} {
        if {[catch {exec sddsquery [lindex $files 0] -arraylist} results]} {
        } else {
            if {[lsearch $results "ReadbackName"] != -1} {
                if {$system != "SR"} {
                    set PVLabel "ReadbackName"
                }
            }
        }
    }

    for {set c 0} {$c < $fileCount} {incr c} {
        set filename [lindex $files $c]
        global pages$c
        set try 1
        if {[catch {exec sddsconvert $filename -pipe=out -recover | sdds2stream -pipe -array=$PVLabel } results]} {
            APSSetVarAndUpdate status "Error: $results"
            bell
            return		
        }
        set results [split $results \n]
        set pages$c [llength $results]
        for {set p 0} {$p < [set pages$c]} {incr p} {
            global controlnames$c$p n_controlnames$c$p CN$c$p
            set CN$c$p [lindex $results $p]
            set i 0
            foreach r [set CN$c$p] {
                set controlnames${c}${p}($i) $r
                incr i
            }
            set n_controlnames$c$p [array size controlnames$c$p]
        }
    }
    if {$systemChanged} {
        global categories beamlines
        update
        if {$restorable == 1} {
            if {[catch {exec sddsconvert [file join $OAGGlobal(LogOnChangeDirectory) $system $system.loc] -pipe=out -retain=column,Category | sddssort -pipe -column=Category -unique | sdds2stream -pipe=in -column=Category} categories]} {
                APSSetVarAndUpdate status "error: $categories"
                return
            }
        } else {
            set categories ""
        }
        foreach category $categories {
            set $category 0
        }
        update
        if {$restorable == 1} {
            if {[catch {exec sddsconvert [file join $OAGGlobal(LogOnChangeDirectory) $system $system.loc] -pipe=out -retain=column,Beamline | sddssort -pipe -column=Beamline -unique | sdds2stream -pipe=in -column=Beamline} beamlines]} {
                APSSetVarAndUpdate status "error: $beamlines"
                return
            }
        } else {
            set beamlines ""
        }
        foreach category $categories {
            set $category 0
        }
        foreach beamline $beamlines {
            set $beamline 0
        }
        destroy .userFrame.top.right.middle.category
        destroy .userFrame.top.right.middle2.beamline
        if {$restorable == 1} {
            APSEnableButton .userFrame.top.right.bottom.review.button
            APSEnableButton .userFrame.top.right.bottom.bottom.enable.button
            APSCheckButtonFrame .category \
              -parent .userFrame.top.right.middle \
              -label "" \
              -limitPerRow 2 \
              -buttonList $categories \
              -variableList $categories \
              -orientation horizontal \
              -allNone 1
            APSCheckButtonFrame .beamline \
              -parent .userFrame.top.right.middle2 \
              -label "" \
              -limitPerRow 3 \
              -buttonList $beamlines \
              -variableList $beamlines \
              -orientation horizontal \
              -allNone 1
        } else {
            APSDisableButton .userFrame.top.right.bottom.review.button
            APSDisableButton .userFrame.top.right.bottom.bottom.enable.button
        }
    }
    APSSetVarAndUpdate status "Done loading files"
    eval $command
}

proc GetTimeStamp {args} {
    set year ""
    set month ""
    set day ""
    set hour ""
    set hourvar ""
    APSStrictParseArguments {year month day hour hourvar}

    if {[string is double $hour]} {
        if {($hour < 0)} {
            set hour 0
        } elseif {($hour >= 24)} {
            set hour 23.9999
        }
        set h [expr {int($hour)}]
        set m [expr {int(fmod($hour,1) * 60)}]
        set s [expr {round(fmod(fmod($hour,1) * 60,1) * 60)}]
        if {$s == 60} {set s 59}
        set hour ${h}:${m}:${s}
        APSSetVarAndUpdate $hourvar $hour
    }
    if {[catch {clock scan "${month}/${day}/${year} $hour"} result]} {
        return -error "Error: Invalid date/time stamp"
    }
    return $result
}

proc ReviewOrRestore {args} {
    set restore 0
    APSStrictParseArguments {restore}
    if {$restore} {
        APSSetVarAndUpdate status "Restoring info"
    } else {
        APSSetVarAndUpdate status "Reviewing info"
    }
    global StartYear StartDay StartMonth StartHour 
    global EndYear EndDay EndMonth EndHour
    global RestoreYear RestoreDay RestoreMonth RestoreHour OAGGlobal
    
    if {[catch {GetTimeStamp -year $StartYear -month $StartMonth \
                  -day $StartDay -hour $StartHour -hourvar StartHour} startTime]} {
        APSSetVarAndUpdate status "$startTime for Starting date/time"
        return
    }
    if {[catch {GetTimeStamp -year $EndYear -month $EndMonth \
                  -day $EndDay -hour $EndHour -hourvar EndHour} endTime]} {
        APSSetVarAndUpdate status "$endTime for Ending date/time"
        return
    }
    if {[catch {GetTimeStamp -year $RestoreYear -month $RestoreMonth \
                  -day $RestoreDay -hour $RestoreHour -hourvar RestoreHour} restoreTime]} {
        APSSetVarAndUpdate status "$restoreTime for Restore date/time"
        return
    }
    if {($restoreTime < $startTime) || ($restoreTime > $endTime)} {
        APSSetVarAndUpdate status "Warning: Restore date/time is not between the Starting and Ending date/time"
        APSSetVarAndUpdate status "This may indicate a problem"
        return
    }
    
    global lastSystem tmpfile categories beamlines
    set categoryOptions ""
    set n 0
    foreach category $categories {
        global $category
        if {[set $category]} {
            if {$n} {
                append categoryOptions ",Category=${category},|"
            } else {
                set categoryOptions "-match=column,Category=$category"
                set n 1
            }
        }
    }
    set beamlineOptions ""
    set n 0
    foreach beamline $beamlines {
        global $beamline
        if {[set $beamline]} {
            if {$n} {
                append beamlineOptions ",Beamline=${beamline},|"
            } else {
                set beamlineOptions "-match=column,Beamline=$beamline"
                set n 1
            }
        }
    }
    if {![llength $categoryOptions]} {
        APSSetVarAndUpdate status "error: No categories selected"
        return
    }
    if {![llength $beamlineOptions]} {
        APSSetVarAndUpdate status "error: No beamlines selected"
        return
    }

    global filterString excludeFilterString

    if {![llength $filterString]} {
        set filterString "*"
    }
    set filterString [split $filterString " ,"]
    set filterStringTmp ""
    foreach fs $filterString {
        if {[string length $fs]} {
            lappend filterStringTmp $fs
        }
    }
    set filterString $filterStringTmp

    set excludeFilterString [split $excludeFilterString " ,"]
    set excludeFilterStringTmp ""
    foreach fs $excludeFilterString {
        if {[string length $fs]} {
            lappend excludeFilterStringTmp $fs
        }
    }
    set excludeFilterString $excludeFilterStringTmp
    
    set includeoptions ""
    set i 0
    foreach fs $filterString {
        if {$i == 0} {
            set i 1
            set includeoptions "-match=column,ControlName=${fs}"
        } else {
            append includeoptions ",ControlName=${fs},|"
        }
    }

    set excludeoptions ""
    set i 0
    foreach fs $excludeFilterString {
        if {$i == 0} {
            set i 1
            set excludeoptions "-match=column,ControlName=${fs},!"
        } else {
            append excludeoptions ",ControlName=${fs},!,&"
        }
    }
    
    if {[catch {eval exec sddsprocess [file join $OAGGlobal(LogOnChangeDirectory) $lastSystem $lastSystem.loc] $tmpfile "-match=column,IsReadOnly=+n" "-match=column,IsProtected=+n" $categoryOptions $beamlineOptions $includeoptions $excludeoptions} results]} {
        APSSetVarAndUpdate status "error: $results"
        return
    }

    if {[catch {exec sdds2stream $tmpfile -column=ControlName} PVNames]} {
        APSSetVarAndUpdate status "error: $PVNames"
        return
    }
    set PVNames [lsort -dictionary $PVNames]
    set files [APSFindFilesBetweenDates \
                 -rootname "${lastSystem}-" \
                 -directory $OAGGlobal(LogOnChangeDirectory)/$lastSystem \
                 -startDateList [APSFormatDate \
                                   -year $RestoreYear \
                                   -day $RestoreDay \
                                   -month $RestoreMonth \
                                   -dateFormat list] \
                 -endDateList [APSFormatDate \
                                 -year $RestoreYear \
                                 -day $RestoreDay \
                                 -month $RestoreMonth \
                                 -dateFormat list]]
    if {![llength $files]} {
        APSSetVarAndUpdate status "No logged data at that time stamp"
        bell
        return
    }
    if {[catch {eval exec sddscombine $files ${tmpfile}1 -overWrite -recover} results]} {
        APSSetVarAndUpdate status "error: $results"
        return
    }
    if {[catch {exec sdds2stream ${tmpfile}1 -parameter=StartTime} sTimes]} {
        APSSetVarAndUpdate status "error: $sTimes"
        return
    }
    set t1 0
    foreach t $sTimes {
        if {($t <= $restoreTime) && ($t > $t1)} {
            set t1 $t
        }
    }
    set page [expr [lsearch -exact $sTimes $t1] + 1]
    if {$page == 0} {
        APSSetVarAndUpdate status "No logged data at that time stamp"
        return
    }

    if {[llength $sTimes] == 1} {
        if {[catch {exec sddsprocess ${tmpfile}1 -pipe=out -filter=column,Time,0,$restoreTime | sddssort -pipe -column=ControlNameIndex -column=Time,decreasing | sddssort -pipe=in ${tmpfile}3 -column=ControlNameIndex -unique} results]} {
            APSSetVarAndUpdate status "error: $results"
            return
        }
    } else {
        if {[catch {exec sddsconvert ${tmpfile}1 -pipe=out -fromPage=$page -toPage=$page | sddsprocess -pipe -filter=column,Time,0,$restoreTime | sddssort -pipe -column=ControlNameIndex -column=Time,decreasing | sddssort -pipe=in ${tmpfile}3 -column=ControlNameIndex -unique} results]} {
            APSSetVarAndUpdate status "error: $results"
            return
        }
    }
    
    sdds load ${tmpfile}3 restoreData

    if {$restore} {
        set rData(ColumnNames) "ControlName Value"
        set rData(ColumnInfo.Value) "type SDDS_DOUBLE"
        foreach name $PVNames {
            set i [lsearch -exact [lindex $restoreData(Array.ControlName) 0] $name]
            if {($i == -1)} {continue}
            incr i -1
            set cIndex [lindex [lindex $restoreData(Array.ControlName) 0] $i]
            
            set k [lsearch -exact [lindex $restoreData(Column.ControlNameIndex) 0] $cIndex]
            if {($k != -1)} {
                lappend rData(Column.ControlName) $name
                lappend rData(Column.Value) [lindex [lindex $restoreData(Column.Value) 0] $k]
            }
        }
        set rData(Column.ControlName) [list $rData(Column.ControlName)]
        set rData(Column.Value) [list $rData(Column.Value)]
        if {[catch {sdds save ${tmpfile}2 rData} results]} {
            APSSetVarAndUpdate status "error: $results"
            return
        }
        if {[catch {exec sddscaramp -rampTo=${tmpfile}2,steps=1,pause=0} results]} {
            APSSetVarAndUpdate status "error: $results"
            return
        }
        APSSetVarAndUpdate status "$results"
    } else {
        global lb fileCount
        destroy .info2
        APSDialogBox .info2 -name "Review Time Stamp"
        destroy .info2.buttonRow.cancel
        APSDialogBoxAddButton .email \
          -parent .info2 \
          -text "EMail" \
          -command "APSScrolledTextEMail -textWidget .info2.userFrame.info.text"
        APSScrolledText .info \
          -parent .info2.userFrame \
          -width 100 -height 30 \
          -name "Logged PV Values"
        .info2.userFrame.info.text insert end "ControlName\t\t\t    Value\t\t\tTime\n"
        foreach name $PVNames {
            set pvValue ""
            set pvTime "0"
            set i [lsearch -exact [lindex $restoreData(Array.ControlName) 0] $name]
            if {($i == -1)} {continue}
            incr i -1
            set cIndex [lindex [lindex $restoreData(Array.ControlName) 0] $i]
            
            set k [lsearch -exact [lindex $restoreData(Column.ControlNameIndex) 0] $cIndex]
            if {($k != -1)} {
                set pvValue [lindex [lindex $restoreData(Column.Value) 0] $k]
                set pvTime [lindex [lindex $restoreData(Column.Time) 0] $k]
                .info2.userFrame.info.text insert end "[format "%-35s" $name]"
                .info2.userFrame.info.text insert end " [format "%-20s" $pvValue]"
                .info2.userFrame.info.text insert end "\t[clock format [expr round($pvTime)] -format {%a %D %T}]\n"
            }
        }
    }
    if {$restore} {
        APSSetVarAndUpdate status "Done restoring info"
    } else {
        APSSetVarAndUpdate status "Done reviewing info"
    }
}

proc ExportData {args} {
    global exportTimeFrame retainColumns minTimeInterval exportFile
    global files

    if {[llength $exportFile] == 0} {
        APSSetVarAndUpdate status "No output file selected"
        bell
        return
    }

    set tmpfile /tmp/[APSTmpString]
    set fileList ""
    set i 0
    foreach f $files {
        if {[catch {exec sdds2stream $f -npages=bare} pages]} {
            APSSetVarAndUpdate status "error: $pages"
            bell
            return
        }
        if {$pages > 1} {
            for {set j 1} {$j <= $pages} {incr j} {
                if {[catch {exec sddsconvert $f -pipe=out  \
                              -frompage=$j -topage=$j \
                              | sddsconvertlogonchange \
                              -pipe=in ${tmpfile}$i \
                              -minimumInterval=$minTimeInterval \
                              -time=$exportTimeFrame \
                              -retain=$retainColumns} result]} {
                    APSSetVarAndUpdate status "error: $result"
                    bell
                    for {set k 0} {$k <= $i} {incr k} {
                        file delete -force ${tmpfile}$k
                    }
                    return
                }
                append fileList "${tmpfile}$i "
                incr i
            }
        } else {
            if {[catch {exec sddsconvertlogonchange \
                          $f ${tmpfile}$i \
                          -minimumInterval=$minTimeInterval \
                          -time=$exportTimeFrame \
                          -retain=$retainColumns} result]} {
                APSSetVarAndUpdate status "error: $result"
                bell
                for {set k 0} {$k <= $i} {incr k} {
                    file delete -force ${tmpfile}$k
                }
                return
            }
            append fileList "${tmpfile}$i "
            incr i
        }
    }

    if {[catch {eval exec sddscombine $fileList $exportFile -merge -overwrite} result]} {
        APSSetVarAndUpdate status "error: $result"
        bell
        for {set k 0} {$k <= $i} {incr k} {
            file delete -force ${tmpfile}$k
        }
        return        
    }
    APSSetVarAndUpdate status "Done"
}

proc PrepareExportData {args} {
    APSSetVarAndUpdate status "Preparing to export data"

    global StartYear StartDay StartMonth StartHour 
    global EndYear EndDay EndMonth EndHour stripInitial
    
    if {[catch {GetTimeStamp -year $StartYear -month $StartMonth \
                  -day $StartDay -hour $StartHour -hourvar StartHour} startTime]} {
        APSSetVarAndUpdate status "$startTime for Starting date/time"
        return
    }
    if {[catch {GetTimeStamp -year $EndYear -month $EndMonth \
                  -day $EndDay -hour $EndHour -hourvar EndHour} endTime]} {
        APSSetVarAndUpdate status "$endTime for Ending date/time"
        return
    }
    if {($endTime < $startTime)} {
        APSSetVarAndUpdate status "Warning: Start date/time is after End date/time"
        return
    }

    global lb
    set selIndex [$lb curselection]
    destroy .export
    APSDialogBox .export \
      -name "Export Dialog" \
      -okCommand "ExportData"
    
    global exportTimeFrame
    set exportTimeFrame "start=[expr {round($startTime)}],end=[expr {round($endTime)}]"
    APSLabeledEntry .time \
      -parent .export.userFrame \
      -label "-time=" \
      -textVariable exportTimeFrame \
      -packOption "-fill x -expand true" \
      -width 80 \
      -contextHelp "Time frame for exported data. Values in epoch time, ie. seconds from 1/1/1970."

    global retainColumns
    set retainColumns ""
    APSLabeledEntry .retain \
      -parent .export.userFrame \
      -label "-retain=" \
      -textVariable retainColumns \
      -packOption "-fill x -expand true" \
      -width 80 \
      -contextHelp "Comma separated list of column names to keep in the output. If PVs are selected on the main PVHistoryTool screen they will be copied to this list when the Export button is pressed."

    global minTimeInterval
    set minTimeInterval 1
    APSLabeledEntry .mintimeinterval \
      -parent .export.userFrame \
      -label "-minimumInterval=" \
      -textVariable minTimeInterval \
      -packOption "-fill x -expand true" \
      -width 80 \
      -contextHelp "Minimum time interval between output rows. Values above zero reduce output file size."

    global exportFile
    APSLabeledEntry .exportFile \
      -parent .export.userFrame \
      -label "Output File:" \
      -textVariable exportFile \
      -packOption "-fill x -expand true" \
      -width 80 \
      -commandButton 1 \
      -fileSelectValidity 0 \
      -contextHelp "The name of the output file."

    foreach index $selIndex {
        lappend retainColumns [$lb get $index]
    }
    if {[string length $retainColumns] == 0} {
        set retainColumns "*"
    }
    set retainColumns [join $retainColumns ,]

}

proc SetupWindow {args} {
    global controlnamesList lb tmpfile filtered OAGGlobal configurationFile

    if {[catch {exec sdds2stream \
                  [file join $OAGGlobal(LogOnChangeDirectory) $configurationFile] \
                  -col=System,Restorable} results]} {
        puts stderr "Error: $results"
        exit
    }

    APSMenubarAddMenu .system -parent .menu -text "System"
    set restorableFound 0
    foreach "system restorable" $results {
        if {$restorable} {
            .menu.system.menu add command -label $system -underline 0 -command "set restorable 1 ; LoadFile -system $system"
            set restorableFound 1
        }
    } 
    .menu.system.menu add separator
    foreach "system restorable" $results {
        if {!$restorable} {
            .menu.system.menu add command -label $system -underline 0 -command "set restorable 0 ; LoadFile -system $system"
        }
    } 
    set tmpfile /tmp/[APSTmpString]
    APSAddToTempFileList $tmpfile ${tmpfile}1 ${tmpfile}2 ${tmpfile}3

    set filtered 1

    APSSetVarAndUpdate status ""
    APSScrolledStatus .scroll \
      -parent .userFrame \
      -textVariable status \
      -packOption "-side top -fill x"

    pack [frame .userFrame.top] -fill both -expand true
    pack [frame .userFrame.top.left -bd 2 -relief ridge] -side left -fill y
    pack [frame .userFrame.top.left.bottom2 -bg red] -side bottom -fill x
    pack [frame .userFrame.top.left.bottom] -side bottom -fill x
    frame .userFrame.top.right -bd 2 -relief ridge
    pack configure .userFrame.top.left -fill both -expand true
    APSScrolledList .controlnames \
      -parent .userFrame.top.left \
      -listvar controlnamesList \
      -width 40 -height 24
    set lb .userFrame.top.left.controlnames.listbox
    global filterString excludeFilterString
    set filterString "*"
    set excludeFilterString ""
    APSLabeledEntry .include \
      -parent .userFrame.top.left \
      -label "Include:" \
      -packOption "-expand true -fill x" \
      -textVariable filterString
    bind .userFrame.top.left.include.entry <Return> "Show"
    APSLabeledEntry .exclude \
      -parent .userFrame.top.left \
      -label "Exclude:" \
      -packOption "-expand true -fill x" \
      -textVariable excludeFilterString
    bind .userFrame.top.left.exclude.entry <Return> "Show"
    
    APSButton .showall \
      -parent .userFrame.top.left \
      -text "Rescan PVs" \
      -command "APSSetVarAndUpdate filtered 0; LoadFile" \
      -contextHelp "Display all PVs in log files"

    APSButton .showfiltered \
      -parent .userFrame.top.left \
      -text "Rescan Active PVs" \
      -command "APSSetVarAndUpdate filtered 1; LoadFile" \
      -contextHelp "Display only PVs that have changed"

    APSButton .export \
      -parent .userFrame.top.left \
      -text "Export..." \
      -command "PrepareExportData" \
      -contextHelp "Export the data to a file"

    APSButton .showinfo \
      -parent .userFrame.top.left.bottom \
      -text "Show PV History" \
      -command "LoadFile -command ShowInfo" \
      -contextHelp "Display selected PV histories" \
      -packOption "-side left -padx 1 -pady 1 -anchor nw"
    global tkplot
    set tkplot 0
    APSCheckButtonFrame .tkplot \
      -parent .userFrame.top.left.bottom.showinfo \
      -label "" \
      -buttonList {"Use tksddsplot"} \
      -variableList "tkplot" \
      -orientation horizontal

    APSButton .plotinfo \
      -parent .userFrame.top.left.bottom \
      -text "Plot PV History" \
      -command "LoadFile -command PlotInfo" \
      -contextHelp "Plot selected PV histories"

    global split
    set split 0
    APSCheckButtonFrame .split \
      -parent .userFrame.top.left.bottom.plotinfo \
      -label "" \
      -buttonList {"Split Plot"} \
      -variableList "split" \
      -orientation horizontal

    global stripInitial
    set stripInitial 0
    APSCheckButtonFrame .strip \
      -parent .userFrame.top.left.bottom2 \
      -label "" \
      -buttonList {"Strip Initial Values"} \
      -variableList "stripInitial" \
      -orientation horizontal

    global RestoreHour
    pack [label .userFrame.top.right.timelabel -text "Restore Time Stamp"]
    APSDateTimeEntry .restoreTime \
      -parent .userFrame.top.right \
      -label "" \
      -dayVariable RestoreDay \
      -yearVariable RestoreYear \
      -monthVariable RestoreMonth \
      -hourVariable RestoreHour \
      -twoDigitYear 0
    
    if {[string is double $RestoreHour]} {
        if {($RestoreHour < 0)} {
            APSSetVarAndUpdate RestoreHour 0
        } elseif {($RestoreHour >= 24)} {
            APSSetVarAndUpdate RestoreHour 23.9999
        }
        set h [expr {int($RestoreHour)}]
        set m [expr {int(fmod($RestoreHour,1) * 60)}]
        set s [expr {round(fmod(fmod($RestoreHour,1) * 60,1) * 60)}]
        if {$s == 60} {set s 59}
        APSSetVarAndUpdate RestoreHour ${h}:${m}:${s}
    }

    pack [frame .userFrame.top.right.middle]
    pack [frame .userFrame.top.right.middle2]
    pack [frame .userFrame.top.right.bottom] -side bottom
    pack [frame .userFrame.top.right.bottom.bottom] -side bottom

    APSButton .review \
      -parent .userFrame.top.right.bottom \
      -text "Review" \
      -command "ReviewOrRestore" \
      -contextHelp "Review PV values that would be restored"

    APSButton .restore \
      -parent .userFrame.top.right.bottom \
      -text "Restore System" \
      -command "ReviewOrRestore -restore 1" \
      -contextHelp "Restore system PVs to the values during the Restore Time Stamp"
    APSDisableButton .userFrame.top.right.bottom.restore.button

    APSButton .enable \
      -parent .userFrame.top.right.bottom.bottom \
      -text "Enable Restore Button" \
      -command {APSEnableButton .userFrame.top.right.bottom.restore.button; after 5000 {APSDisableButton .userFrame.top.right.bottom.restore.button}}

    MakeDateTimeFrame .timeFrame -parent .userFrame

    APSSetVarAndUpdate lastSystem ""
    #    LoadFile -system LPL

}
set previousfiles none

APSApplication . -name "PV History Tool"

set configurationFile PVHistoryTool.config.1.sdds
set bts 0

set args $argv
APSStrictParseArguments {configurationFile bts}

SetupWindow

if {$bts} {
    set restorable 0
    set filtered 0
    set filterString B:ES2:VoltageSetSendAO,BTS:AH1:CurrentAO,BTS:AH2:CurrentAO,BTS:AH3:CurrentAO,BTS:BH1:CurrentAO,BTS:BH2:CurrentAO,BTS:AV1:CurrentAO,BTS:AV2:CurrentAO,BTS:AV3:CurrentAO,BTS:BV1:CurrentAO,BTS:CV1:CurrentAO
    LoadFile -system Booster
}
