# copyright (C) 1997-2006 Jean-Luc Fontaine (mailto:jfontain@free.fr)
# this program is free software: please read the COPYRIGHT file enclosed in this package or use the Help Copyright menu

# $Id: sumtable.tcl,v 2.82 2006/01/28 19:16:59 jfontain Exp $


class summaryTable {

if {$global::withGUI} { ;# >8

    proc summaryTable {this parentPath args} composite {[new frame $parentPath] $args} viewTable {} viewer {} {
        composite::complete $this
        constructor $this
    }

} else { ;# >8                       remove Tk dependant code since this class can be used by monitoring daemon in a Tcl environment

    proc summaryTable {this args} switched {$args} viewTable {} viewer {} {
        switched::complete $this
        constructor $this
    }

} ;# >8

    proc constructor {this} {
        set dataName ::summaryTable::$(nextDataIndex)data        ;# generate unique name based on index (available after completion)
        incr (nextDataIndex)
        unset -nocomplain $dataName
        array set $dataName [list\
            updates 0\
            0,label [mc data] 0,type ascii 0,message [mc {data cell description}]\
            1,label [mc current] 1,type real 1,message [mc {current value}]\
            2,label [mc average] 2,type real\
                2,message [mc {average value (since viewer creation in real time mode or for range in database mode)}]\
            3,label [mc minimum] 3,type real\
                3,message [mc {minimum value (since viewer creation in real time mode or for range in database mode)}]\
            4,label [mc maximum] 4,type real\
                4,message [mc {maximum value (since viewer creation in real time mode or for range in database mode)}]\
            5,label [mc deviation] 5,type real\
                5,message [mc {standard deviation (since viewer creation in real time mode or for range in database mode)}]\
            indexColumns 0\
            sort {0 increasing}\
        ]
if {$global::withGUI} { ;# >8
        viewTable::createTable $this $dataName "summaryTable::dragData $this"
        updateMessage $this
} else { ;# >8
        viewTable::setDataName $this $dataName
} ;# >8
    }

    proc ~summaryTable {this} {
if {$global::withGUI} { ;# >8
        variable ${this}cellRange                                                                                ;# for history mode

        foreach {name wish} [array get {} $this,rowLastWish,*] {                   ;# delete remaining last wishes, one for each row
            delete $wish                                                                                ;# which in turn deletes row
        }
        unset -nocomplain ${this}cellRange
        if {[string length $composite::($this,-deletecommand)] > 0} {
            uplevel #0 $composite::($this,-deletecommand)                                   ;# always invoke command at global level
        }
} else { ;# >8
        if {[string length $switched::($this,-deletecommand)] > 0} {
            uplevel #0 $switched::($this,-deletecommand)
        }
} ;# >8
    }

if {$global::withGUI} { ;# >8

    proc iconData {} {
        return {
            R0lGODdhJAAkAOMAAPj8+Hh4eAAAAHh8eNjc2ICEgIiQiJCYkKCkoKisqLi8uMDEwMjQyNDY0ODk4Ojs6CwAAAAAJAAkAAAE/hDIKQO99s5cNeUaiH2aYJ5o
            qqZbFQyDQBDDbN/1jRMmDIMymm43nNUEg84lmCs2h8ckQARA+q7YLBapDLxiAGF4TAjXyOSj9UeRAZLl+BiOLie505JZzj/z93hUa1qEWYEuMExFRotCPT5A
            jItPOlFKbZJOjZZ5S4WfW1IZXol7daZ/joNSEm50f69od6J6Yql+dZyCoLwxtFNfipObPKuYQsPDh0uZUMTLbb2gyyy2uamAKleup3bdb1VZBeMFAqjX3VHk
            4wbtBqvSoe7tB/UHwprKA/b1CP4I+Jzp++cvgcEEASs9G3DQoIKHClZInNgD4sMFGDHGA5URI4OPIyBDihxJsmSDkyhTqlzJsqWDlzBjypxJs+aDmzhz6tzJ
            s2cEADs=
        }
    }

} ;# >8

    proc options {this} {
        # data index must be forced so that initialization always occur
if {$global::withGUI} { ;# >8
        set font $font::(mediumBold)
} else { ;# >8
        set font {}
} ;# >8
        return [list\
            [list -cellrows {} {}]\
            [list -dataindex {}]\
            [list -deletecommand {} {}]\
            [list -draggable 0 0]\
            [list -interval 0 0]\
        ]
    }

    proc set-cellrows {this value} {                                                 ;# restore cell to row mapping from a save file
if {$global::withGUI} { ;# >8
        set complete $composite::($this,complete)
} else { ;# >8
        set complete $switched::($this,complete)
} ;# >8
        if {$complete} {
            error {option -cellrows cannot be set dynamically}
        }
        viewTable::setCellRows $this $value
    }

    set (nextDataIndex) 0                            ;# used when data array index is not specified as an option when creating table
    proc reset {} {                                  ;# reset generated counter (invoker must insure that there are no viewers left)
        set (nextDataIndex) 0
    }
    # data array name index must be specifiable so that data viewers depending on summary table data array name (through their
    # monitored cells) do not fail accessing that data (required when generating viewers from save file)
    proc set-dataindex {this value} {                                                         ;# always invoked at construction time
if {$global::withGUI} { ;# >8
        set complete $composite::($this,complete)
} else { ;# >8
        set complete $switched::($this,complete)
} ;# >8
        if {$complete} {
            error {option -dataindex cannot be set dynamically}
        }
        if {[string length $value] > 0} {                           ;# specified, else use internally generated next available index
            if {$value < $(nextDataIndex)} {
                error "specified data index ($value) is lower than internal summary table index"
            }
            set (nextDataIndex) $value
        }
    }

    proc set-deletecommand {this value} {}

    proc set-draggable {this value} {
if {$global::withGUI} { ;# >8
        set complete $composite::($this,complete)
} else { ;# >8
        set complete $switched::($this,complete)
} ;# >8
        if {$complete} {
            error {option -draggable cannot be set dynamically}
        }
    }

    proc set-interval {this value} {}                                                   ;# only so that history mode can be detected

    proc supportedTypes {this} {
        return $global::numericDataTypes
    }

    proc monitorCell {this array row column} {
if {$global::withGUI} { ;# >8
        variable ${this}cellRange
} ;# >8

        set cell ${array}($row,$column)
        if {[string length [viewTable::row $this $cell]] > 0} return                                     ;# already displayed, abort
if {$global::withGUI} { ;# >8
        if {\
            ($composite::($this,-interval) == 0) &&\
            ([lindex [modules::decoded [modules::namespaceFromArray $array]] 0] ne "instance")\
        } {
            lifoLabel::flash $global::messenger $viewTable::(monitorInstanceCellsMessage)
            return                                                   ;# in history mode, ignore any cell not from an instance module
        }
} ;# >8
        foreach {label incomplete} [viewer::label $array $row $column] {}
        set row [viewTable::register $this $cell $array]                                                           ;# view table row
        set dataName $viewTable::($this,dataName)
        set ${dataName}($row,0) $label
        set current ?
        catch {set current [set $cell]}                                                                        ;# cell may not exist
        set ${dataName}($row,1) $current
        array set $dataName [list $row,2 ? $row,3 ? $row,4 ? $row,5 ?]         ;# initialize average, minimum, maximum and deviation
        set ${dataName}($row,updates) 0
        set ${dataName}($row,sum) 0.0
        set ${dataName}($row,squares) 0.0                                ;# sum of squared values for standard deviation calculation
if {$global::withGUI} { ;# >8
        set ${dataName}($row,new) 1
        set ${this}cellRange($cell,start) 0                                                                      ;# for history mode
        set ${this}cellRange($cell,end) 0
        # setup action when a row is deleted through a cell drop in eraser site
        set ($this,rowLastWish,$row) [new lastWish "summaryTable::deleteRow $this $cell"]
} ;# >8
        if {$incomplete} {                                                                         ;# label cannot be determined yet
            set ($this,relabel,$row) {}
        }
        incr ${dataName}(updates)                       ;# let data table update itself (so colors can be set on cells, for example)
if {$global::withGUI} { ;# >8
        updateMessage $this
} ;# >8
    }

    proc update {this array} {
        set dataName $viewTable::($this,dataName)
        set updated 0
        foreach {cell row} [viewTable::cellsAndRows $this] {
            if {[string first $array $cell] != 0} continue             ;# no need to update if cell does not belong to updated array
            set current ?; catch {set current [set $cell]}
            set ${dataName}($row,1) $current
if {$global::withGUI} { ;# >8
            if {$composite::($this,-interval) == 0} {                                      ;# history mode (available from GUI only)
                processHistory $this $row $cell                                           ;# last value may be void but others valid
            } else {
                viewer::parse $cell ignore cellRow cellColumn type
                history::remember $array $cellRow $cellColumn $current
                if {[set ${dataName}($row,new)]} {                                                                   ;# first update
                    foreach {seconds value} [history::list $array $cellRow $cellColumn] {
                        updateCalculations $this $row $value
                    }
                } else {
                    updateCalculations $this $row $current
                }
            }
            set ${dataName}($row,new) 0
} else { ;# >8
            updateCalculations $this $row $current
} ;# >8
            if {[info exists ($this,relabel,$row)] && [info exists $cell]} {               ;# if label is not yet defined, update it
                viewer::parse $cell ignore cellRow cellColumn type
                foreach [list ${dataName}($row,0) incomplete] [viewer::label $array $cellRow $cellColumn] {}
                if {!$incomplete} {                                                                  ;# label now completely defined
                    unset ($this,relabel,$row)
                }
            }
            set updated 1
        }
        if {$updated} {incr ${dataName}(updates)}                                                    ;# let data table update itself
    }

    proc cells {this} {           ;# return cells in creation order (increasing row) so that restoring from save file works properly
        return [viewTable::cells $this]
    }

