# stack.tcl --
#
#	Stack implementation for Tcl.
#
# Copyright (c) 1998-2000 by Ajuba Solutions.
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
# 
# RCS: @(#) $Id: stack_tcl.tcl,v 1.2 2009/11/26 04:42:16 andreas_kupries Exp $

namespace eval ::struct {}

namespace eval ::struct::stack {
    # The stacks array holds all of the stacks you've made
    variable stacks
    
    # counter is used to give a unique name for unnamed stacks
    variable counter 0

    # Only export one command, the one used to instantiate a new stack
    namespace export stack_tcl
}

# ::struct::stack::stack_tcl --
#
#	Create a new stack with a given name; if no name is given, use
#	stackX, where X is a number.
#
# Arguments:
#	name	name of the stack; if null, generate one.
#
# Results:
#	name	name of the stack created

proc ::struct::stack::stack_tcl {args} {
    variable stacks
    variable counter
    
    switch -exact -- [llength [info level 0]] {
	1 {
	    # Missing name, generate one.
	    incr counter
	    set name "stack${counter}"
	}
	2 {
	    # Standard call. New empty stack.
	    set name [lindex $args 0]
	}
	default {
	    # Error.
	    return -code error \
		    "wrong # args: should be \"stack ?name?\""
	}
    }

    # FIRST, qualify the name.
    if {![string match "::*" $name]} {
        # Get caller's namespace; append :: if not global namespace.
        set ns [uplevel 1 [list namespace current]]
        if {"::" != $ns} {
            append ns "::"
        }

        set name "$ns$name"
    }
    if {[llength [info commands $name]]} {
	return -code error \
		"command \"$name\" already exists, unable to create stack"
    }

    set stacks($name) [list ]

    # Create the command to manipulate the stack
    interp alias {} $name {} ::struct::stack::StackProc $name

    return $name
}

##########################
# Private functions follow

# ::struct::stack::StackProc --
#
#	Command that processes all stack object commands.
#
# Arguments:
#	name	name of the stack object to manipulate.
#	args	command name and args for the command
#
# Results:
#	Varies based on command to perform

proc ::struct::stack::StackProc {name cmd args} {
    # Do minimal args checks here
    if { [llength [info level 0]] == 2 } {
	return -code error "wrong # args: should be \"$name option ?arg arg ...?\""
    }

    # Split the args into command and args components
    set sub _$cmd
    if { [llength [info commands ::struct::stack::$sub]] == 0 } {
	set optlist [lsort [info commands ::struct::stack::_*]]
	set xlist {}
	foreach p $optlist {
	    set p [namespace tail $p]
	    lappend xlist [string range $p 1 end]
	}
	set optlist [linsert [join $xlist ", "] "end-1" "or"]
	return -code error \
		"bad option \"$cmd\": must be $optlist"
    }

    uplevel 1 [linsert $args 0 ::struct::stack::$sub $name]
}

# ::struct::stack::_clear --
#
#	Clear a stack.
#
# Arguments:
#	name	name of the stack object.
#
# Results:
#	None.

proc ::struct::stack::_clear {name} {
    set ::struct::stack::stacks($name) [list]
    return
}

# ::struct::stack::_destroy --
#
#	Destroy a stack object by removing it's storage space and 
#	eliminating it's proc.
#
# Arguments:
#	name	name of the stack object.
#
# Results:
#	None.

proc ::struct::stack::_destroy {name} {
    unset ::struct::stack::stacks($name)
    interp alias {} $name {}
    return
}

# ::struct::stack::_get --
#
#	Retrieve the whole contents of the stack.
#
# Arguments:
#	name	name of the stack object.
#
# Results:
#	items	list of all items in the stack.

proc ::struct::stack::_get {name} {
    variable stacks
    upvar 0  stacks($name) mystack

    set n [llength $mystack]
    if {$n == 1} {
	return [list [_peek $name $n]]
    } elseif {$n > 0} {
	return [_peek $name $n]
    } else {
	return {}
    }
}

# ::struct::stack::_peek --
#
#	Retrieve the value of an item on the stack without popping it.
#
# Arguments:
#	name	name of the stack object.
#	count	number of items to pop; defaults to 1
#
# Results:
#	items	top count items from the stack; if there are not enough items
#		to fulfill the request, throws an error.

proc ::struct::stack::_peek {name {count 1}} {
    variable stacks
    upvar 0  stacks($name) mystack

    if { $count < 1 } {
	return -code error "invalid item count $count"
    } elseif { $count > [llength $mystack] } {
	return -code error "insufficient items on stack to fill request"
    }

    if { $count == 1 } {
	# Handle this as a special case, so single item peeks are not
	# listified
	return [lindex $mystack end]
    }

    # Otherwise, return a list of items
    incr count -1
    return [lreverse [lrange $mystack end-$count end]]
}

