//////////////////////////////////////////////////////////////////////
// LibFile: stacks.scad
//   Stack data structure implementation.
//   To use, add the following lines to the beginning of your file:
//   ```
//   use <BOSL2/std.scad>
//   use <BOSL2/stacks.scad>
//   ```
//////////////////////////////////////////////////////////////////////


// Section: Stack Data Structure
//   A stack is a last-in-first-out collection of items.  You can push items onto the top of the
//   stack, or pop the top item off.  While you can treat a stack as an opaque data type, using the
//   functions below, it's simply implemented as a list.  This means that you can use any list
//   function to manipulate the stack.  The last item in the list is the topmost stack item.
//   The depth of an item is how far buried in the stack that item is.  An item at depth 1 is the
//   top-most stack item.  An item at depth 3 is two items below the top-most stack item.


// Function: stack_init()
// Usage:
//   stack = stack_init();
// Description:
//   Creates an empty stack/list.
// Example:
//   stack = stack_init();  // Return: []
function stack_init() = [];


// Function: stack_empty()
// Usage:
//   if (stack_empty(stack)) ...
// Description:
//   Returns true if the given stack is empty.
// Arguments:
//   stack = The stack to test if empty.
// Example:
//   stack = stack_init();
//   is_empty = stack_empty(stack);  // Returns: true
//   stack2 = stack_push(stack, "foo");
//   is_empty2 = stack_empty(stack2);  // Returns: false
function stack_empty(stack) =
    assert(is_list(stack))
    len(stack)==0;


// Function: stack_depth()
// Usage:
//   depth = stack_depth(stack);
// Description:
//   Returns the depth of the given stack.
// Arguments:
//   stack = The stack to get the depth of.
// Example:
//   stack = stack_init();
//   depth = stack_depth(stack);  // Returns: 0
//   stack2 = stack_push(stack, "foo");
//   depth2 = stack_depth(stack2);  // Returns: 1
//   stack3 = stack_push(stack2, ["bar","baz","qux"]);
//   depth3 = stack_depth(stack3);  // Returns: 4
function stack_depth(stack) =
    assert(is_list(stack))
    len(stack);


// Function: stack_top()
// Usage:
//   item = stack_top(stack);
//   list = stack_top(stack,n);
// Description:
//   If n is not given, returns the topmost item of the given stack.
//   If n is given, returns a list of the `n` topmost items.
// Arguments:
//   stack = The stack/list to get the top item(s) of.
// Example:
//   stack = [4,5,6,7];
//   item = stack_top(stack);  // Returns: 7
//   list = stack_top(stack,n=3);  // Returns: [5,6,7]
function stack_top(stack,n=undef) =
    assert(is_list(stack))
    is_undef(n)? (
        stack[len(stack)-1]
    ) : (
        let(stacksize = len(stack))
        assert(is_num(n))
        assert(n>=0)
        assert(stacksize>=n, "stack underflow")
        [for (i=[0:1:n-1]) stack[stacksize-n+i]]
    );


// Function: stack_peek()
// Usage:
//   item = stack_peek(stack,[depth]);
//   list = stack_peek(stack,depth,n);
// Description:
//   If `n` is not given, returns the stack item at depth `depth`.
//   If `n` is given, returns a list of the `n` stack items at and above depth `depth`.
// Arguments:
//   stack = The stack to read from.
//   depth = The depth of the stack item to read.  Default: 0
//   n = The number of stack items to return.  Default: undef (Return only the stack item at `depth`)
// Example:
//   stack = [2,3,4,5,6,7,8,9];
//   item = stack_peek(stack);  // Returns: 9
//   item2 = stack_peek(stack, 3);  // Returns: 7
//   list = stack_peek(stack, 6, 4);  // Returns: [4,5,6,7]
function stack_peek(stack,depth=0,n=undef) =
    assert(is_list(stack))
    assert(is_num(depth))
    assert(depth>=0)
    let(stacksize = len(stack))
    assert(stacksize>=depth, "stack underflow")
    is_undef(n)? (
        stack[stacksize-depth-1]
    ) : (
        assert(is_num(n))
        assert(n>=0)
        assert(n<=depth+1)
        [for (i=[0:1:n-1]) stack[stacksize-1-depth+i]]
    );


// Function: stack_push()
// Usage:
//   modified_stack = stack_push(stack,items);
// Description:
//   Pushes the given `items` onto the stack `stack`.  Returns the modified stack.
// Arguments:
//   stack = The stack to modify.
//   items = A value or list of values to push onto the stack.
// Example:
//   stack = [4,9,2,3];
//   stack2 = stack_push(stack,7);  // Returns: [4,9,2,3,7]
//   stack3 = stack_push(stack2,[6,1]);  // Returns: [4,9,2,3,7,6,1]
//   stack4 = stack_push(stack,[[5,8]]);  // Returns: [4,9,2,3,[5,8]]
//   stack5 = stack_push(stack,[[5,8],6,7]);  // Returns: [4,9,2,3,[5,8],6,7]
function stack_push(stack,items) =
    assert(is_list(stack))
    is_list(items)? concat(stack, items) : concat(stack, [items]);


// Function: stack_pop()
// Usage:
//   modified_stack = stack_pop(stack, [n]);
// Description:
//   Removes the `n` topmost items from the stack.  Returns the modified stack.
// Arguments:
//   stack = The stack to modify.
//   n = The number of items to remove off the top of the stack.  Default: 1
// Example:
//   stack = [4,5,6,7,8,9];
//   stack2 = stack_pop(stack);  // Returns: [4,5,6,7,8]
//   stack3 = stack_pop(stack2,n=3);  // Returns: [4,5]
function stack_pop(stack,n=1) =
    assert(is_list(stack))
    assert(is_num(n))
    assert(n>=0)
    assert(len(stack)>=n, "stack underflow")
    [for (i = [0:1:len(stack)-1-n]) stack[i]];


// Function: stack_rotate()
// Usage:
//   modified_stack = stack_rotate(stack, [n]);
// Description:
//   Rotates the top `abs(n)` stack items, and returns the modified stack.
//   If `n` is positive, then the depth `n` stack item is rotated (left) to the top.
//   If `n` is negative, then the top stack item is rotated (right) to depth `abs(n)`.
// Arguments:
//   stack = The stack to modify.
//   n = The number of stack items to rotate.  If negative, reverse rotation direction.  Default: 3
// Example:
//   stack = [4,5,6,7,8];
//   stack2 = stack_rotate(stack,3);  // Returns: [4,5,7,8,6]
//   stack3 = stack_rotate(stack2,-4);  // Returns: [4,6,5,7,8]
function stack_rotate(stack,n=3) =
    assert(is_list(stack))
    let(stacksize = len(stack))
    assert(stacksize>=n, "stack underflow")
    n>=0? concat(
        [for (i=[0:1:stacksize-1-n]) stack[i]],
        [for (i=[0:1:n-2]) stack[stacksize-n+i+1]],
        [stack[stacksize-n]]
    ) : concat(
        [for (i=[0:1:stacksize-1+n]) stack[i]],
        [stack[stacksize-1]],
        [for (i=[0:1:-n-2]) stack[stacksize+n+i]]
    );


// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap