/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:  
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xerces" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written 
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation and was
 * originally based on software copyright (c) 1999, International
 * Business Machines, Inc., http://www.apache.org.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.apache.xerces.dom;

import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.TreeWalker;

/** This class implements the TreeWalker interface. */
/**
 * @version $Id: TreeWalkerImpl.java,v 1.8 2003/07/29 18:29:40 elena Exp $
 */

public class TreeWalkerImpl implements TreeWalker {
    
    //
    // Data
    //
    
    /** When TRUE, the children of entites references are returned in the iterator. */
    private boolean fEntityReferenceExpansion = false;
    /** The whatToShow mask. */
    int fWhatToShow = NodeFilter.SHOW_ALL;
    /** The NodeFilter reference. */
    NodeFilter fNodeFilter;
    /** The current Node. */
    Node fCurrentNode;
    /** The root Node. */
    Node fRoot;
    
    //
    // Implementation Note: No state is kept except the data above
    // (fWhatToShow, fNodeFilter, fCurrentNode, fRoot) such that 
    // setters could be created for these data values and the 
    // implementation will still work.
    
    
    // 
    // Constructor
    //
    
    /** Public constructor */
    public TreeWalkerImpl(Node root, 
                          int whatToShow, 
                          NodeFilter nodeFilter,
                          boolean entityReferenceExpansion) {
        fCurrentNode = root;
        fRoot = root;
        fWhatToShow = whatToShow;
        fNodeFilter = nodeFilter;
        fEntityReferenceExpansion = entityReferenceExpansion;
    }
    
    public Node getRoot() {
	return fRoot;
    }

    /** Return the whatToShow value */
    public int                getWhatToShow() {
        return fWhatToShow;
    }

    public void setWhatShow(int whatToShow){
        fWhatToShow = whatToShow;
    }
    /** Return the NodeFilter */
    public NodeFilter         getFilter() {
        return fNodeFilter;
    }
    
    /** Return whether children entity references are included in the iterator. */
    public boolean            getExpandEntityReferences() {
        return fEntityReferenceExpansion;
    }
            
    /** Return the current Node. */
    public Node               getCurrentNode() {
        return fCurrentNode;
    }
    /** Return the current Node. */
    public void               setCurrentNode(Node node) {
        if (node == null) {
            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null);
              throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
        }