# ::struct::stack::_trim --
#
#	Pop items off a stack until a maximum size is reached.
#
# Arguments:
#	name	name of the stack object.
#	count	requested size of the stack.
#
# Results:
#	item	List of items trimmed, may be empty.

proc ::struct::stack::_trim {name newsize} {
    variable stacks
    upvar 0  stacks($name) mystack

    if { ![string is integer -strict $newsize]} {
	return -code error "expected integer but got \"$newsize\""
    } elseif { $newsize < 0 } {
	return -code error "invalid size $newsize"
    } elseif { $newsize >= [llength $mystack] } {
	# Stack is smaller than requested, do nothing.
	return {}
    }

    # newsize < [llength $mystack]
    # pop '[llength $mystack]' - newsize elements.

    set n [expr {[llength $mystack] - $newsize}]
    if {$n == 1} {
	return [list [_pop $name $n]]
    } elseif {$n > 1} {
	return [_pop $name $n]
    } else {
	return {}
    }
}

# ::struct::stack::_pop --
#
#	Pop an item off a stack.
#
# Arguments:
#	name	name of the stack object.
#	count	number of items to pop; defaults to 1
#
# Results:
#	item	top count items from the stack; if the stack is empty, 
#		returns a list of count nulls.

proc ::struct::stack::_pop {name {count 1}} {
    variable stacks
    upvar 0  stacks($name) mystack

    if { $count < 1 } {
	return -code error "invalid item count $count"
    } elseif { $count > [llength $mystack] } {
	return -code error "insufficient items on stack to fill request"
    }

    if { $count == 1 } {
	# Handle this as a special case, so single item pops are not
	# listified
	set item [lindex $mystack end]
	if {$count == [llength $mystack]} {
	    set mystack [list]
	} else {
	    set mystack [lreplace [K $mystack [unset mystack]] end end]
	}
	return $item
    }

    # Otherwise, return a list of items, and remove the items from the
    # stack.
    if {$count == [llength $mystack]} {
	set result  [lreverse [K $mystack [unset mystack]]]
	set mystack [list]
    } else {
	incr count -1
	set result  [lreverse [lrange $mystack end-$count end]]
	set mystack [lreplace [K $mystack [unset mystack]] end-$count end]
    }
    return $result
}

# ::struct::stack::_push --
#
#	Push an item onto a stack.
#
# Arguments:
#	name	name of the stack object
#	args	items to push.
#
# Results:
#	None.

proc ::struct::stack::_push {name args} {
    if { [llength $args] == 0 } {
	return -code error "wrong # args: should be \"$name push item ?item ...?\""
    }
    variable stacks
    upvar 0  stacks($name) mystack
    if {[llength $args] == 1} {
        lappend mystack [lindex $args 0]
    } else {
	# 8.5: lappend mystack {*}$args
	eval [linsert $args 0 lappend mystack]
    }
    return
}

# ::struct::stack::_rotate --
#
#	Rotate the top count number of items by step number of steps.
#
# Arguments:
#	name	name of the stack object.
#	count	number of items to rotate.
#	steps	number of steps to rotate.
#
# Results:
#	None.

proc ::struct::stack::_rotate {name count steps} {
    variable stacks
    upvar 0  stacks($name) mystack
    set len [llength $mystack]
    if { $count > $len } {
	return -code error "insufficient items on stack to fill request"
    }

    # Rotation algorithm:
    # do
    #   Find the insertion point in the stack
    #   Move the end item to the insertion point
    # repeat $steps times

    set start [expr {$len - $count}]
    set steps [expr {$steps % $count}]

    if {$steps == 0} return

    for {set i 0} {$i < $steps} {incr i} {
	set item [lindex $mystack end]
	set mystack [linsert \
			 [lreplace \
			      [K $mystack [unset mystack]] \
			      end end] $start $item]
    }
    return
}

# ::struct::stack::_size --
#
#	Return the number of objects on a stack.
#
# Arguments:
#	name	name of the stack object.
#
# Results:
#	count	number of items on the stack.

proc ::struct::stack::_size {name} {
    return [llength $::struct::stack::stacks($name)]
}

# ### ### ### ######### ######### #########

proc ::struct::stack::K {x y} { set x }

if {![llength [info commands lreverse]]} {
    proc ::struct::stack::lreverse {x} {
	# assert (llength(x) > 1)
	set r [list]
	set l [llength $x]
	while {$l} { lappend r [lindex $x [incr l -1]] }
	return $r
    }
}

# ### ### ### ######### ######### #########
## Ready

namespace eval ::struct {
    # Get 'stack::stack' into the general structure namespace for
    # pickup by the main management.
    namespace import -force stack::stack_tcl
}
