
#@package: TclX-ArrayProcedures for_array_keys

proc for_array_keys {varName arrayName codeFragment} {
    upvar $varName enumVar $arrayName enumArray

    if ![info exists enumArray] {
	error "\"$arrayName\" isn't an array"
    }

    set searchId [array startsearch enumArray]
    while {[array anymore enumArray $searchId]} {
	set enumVar [array nextelement enumArray $searchId]
	uplevel $codeFragment
    }
    array donesearch enumArray $searchId
}

#@package: TclX-assign_fields assign_fields

proc assign_fields {list args} {
    foreach varName $args {
        set value [lvarpop list]
        uplevel "set $varName [list $value]"
    }
}

#@package: TclX-developer_utils saveprocs edprocs

proc saveprocs {fileName args} {
    set fp [open $fileName w]
    puts $fp "# tcl procs saved on [fmtclock [getclock]]\n"
    puts $fp [eval "showprocs $args"]
    close $fp
}

proc edprocs {args} {
    global env

    set tmpFilename /tmp/tcldev.[id process]

    set fp [open $tmpFilename w]
    puts $fp "\n# TEMP EDIT BUFFER -- YOUR CHANGES ARE FOR THIS SESSION ONLY\n"
    puts $fp [eval "showprocs $args"]
    close $fp

    if [info exists env(EDITOR)] {
        set editor $env(EDITOR)
    } else {
	set editor vi
    }

    set startMtime [file mtime $tmpFilename]
    system "$editor $tmpFilename"

    if {[file mtime $tmpFilename] != $startMtime} {
	source $tmpFilename
	echo "Procedures were reloaded."
    } else {
	echo "No changes were made."
    }
    unlink $tmpFilename
    return
}

#@package: TclX-forfile for_file

proc for_file {var filename code} {
    upvar $var line
    set fp [open $filename r]
    while {[gets $fp line] >= 0} {
        uplevel $code
    }
    close $fp
}


#@package: TclX-forrecur for_recursive_glob