    proc updateCalculations {this row current} {
        if {![string is double -strict $current]} return                              ;# ignore void values which break calculations
        set dataName $viewTable::($this,dataName)
        set sum [expr {[set ${dataName}($row,sum)] + $current}]
        set updates [incr ${dataName}($row,updates)]
        set average [expr {$sum / $updates}]
        set ${dataName}($row,2) [format %.2f $average]
        set value [set ${dataName}($row,3)]
        if {[string equal $value ?] || ($current < $value)} {                                         ;# possibly initialize minimum
            set ${dataName}($row,3) $current
        }
        set value [set ${dataName}($row,4)]
        if {[string equal $value ?] || ($current > $value)} {                                         ;# possibly initialize maximum
            set ${dataName}($row,4) $current
        }
        set squares [expr {[set ${dataName}($row,squares)] + ($current * $current)}]
        set value 0                                                           ;# for the first sample as division by zero occurs
        catch {set value [expr {sqrt(($squares + ($updates * $average * $average) - (2 * $average * $sum)) / ($updates - 1))}]}
        set ${dataName}($row,5) [format %.2f $value]
        set ${dataName}($row,sum) $sum
        set ${dataName}($row,squares) $squares
    }

if {$global::withGUI} { ;# >8

    proc processHistory {this row cell} {
        variable ${this}cellRange

        set dataName $viewTable::($this,dataName)
        foreach {start end} [databaseInstances::range $cell] {}               ;# database cell range in seconds (limited by cursors)
        if {[string length $start] == 0} {                                                                        ;# no history data
            set ${this}cellRange($cell,start) 0
            set ${this}cellRange($cell,end) 0
            set ${dataName}($row,2) ?
            set ${dataName}($row,3) ?
            set ${dataName}($row,4) ?
            set ${dataName}($row,5) ?
            return
        }
        if {([set ${this}cellRange($cell,start)] == $start) && ([set ${this}cellRange($cell,end)] == $end)} {
            return                                                      ;# no range change: avoid potentially lengthy database query
        }
        blt::vector create values
        set start 0
        foreach {stamp value} [databaseInstances::history $cell] {                                        ;# empty if database error
            if {$start == 0} {set start $stamp}
            if {![string is double -strict $value]} continue       ;# void (empty string instead of ? as data comes from a database)
            values append $value
        }
        if {[info exists stamp]} {                                         ;# remember whole data (even including voids) time limits
            set ${this}cellRange($cell,start) $start
            set ${this}cellRange($cell,end) $stamp
        }                                                                                           ;# else a database error occured
        if {[values length] > 0} {
            blt::vector create result
            result expr {mean(values)}
            set ${dataName}($row,2) [format %.2f [result index 0]]
            result expr {min(values)}
            regsub {\.0$} [result index 0] {} ${dataName}($row,3)                           ;# strip .0 trailer if result is integer
            result expr {max(values)}
            regsub {\.0$} [result index 0] {} ${dataName}($row,4)                           ;# strip .0 trailer if result is integer
            result expr {sdev(values)}                                                                         ;# standard deviation
            set ${dataName}($row,5) [format %.2f [result index 0]]
            blt::vector destroy result
        } else {                                                                                            ;# there were only voids
            set ${dataName}($row,2) ?
            set ${dataName}($row,3) ?
            set ${dataName}($row,4) ?
            set ${dataName}($row,5) ?
        }
        blt::vector destroy values
    }

    proc dragData {this format} {
        switch $format {
            OBJECTS {
                set lastWishes {}
                foreach row [viewTable::selectedRows $this $format] {
                    lappend lastWishes $($this,rowLastWish,$row)
                }
                if {[llength $lastWishes] == 0} {
                    return $this                                                                  ;# self destruct if no rows remain
                } else {
                    return $lastWishes
                }
            }
            DATACELLS {
                return [viewTable::dragCells $this]
            }
        }
    }

    proc deleteRow {this cell} {                                   ;# last wish object is deleted after completion of this procedure
        variable ${this}cellRange

        set dataName $viewTable::($this,dataName)
        set row [viewTable::deleteRow $this $cell]
        unset ${dataName}($row,0) ${dataName}($row,1) ${dataName}($row,2) ${dataName}($row,3) ${dataName}($row,4)\
            ${dataName}($row,5) ($this,rowLastWish,$row) ${dataName}($row,updates) ${dataName}($row,sum) ${dataName}($row,squares)
        unset ${dataName}($row,new) ${this}cellRange($cell,start) ${this}cellRange($cell,end)
        unset -nocomplain ($this,relabel,$row)
        viewTable::update $this
        incr ${dataName}(updates)                                              ;# so related viewers can show cells have disappeared
        updateMessage $this
    }

    proc initializationConfiguration {this} {
        return [viewTable::initializationConfiguration $this]
    }

    proc monitored {this cell} {
        return [viewTable::monitored $this $cell]
    }

    proc setCellColor {this source color} {
        viewTable::setCellColor $this $source $color
    }

    proc updateMessage {this} {
        if {[viewTable::numberOfRows $this]} {
            centerMessage $widget::($this,path) {}
        } else {
            centerMessage $widget::($this,path)\
                [mc "statistics table:\ndrop data cell(s)"] $viewer::(background) $global::viewerMessageColor
        }
    }

    proc updateLabels {this} {
        viewTable::updateLabels $this
    }

} ;# >8

}
