<?php

/* vim: set expandtab shiftwidth=4 tabstop=4 softtabstop=4 foldmethod=marker textwidth=80: */

/**
 * Linked list structure
 * 
 * This package implements a doubly linked list structure. Each node
 * (Structures_LinkedList_DoubleNode object) in the list
 * (Structures_LinkedList_Double) knows the previous node and the next
 * node in the list. Unlike an array, you can insert or delete nodes at
 * arbitrary points in the list.
 *
 * If your application normally traverses the linked list in a forward-only
 * direction, use the singly-linked list implemented by
 * {@link Structures_LinkedList_Single}. If, however, your application
 * needs to traverse the list backwards, or insert nodes into the list before
 * other nodes in the list, use the double-linked list implemented by
 * {@link Structures_LinkedList_Double} to give your application better
 * performance at the cost of a slightly larger memory footprint.
 *
 * Structures_LinkedList_Double implements the Iterator interface so control
 * structures like foreach($list as $node) and while($list->next()) work
 * as expected.
 *
 * To use this package, derive a child class from
 * Structures_LinkedList_DoubleNode  and add data to the object. Then use the
 * Structures_LinkedList_Double class to access the nodes.
 *
 * PHP version 5
 *
 * LICENSE:  Copyright 2006 Dan Scott
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @category   Structures
 * @package    Structures_LinkedList_Single
 * @author     Dan Scott <dscott@laurentian.ca>
 * @copyright  2006 Dan Scott
 * @license    http://www.apache.org/licenses/LICENSE-2.0  Apache License, Version 2.0
 * @version    CVS: $Id:$
 * @link       http://pear.php.net/package/Structures_LinkedList_Single
 * @example    double_link_example.php
 *
 * @todo Add some actual error conditions
 **/

require_once 'PEAR/Exception.php';
require_once 
'Single.php';

// {{{ class Structures_LinkedList_Double
/**
 * The Structures_LinkedList_Double class represents a linked list structure
 * composed of {@link Structures_LinkedList_DoubleNode} objects.
 *
 * @category   Structures
 * @package    Structures_LinkedList_Single
 * @author     Dan Scott <dscott@laurentian.ca>
 * @license    http://www.apache.org/licenses/LICENSE-2.0  Apache License, Version 2.0
 * @link       http://pear.php.net/package/Structures_LinkedList_Single
 */
class Structures_LinkedList_Double extends Structures_LinkedList_Single implements Iterator {
    
// {{{ properties
    /**
     * Tail node of the linked list
     * @var Structures_LinkedList_DoubleNode
     */
    
protected $tail_node;
    
// }}}

    // {{{ Constructor: function __construct()
    /**
     * Structures_LinkedList_Double constructor
     *
     * @param Structures_LinkedList_DoubleNode $root root node for the
     * linked list
     */
    
function __construct(Structures_LinkedList_DoubleNode $root null)
    {
        if (
$root) {
            
$this->tail_node $root;
        } else {
            
$this->tail_node null;
        }
        
parent::__construct($root);
    }
    
// }}}

    // {{{ function end()
    /**
     * Sets the pointer for the linked list to its last node
     *
     * @return Structures_LinkedList_DoubleNode last node in the linked list
     */
    
public function end()
    {
        if (
$this->tail_node) {
            
$this->current $this->tail_node;
        } else {
            
$this->current null;
        }
        return 
$this->current;
    }
    
// }}}

    // {{{ function previous()
    /**
     * Sets the pointer for the linked list to the previous node and
     * returns that node
     *
     * @return Structures_LinkedList_DoubleNode previous node in the linked list
     */
    
public function previous()
    {
        if (!
$this->current()->previous()) {
            return 
false;
        }
        
$this->current $this->current()->previous();
        return 
$this->current();
    }
    
// }}}

    // {{{ function insertNode()
    /**
     * Inserts a {@link Structures_LinkedList_DoubleNode} object into the linked
     * list, based on a reference node that already exists in the list.
     *
     * @param Structures_LinkedList_DoubleNode $new_node New node to add to
     * the list
     * @param Structures_LinkedList_DoubleNode $existing_node Reference
     * position node
     * @param bool $before Insert new node before or after the existing node
     * @return bool Success or failure
     **/
    
public function insertNode(Structures_LinkedList_DoubleNode $new_nodeStructures_LinkedList_DoubleNode $existing_node$before false)
    {
        if (!
$this->root_node) {
            
$this->__construct($new_node);
        }

        
// Now add the node according to the requested mode
        
switch ($before) {

        case 
true:
            
$previous_node $existing_node->previous();
            if (
$previous_node) {
                
$previous_node->setNext($new_node);
                
$new_node->setPrevious($previous_node);
            } else {
                
// The existing node must be root node; make new node root
                
$this->root_node $new_node;
                
$new_node->setPrevious();
            }
            
$new_node->setNext($existing_node);
            
$existing_node->setPrevious($new_node);

            break;

        case 
false:
            
$new_node->setPrevious($existing_node);
            
$next_node $existing_node->next();
            if (
$next_node) {
                
$new_node->setNext($next_node);
            } else {
                
// The existing node must have been the tail node
                
$this->tail_node $new_node;
            }
            
$existing_node->setNext($new_node);

            break;

        }

        return 
true;
    }
    
// }}}