proc for_recursive_glob {var globlist code {depth 1}} {
    upvar $depth $var myVar
    foreach globpat $globlist {
        foreach file [glob -nocomplain $globpat] {
            if [file isdirectory $file] {
                for_recursive_glob $var $file/* $code [expr {$depth + 1}]
	    }
	    set myVar $file
	    uplevel $depth $code
        }
    }
}

#@package: TclX-globrecur recursive_glob

proc recursive_glob {globlist} {
    set result ""
    foreach pattern $globlist {
        foreach file [glob -nocomplain $pattern] {
            lappend result $file
            if [file isdirectory $file] {
                set result [concat $result [recursive_glob $file/*]]
            }
        }
    }
    return $result
}

#@package: TclX-help help helpcd helppwd apropos


proc help:flattenPath {pathName} {
    set newPath {}
    foreach element [split $pathName /] {
        if {"$element" == "."} {
           continue
        }
        if {"$element" == ".."} {
            if {[llength [join $newPath /]] == 0} {
                error "Help: name goes above subject directory root"}
            lvarpop newPath [expr [llength $newPath]-1]
            continue
        }
        lappend newPath $element
    }
    set newPath [join $newPath /]
    

    if {("$newPath" == "") && [string match "/*" $pathName]} {
        set newPath "/"}
        
    return $newPath
}


proc help:EvalPath {pathName} {
    global TCLENV

    if {![string match "/*" $pathName]} {
        if {"$pathName" == ""} {
            return $TCLENV(help:curDir)}
        if {"$TCLENV(help:curDir)" == "/"} {
            set pathName "/$pathName"
        } else {
            set pathName "$TCLENV(help:curDir)/$pathName"
        }
    }
    set pathName [help:flattenPath $pathName]
    if {[string match "*/" $pathName] && ($pathName != "/")} {
        set pathName [csubstr $pathName 0 [expr [length $pathName]-1]]}

    return $pathName    
}


proc help:Display {line} {
    global TCLENV
    if {$TCLENV(help:lineCnt) >= 23} {
        set TCLENV(help:lineCnt) 0
        puts stdout ":" nonewline
        flush stdout
        gets stdin response
        if {![lempty $response]} {
            return 0}
    }
    puts stdout $line
    incr TCLENV(help:lineCnt)
}


proc help:DisplayFile {filepath} {

    set inFH [open $filepath r]
    while {[gets $inFH fileBuf] >= 0} {
        if {![help:Display $fileBuf]} {
            break}
    }
    close $inFH

}    


proc help:ListDir {dirPath} {
    set dirList {}
    set fileList {}
    if {[catch {set dirFiles [glob $dirPath/*]}] != 0} {
        error "No files in subject directory: $dirPath"}
    foreach fileName $dirFiles {
        if [file isdirectory $fileName] {
            lappend dirList "[file tail $fileName]/"
        } else {
            lappend fileList [file tail $fileName]
        }
    }
   return [list [lsort $dirList] [lsort $fileList]]
}


proc help:DisplayColumns {nameList} {
    set count 0
    set outLine ""
    foreach name $nameList {
        if {$count == 0} {
            append outLine "   "}
        append outLine $name
        if {[incr count] < 4} {
            set padLen [expr 17-[clength $name]]
            if {$padLen < 3} {
               set padLen 3}
            append outLine [replicate " " $padLen]
        } else {
           if {![help:Display $outLine]} {
               return}
           set outLine ""
           set count 0
        }
    }
    if {$count != 0} {
        help:Display $outLine}
    return
}



proc help {{subject {}}} {
    global TCLENV

    set TCLENV(help:lineCnt) 0


    if {($subject == "help") || ($subject == "?")} {
        help:DisplayFile "$TCLENV(help:root)/help"
        return
    }

    set request [help:EvalPath $subject]
    set requestPath "$TCLENV(help:root)$request"

    if {![file exists $requestPath]} {
        error "Help:\"$request\" does not exist"}
    
    if [file isdirectory $requestPath] {
        set dirList [help:ListDir $requestPath]
        set subList  [lindex $dirList 0]
        set fileList [lindex $dirList 1]
        if {[llength $subList] != 0} {
            help:Display "\nSubjects available in $request:"
            help:DisplayColumns $subList
        }
        if {[llength $fileList] != 0} {
            help:Display "\nHelp files available in $request:"
            help:DisplayColumns $fileList
        }
    } else {
        help:DisplayFile $requestPath
    }
    return
}



proc helpcd {{dir /}} {
    global TCLENV

    set request [help:EvalPath $dir]
    set requestPath "$TCLENV(help:root)$request"

    if {![file exists $requestPath]} {
        error "Helpcd: \"$request\" does not exist"}
    
    if {![file isdirectory $requestPath]} {
        error "Helpcd: \"$request\" is not a directory"}

    set TCLENV(help:curDir) $request
    return    
}


proc helppwd {} {
        global TCLENV
        echo "Current help subject directory: $TCLENV(help:curDir)"
}


proc apropos {name} {
    global TCLENV

    set TCLENV(help:lineCnt) 0

    set aproposCT [scancontext create]
    scanmatch -nocase $aproposCT $name {
        set path [lindex $matchInfo(line) 0]
        set desc [lrange $matchInfo(line) 1 end]
        if {![help:Display [format "%s - %s" $path $desc]]} {
            return}
    }
    foreach brief [glob -nocomplain $TCLENV(help:root)/*.brf] {
        set briefFH [open $brief]
        scanfile $aproposCT $briefFH
        close $briefFH
    }
    scancontext delete $aproposCT
}

global TCLENV TCLPATH

set TCLENV(help:root) [searchpath $TCLPATH help]
set TCLENV(help:curDir) "/"
set TCLENV(help:outBuf) {}

#@package: TclX-packages packages autoprocs

proc packages {{option {}}} {
    global TCLENV
    set packList {}
    foreach key [array names TCLENV] {
        if {[string match "PKG:*" $key]} {
            lappend packList [string range $key 4 end]
        }
    }
    if [lempty $option] {
        return $packList
    } else {
        if {$option != "-location"} {
            error "Unknow option \"$option\", expected \"-location\""
        }
        set locList {}
        foreach pack $packList {
            set fileId [lindex $TCLENV(PKG:$pack) 0]
            
            lappend locList [list $pack [concat $TCLENV($fileId) \
                                             [lrange $TCLENV(PKG:$pack) 1 2]]]
        }
        return $locList
    }
}

proc autoprocs {} {
    global TCLENV
    set procList {}
    foreach key [array names TCLENV] {
        if {[string match "PROC:*" $key]} {
            lappend procList [string range $key 5 end]
        }
    }
    return $procList
}

#@package: TclX-directory_stack pushd popd dirs

global TCLENV(dirPushList)

set TCLENV(dirPushList) ""

proc pushd {args} {
    global TCLENV

    if {[llength $args] > 1} {
        error "bad # args: pushd [dir_to_cd_to]"
    }
    set TCLENV(dirPushList) [linsert $TCLENV(dirPushList) 0 [pwd]]

    if {[llength $args] != 0} {
        cd [glob $args]
    }
}

proc popd {} {
    global TCLENV

    if [llength $TCLENV(dirPushList)] {
        cd [lvarpop TCLENV(dirPushList)]
        pwd
    } else {
        error "directory stack empty"
    }
}

proc dirs {} { 
    global TCLENV
    echo [pwd] $TCLENV(dirPushList)
}

#@package: TclX-set_functions union intersect intersect3 lrmdups

proc union {lista listb} {
    set full_list [lsort [concat $lista $listb]]
    set check_element [lindex $full_list 0]
    set outlist $check_element
    foreach element [lrange $full_list 1 end] {
	if {$check_element == $element} continue
	lappend outlist $element
	set check_element $element
    }
    return $outlist
}

proc lrmdups {list} {
    set list [lsort $list]
    set result [lvarpop list]
    lappend last $result
    foreach element $list {
	if {$last != $element} {
	    lappend result $element
	    set last $element
	}
    }
    return $result
}


proc intersect3 {list1 list2} {
    set list1Result ""
    set list2Result ""
    set intersectList ""

    set list1 [lrmdups $list1]
    set list2 [lrmdups $list2]

    while {1} {
        if [lempty $list1] {
            if ![lempty $list2] {
                set list2Result [concat $list2Result $list2]
            }
            break
        }
        if [lempty $list2] {
	    set list1Result [concat $list1Result $list1]
            break
        }
        set compareResult [string compare [lindex $list1 0] [lindex $list2 0]]

        if {$compareResult < 0} {
            lappend list1Result [lvarpop list1]
            continue
        }
        if {$compareResult > 0} {
            lappend list2Result [lvarpop list2]
            continue
        }
        lappend intersectList [lvarpop list1]
        lvarpop list2
    }
    return [list $list1Result $intersectList $list2Result]
}

proc intersect {list1 list2} {
    set intersectList ""

    set list1 [lsort $list1]
    set list2 [lsort $list2]

    while {1} {
        if {[lempty $list1] || [lempty $list2]} break

        set compareResult [string compare [lindex $list1 0] [lindex $list2 0]]

        if {$compareResult < 0} {
            lvarpop list1
            continue
        }

        if {$compareResult > 0} {
            lvarpop list2
            continue
        }

        lappend intersectList [lvarpop list1]
        lvarpop list2
    }
    return $intersectList
}



#@package: TclX-show_procedures showproc showprocs

proc showproc {procname} {
    if [lempty [info procs $procname]] {demand_load $procname}
	set arglist [info args $procname]
	set nargs {}
	while {[llength $arglist] > 0} {
	    set varg [lvarpop arglist 0]
	    if [info default $procname $varg defarg] {
		lappend nargs [list $varg $defarg]
	    } else {
		lappend nargs $varg
	    }
    }
    format "proc %s \{%s\} \{%s\}\n" $procname $nargs [info body $procname]
}

proc showprocs {args} {
    if [lempty $args] { set args [info procs] }
    set out ""

    foreach i $args {
	foreach j $i { append out [showproc $j] "\n"}
    }
    return $out
}


#@package: TclX-stringfile_functions read_file write_file

proc read_file {fileName {numBytes {}}} {
    set fp [open $fileName]
    if {$numBytes != ""} {
        set result [read $fp $numBytes]
    } else {
        set result [read $fp]
    }
    close $fp
    return $result
} 

proc write_file {fileName args} {
    set fp [open $fileName w]
    foreach string $args {
        puts $fp $string
    }
    close $fp
}


#@package: TclX-Compatibility execvp

proc execvp {progname args} {
    execl $progname $args
}

#@package: TclX-convertlib convert_lib

proc convert_lib {tclIndex packageLib {ignore {}}} {
    if {[file tail $tclIndex] != "tclIndex"} {
        error "Tail file name numt be `tclIndex': $tclIndex"}
    set srcDir [file dirname $tclIndex]

    if {[file extension $packageLib] != ".tlib"} {
        append packageLib ".tlib"}


    set tclIndexFH [open $tclIndex r]
    while {[gets $tclIndexFH line] >= 0} {
        if {([cindex $line 0] == "#") || ([llength $line] != 2)} {
            continue}
        if {[lsearch $ignore [lindex $line 1]] >= 0} {
            continue}
        lappend entryTable([lindex $line 1]) [lindex $line 0]
    }
    close $tclIndexFH

    set libFH [open $packageLib w]
    foreach srcFile [array names entryTable] {
        set srcFH [open $srcDir/$srcFile r]
        puts $libFH "#@package: $srcFile $entryTable($srcFile)\n"
        copyfile $srcFH $libFH
        close $srcFH
    }
    close $libFH
    buildpackageindex $packageLib
}

#@package: TclX-profrep profrep

proc profrep:summarize {profDataVar stackDepth sumProfDataVar} {
    upvar $profDataVar profData $sumProfDataVar sumProfData

    if {(![info exists profData]) || ([catch {array size profData}] != 0)} {
        error "`profDataVar' must be the name of an array returned by the `profile off' command"
    }
    set maxNameLen 0
    foreach procStack [array names profData] {
        if {[llength $procStack] < $stackDepth} {
            set sigProcStack $procStack
        } else {
            set sigProcStack [lrange $procStack 0 [expr {$stackDepth - 1}]]
        }
        set maxNameLen [max $maxNameLen [clength $sigProcStack]]
        if [info exists sumProfData($sigProcStack)] {
            set cur $sumProfData($sigProcStack)
            set add $profData($procStack)
            set     new [expr [lindex $cur 0]+[lindex $add 0]]
            lappend new [expr [lindex $cur 1]+[lindex $add 1]]
            lappend new [expr [lindex $cur 2]+[lindex $add 2]]
            set $sumProfData($sigProcStack) $new
        } else {
            set sumProfData($sigProcStack) $profData($procStack)
        }
    }
    return $maxNameLen
}

proc profrep:sort {sumProfDataVar sortKey} {
    upvar $sumProfDataVar sumProfData

    case $sortKey {
        {calls} {set keyIndex 0}
        {real}  {set keyIndex 1}
        {cpu}   {set keyIndex 2}
        default {
            error "Expected a sort of: `calls',  `cpu' or ` real'"}
    }


    foreach procStack [array names sumProfData] {
        set key [format "%016d" [lindex $sumProfData($procStack) $keyIndex]]
        lappend keyProcList [list $key $procStack]
    }
    set keyProcList [lsort $keyProcList]


    for {set idx [expr [llength $keyProcList]-1]} {$idx >= 0} {incr idx -1} {
        lappend sortedProcList [lindex [lindex $keyProcList $idx] 1]
    }
    return $sortedProcList
}


proc profrep:print {sumProfDataVar sortedProcList maxNameLen outFile
                    userTitle} {
    upvar $sumProfDataVar sumProfData
    
    if {$outFile == ""} {
        set outFH stdout
    } else {
        set outFH [open $outFile w]
    }


    set stackTitle "Procedure Call Stack"
    set maxNameLen [max $maxNameLen [clength $stackTitle]]
    set hdr [format "%-${maxNameLen}s %10s %10s %10s" $stackTitle \
                    "Calls" "Real Time" "CPU Time"]
    if {$userTitle != ""} {
        puts $outFH [replicate - [clength $hdr]]
        puts $outFH $userTitle
    }
    puts $outFH [replicate - [clength $hdr]]
    puts $outFH $hdr
    puts $outFH [replicate - [clength $hdr]]


    foreach procStack $sortedProcList {
        set data $sumProfData($procStack)
        puts $outFH [format "%-${maxNameLen}s %10d %10d %10d" $procStack \
                            [lindex $data 0] [lindex $data 1] [lindex $data 2]]
    }
    if {$outFile != ""} {
        close $outFH
    }
}


proc profrep {profDataVar sortKey stackDepth {outFile {}} {userTitle {}}} {
    upvar $profDataVar profData

    set maxNameLen [profrep:summarize profData $stackDepth sumProfData]
    set sortedProcList [profrep:sort sumProfData $sortKey]
    profrep:print sumProfData $sortedProcList $maxNameLen $outFile $userTitle

}