        fCurrentNode = node;
    }
    
    /** Return the parent Node from the current node, 
     *  after applying filter, whatToshow.
     *  If result is not null, set the current Node.
     */
    public Node               parentNode() {

        if (fCurrentNode == null) return null;
                
        Node node = getParentNode(fCurrentNode);
        if (node !=null) {
            fCurrentNode = node;
        }
        return node;
        
    }

    /** Return the first child Node from the current node, 
     *  after applying filter, whatToshow.
     *  If result is not null, set the current Node.
     */
    public Node               firstChild() {
        
        if (fCurrentNode == null) return null;
                
        Node node = getFirstChild(fCurrentNode);
        if (node !=null) {
            fCurrentNode = node;
        }
        return node;
    }
    /** Return the last child Node from the current node, 
     *  after applying filter, whatToshow.
     *  If result is not null, set the current Node.
     */
    public Node               lastChild() {

        if (fCurrentNode == null) return null;
                
        Node node = getLastChild(fCurrentNode);
        if (node !=null) {
            fCurrentNode = node;
        }
        return node;
    }
    
    /** Return the previous sibling Node from the current node, 
     *  after applying filter, whatToshow.
     *  If result is not null, set the current Node.
     */
    public Node               previousSibling() {

        if (fCurrentNode == null) return null;
                
        Node node = getPreviousSibling(fCurrentNode);
        if (node !=null) {
            fCurrentNode = node;
        }
        return node;
    }
    
    /** Return the next sibling Node from the current node, 
     *  after applying filter, whatToshow.
     *  If result is not null, set the current Node.
     */
    public Node               nextSibling(){
        if (fCurrentNode == null) return null;
                
        Node node = getNextSibling(fCurrentNode);
        if (node !=null) {
            fCurrentNode = node;
        }
        return node;
    }
    
    /** Return the previous Node from the current node, 
     *  after applying filter, whatToshow.
     *  If result is not null, set the current Node.
     */
    public Node               previousNode() {
        Node result;
        
        if (fCurrentNode == null) return null;
        
        // get sibling
        result = getPreviousSibling(fCurrentNode);
        if (result == null) {
            result = getParentNode(fCurrentNode);
            if (result != null) {
                fCurrentNode = result;
                return fCurrentNode;
            } 
            return null;
        }
        
        // get the lastChild of result.
        Node lastChild  = getLastChild(result);
        
        Node prev = lastChild ;
        while (lastChild != null) {
          prev = lastChild ;
          lastChild = getLastChild(prev) ;
        }

        lastChild = prev ;
        
        // if there is a lastChild which passes filters return it.
        if (lastChild != null) {
            fCurrentNode = lastChild;
            return fCurrentNode;
        } 
        
        // otherwise return the previous sibling.
        if (result != null) {
            fCurrentNode = result;
            return fCurrentNode;
        }
        
        // otherwise return null.
        return null;
    }
    
    /** Return the next Node from the current node, 
     *  after applying filter, whatToshow.
     *  If result is not null, set the current Node.
     */
    public Node               nextNode() {
        
        if (fCurrentNode == null) return null;
        
        Node result = getFirstChild(fCurrentNode);
        
        if (result != null) {
            fCurrentNode = result;
            return result;
        }
        
        result = getNextSibling(fCurrentNode);
        
        if (result != null) {
            fCurrentNode = result;
            return result;
        }
                
        // return parent's 1st sibling.
        Node parent = getParentNode(fCurrentNode);
        while (parent != null) {
            result = getNextSibling(parent);
            if (result != null) {
                fCurrentNode = result;
                return result;
            } else {
                parent = getParentNode(parent);
            }
        }
        
        // end , return null
        return null;
    }
    
    /** Internal function.
     *  Return the parent Node, from the input node
     *  after applying filter, whatToshow.
     *  The current node is not consulted or set.
     */
    Node getParentNode(Node node) {
        
        if (node == null || node == fRoot) return null;
        
        Node newNode = node.getParentNode();
        if (newNode == null)  return null; 
                        
        int accept = acceptNode(newNode);
        
        if (accept == NodeFilter.FILTER_ACCEPT)
            return newNode;
        else 
        //if (accept == NodeFilter.SKIP_NODE) // and REJECT too.
        {
            return getParentNode(newNode);
        }
        
        
    }
    
    /** Internal function.
     *  Return the nextSibling Node, from the input node
     *  after applying filter, whatToshow.
     *  The current node is not consulted or set.
     */
    Node getNextSibling(Node node) {
		return getNextSibling(node, fRoot);
	}

    /** Internal function.
     *  Return the nextSibling Node, from the input node
     *  after applying filter, whatToshow.
     *  NEVER TRAVERSES ABOVE THE SPECIFIED ROOT NODE. 
     *  The current node is not consulted or set.
     */
    Node getNextSibling(Node node, Node root) {
        
        if (node == null || node == root) return null;
        
        Node newNode = node.getNextSibling();
        if (newNode == null) {
                
            newNode = node.getParentNode();
                
            if (newNode == null || newNode == root)  return null; 
                
            int parentAccept = acceptNode(newNode);
                
            if (parentAccept==NodeFilter.FILTER_SKIP) {
                return getNextSibling(newNode, root);
            }
                
            return null;
        }
        
        int accept = acceptNode(newNode);
        
        if (accept == NodeFilter.FILTER_ACCEPT)
            return newNode;
        else 
        if (accept == NodeFilter.FILTER_SKIP) {
            Node fChild = getFirstChild(newNode);
            if (fChild == null) {
                return getNextSibling(newNode, root);
            }
            return fChild;
        }
        else 
        //if (accept == NodeFilter.REJECT_NODE) 
        {
            return getNextSibling(newNode, root);
        }
        
    } // getNextSibling(Node node) {
    
    /** Internal function.
     *  Return the previous sibling Node, from the input node
     *  after applying filter, whatToshow.
     *  The current node is not consulted or set.
     */
    Node getPreviousSibling(Node node) {
		return getPreviousSibling(node, fRoot);
	}

    /** Internal function.
     *  Return the previousSibling Node, from the input node
     *  after applying filter, whatToshow.
	 *  NEVER TRAVERSES ABOVE THE SPECIFIED ROOT NODE. 
     *  The current node is not consulted or set.
     */
    Node getPreviousSibling(Node node, Node root) {
        
        if (node == null || node == root) return null;
        
        Node newNode = node.getPreviousSibling();
        if (newNode == null) {
                
            newNode = node.getParentNode();
            if (newNode == null || newNode == root)  return null; 
                
            int parentAccept = acceptNode(newNode);
                
            if (parentAccept==NodeFilter.FILTER_SKIP) {
                return getPreviousSibling(newNode, root);
            }
            
            return null;
        }
        
        int accept = acceptNode(newNode);
        
        if (accept == NodeFilter.FILTER_ACCEPT)
            return newNode;
        else 
        if (accept == NodeFilter.FILTER_SKIP) {
            Node fChild =  getLastChild(newNode);
            if (fChild == null) {
                return getPreviousSibling(newNode, root);
            }
            return fChild;
        }
        else 
        //if (accept == NodeFilter.REJECT_NODE) 
        {
            return getPreviousSibling(newNode, root);
        }
        
    } // getPreviousSibling(Node node) {
    
    /** Internal function.
     *  Return the first child Node, from the input node
     *  after applying filter, whatToshow.
     *  The current node is not consulted or set.
     */
    Node getFirstChild(Node node) {
        if (node == null) return null;
        
        if ( !fEntityReferenceExpansion
             && node.getNodeType() == Node.ENTITY_REFERENCE_NODE)
            return null;
        Node newNode = node.getFirstChild();
        if (newNode == null)  return null;
        int accept = acceptNode(newNode);
        
        if (accept == NodeFilter.FILTER_ACCEPT)
            return newNode;
        else 
        if (accept == NodeFilter.FILTER_SKIP
            && newNode.hasChildNodes()) 
        {
            Node fChild = getFirstChild(newNode);
            
            if (fChild == null) {
                return getNextSibling(newNode, node);
            }
            return fChild;
        }
        else 
        //if (accept == NodeFilter.REJECT_NODE) 
        {
            return getNextSibling(newNode, node);
        }
        
        
    }
   
    /** Internal function.
     *  Return the last child Node, from the input node
     *  after applying filter, whatToshow.
     *  The current node is not consulted or set.
     */
    Node getLastChild(Node node) {
        
        if (node == null) return null;
        
        if ( !fEntityReferenceExpansion
             && node.getNodeType() == Node.ENTITY_REFERENCE_NODE)
            return null;
            
        Node newNode = node.getLastChild();
        if (newNode == null)  return null; 
        
        int accept = acceptNode(newNode);
        
        if (accept == NodeFilter.FILTER_ACCEPT)
            return newNode;
        else 
        if (accept == NodeFilter.FILTER_SKIP
            && newNode.hasChildNodes()) 
        {
            Node lChild = getLastChild(newNode);
            if (lChild == null) {
                return getPreviousSibling(newNode, node);
            }
            return lChild;
        }
        else 
        //if (accept == NodeFilter.REJECT_NODE) 
        {
            return getPreviousSibling(newNode, node);
        }
        
        
    }
    
    /** Internal function. 
     *  The node whatToShow and the filter are combined into one result. */
    short acceptNode(Node node) {
        /***
         7.1.2.4. Filters and whatToShow flags 

         Iterator and TreeWalker apply whatToShow flags before applying Filters. If a node is rejected by the
         active whatToShow flags, a Filter will not be called to evaluate that node. When a node is rejected by
         the active whatToShow flags, children of that node will still be considered, and Filters may be called to
         evaluate them.
         ***/
                
        if (fNodeFilter == null) {
            if ( ( fWhatToShow & (1 << node.getNodeType()-1)) != 0) {
                return NodeFilter.FILTER_ACCEPT;
            } else {
                return NodeFilter.FILTER_SKIP;
            }
        } else {
            if ((fWhatToShow & (1 << node.getNodeType()-1)) != 0 ) {
                return fNodeFilter.acceptNode(node);
            } else {
                // What to show has failed. See above excerpt from spec.
                // Equivalent to FILTER_SKIP.
                return NodeFilter.FILTER_SKIP;
            }
        }
    } 
}