    // {{{ private function _getTailNode()
    /**
     * Returns the tail node of the linked list.
     *
     * This is a cheap operation for a doubly-linked list.
     *
     * @param Structures_LinkedList_DoubleNode $new_node New node to append
     * @return bool Success or failure
     **/
    
private function _getTailNode()
    {
        return 
$this->tail_node;
    }
    
// }}}

    // {{{ function deleteNode()
    /**
     * Deletes a {@link Structures_LinkedList_DoubleNode} from the list.
     *
     * @param Structures_LinkedList_DoubleNode $node Node to delete.
     */
    
public function deleteNode(Structures_LinkedList_DoubleNode $node)
    {
        
/* If this is the root node, and there are more nodes in the list,
         * make the next node the new root node before deleting this node.
         */
        
if ($node === $this->root_node) {
            
$this->root_node $node->next();
        }
        
        
/* If this is the tail node, and there are more nodes in the list,
         * make the previous node the tail node before deleting this node
         */
        
if ($node === $this->tail_node) {
            
$this->tail_node $node->previous();
        }

        
/* If this is the current node, and there are other nodes in the list,
         * try making the previous node the current node so that next() works
         * as expected.
         *
         * If that fails, make the next node the current node.
         *
         * If that fails, null isn't such a bad place to be.
         */
        
if ($node === $this->current) {
            if (
$node->previous()) {
                
$this->current $node->previous();
            } elseif (
$node->next()) {
                
$this->current $node->next();
            } else {
                
$this->current null;
            }
        }
        
$node->__destruct();
    }
    
// }}}

}
// }}}

// {{{ class Structures_LinkedList_DoubleNode
/**
 * The Structures_LinkedList_DoubleNode class represents a node in a
 * {@link Structures_LinkedList_Double} linked list structure.
 *
 * @category   Structures
 * @package    Structures_LinkedList_Single
 * @author     Dan Scott <dscott@laurentian.ca>
 * @license    http://www.apache.org/licenses/LICENSE-2.0  Apache License, Version 2.0
 * @link       http://pear.php.net/package/Structures_LinkedList_Single
 */
class Structures_LinkedList_DoubleNode extends Structures_LinkedList_SingleNode {
    
// {{{ properties
    /**
     * Previous node in the linked list
     * @var Structures_LinkedList_DoubleNode
     */
    
protected $previous;
    
// }}}

    // {{{ Constructor: function __construct()
    /**
     * Structures_LinkedList_DoubleNode constructor
     */
    
public function __construct()
    {
        
$this->next null;
        
$this->previous null;
    }
    
// }}}

    // {{{ Destructor: function __destruct()
    /**
     * Removes node from the list, adjusting the related nodes accordingly.
     *
     * This is a problem if the node is the root node for the list.
     * At this point, however, we do not have access to the list itself. Hmm.
     */
    
public function __destruct()
    {
        
$next $this->next();
        
$previous $this->previous();
        if (
$previous && $next) {
            
$previous->setNext($next);
            
$next->setPrevious($previous);
        } elseif (
$previous) {
            
$previous->setNext();
        } elseif (
$next) {
            
$next->setPrevious();
        }
    }
    
// }}}

    // {{{ function previous()
    /**
     * Return the previous node in the linked list
     *
     * @return Structures_LinkedList_DoubleNode previous node in the linked list
     */
    
public function previous()
    {
        if (
$this->previous) {
            return 
$this->previous;
        } else {
            return 
false;
        }
    }
    
// }}}

    // {{{ function setPrevious()
    /**
     * Sets the pointer for the previous node in the linked list
     * to the specified node
     *
     * @param Structures_LinkedList_DoubleNode $node new previous node
     * in the linked list
     * @return Structures_LinkedList_DoubleNode new previous node in
     * the linked list
     */
    
public function setPrevious(Structures_LinkedList_DoubleNode $node null)
    {
        
$this->previous $node;
        return 
$this->previous;
    }
    
// }}}

}
// }}}

?>