/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

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.NodeIterator;


/** DefaultNodeIterator implements a NodeIterator, which iterates a 
 *  DOM tree in the expected depth first way. 
 *
 *  <p>The whatToShow and filter functionality is implemented as expected.
 *  
 *  <p>This class also has method removeNode to enable iterator "fix-up" 
 *  on DOM remove. It is expected that the DOM implementation call removeNode
 *  right before the actual DOM transformation. If not called by the DOM,
 *  the client could call it before doing the removal.
 *  
 * @xerces.internal
 *
 * @version $Id: NodeIteratorImpl.java 447266 2006-09-18 05:57:49Z mrglavas $
 */
public class NodeIteratorImpl implements NodeIterator {
    
    //
    // Data
    //
    
    /** The DocumentImpl which created this iterator, so it can be detached. */
    private DocumentImpl fDocument;
    /** The root. */
    private Node fRoot;
    /** The whatToShow mask. */
    private int fWhatToShow = NodeFilter.SHOW_ALL;
    /** The NodeFilter reference. */
    private NodeFilter fNodeFilter;
    /** If detach is called, the fDetach flag is true, otherwise flase. */
    private boolean fDetach = false;
    
    // 
    // Iterator state - current node and direction.
    //
    // Note: The current node and direction are sufficient to implement
    // the desired behaviour of the current pointer being _between_
    // two nodes. The fCurrentNode is actually the last node returned, 
    // and the
    // direction is whether the pointer is in front or behind this node.
    // (usually akin to whether the node was returned via nextNode()) 
    // (eg fForward = true) or previousNode() (eg fForward = false).
    // Note also, if removing a Node, the fCurrentNode
    // can be placed on a Node which would not pass filters. 
    
    /** The last Node returned. */
    private Node fCurrentNode;
    
    /** The direction of the iterator on the fCurrentNode.
     *  <pre>
     *  nextNode()  ==      fForward = true;
     *  previousNode() ==   fForward = false;
     *  </pre>
     */
    private boolean fForward = true;
    
    /** When TRUE, the children of entites references are returned in the iterator. */
    private boolean fEntityReferenceExpansion;
    
    // 
    // Constructor
    //
    
    /** Public constructor */
    public NodeIteratorImpl( DocumentImpl document,
                             Node root, 
                             int whatToShow, 
                             NodeFilter nodeFilter,
                             boolean entityReferenceExpansion) {
        fDocument = document;
        fRoot = root;
        fCurrentNode = null;
        fWhatToShow = whatToShow;
        fNodeFilter = nodeFilter;
        fEntityReferenceExpansion = entityReferenceExpansion;
    }
    
    public Node getRoot() {
	return fRoot;
    }

    // Implementation Note: Note that the iterator looks at whatToShow
    // and filter values at each call, and therefore one _could_ add
    // setters for these values and alter them while iterating!
    
    /** Return the whatToShow value */
    public int                getWhatToShow() {
        return fWhatToShow;
    }

    /** Return the filter */
    public NodeFilter         getFilter() {
        return fNodeFilter;
    }
    
    /** Return whether children entity references are included in the iterator. */
    public boolean            getExpandEntityReferences() {
        return fEntityReferenceExpansion;
    }
            
    /** Return the next Node in the Iterator. The node is the next node in 
     *  depth-first order which also passes the filter, and whatToShow. 
     *  If there is no next node which passes these criteria, then return null.
     */
    public Node               nextNode() {
        
    	if( fDetach) {
    		throw new DOMException(
    		DOMException.INVALID_STATE_ERR, 
                DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
        }
        
        // if root is null there is no next node.
        if (fRoot == null) return null;
        
        Node nextNode = fCurrentNode;
        boolean accepted = false; // the next node has not been accepted.
     
        accepted_loop:
        while (!accepted) {
            
            // if last direction is not forward, repeat node.
            if (!fForward && nextNode!=null) {
                //System.out.println("nextNode():!fForward:"+fCurrentNode.getNodeName());
                nextNode = fCurrentNode;
            } else { 
            // else get the next node via depth-first
                if (!fEntityReferenceExpansion
                    && nextNode != null
                    && nextNode.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
                    nextNode = nextNode(nextNode, false);
                } else {
                    nextNode = nextNode(nextNode, true);
                }
            }
   
            fForward = true; //REVIST: should direction be set forward before null check?
            
            // nothing in the list. return null.
            if (nextNode == null) return null; 
            
            // does node pass the filters and whatToShow?
            accepted = acceptNode(nextNode);
            if (accepted) {
                // if so, then the node is the current node.
                fCurrentNode = nextNode;
                return fCurrentNode;
            } else 
                continue accepted_loop;
            
        } // while (!accepted) {
        
        // no nodes, or no accepted nodes.
        return null;
            
    }
    
    /** Return the previous Node in the Iterator. The node is the next node in 
     *  _backwards_ depth-first order which also passes the filter, and whatToShow. 
     */
    public Node               previousNode() {
        
    	if( fDetach) {
    		throw new DOMException(
    		DOMException.INVALID_STATE_ERR, 
                DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
        }
 
        // if the root is null, or the current node is null, return null.
        if (fRoot == null || fCurrentNode == null) return null;
       
        Node previousNode = fCurrentNode;
        boolean accepted = false;
        
        accepted_loop:
        while (!accepted) {
            
            if (fForward && previousNode != null) {
                //repeat last node.
                previousNode = fCurrentNode;
            } else { 
                // get previous node in backwards depth first order.
                previousNode = previousNode(previousNode);
            }
            
            // we are going backwards
            fForward = false;
            
            // if the new previous node is null, we're at head or past the root,
            // so return null. 
            if (previousNode == null) return null;
            
            // check if node passes filters and whatToShow.
            accepted = acceptNode(previousNode);
            if (accepted) {
                // if accepted, update the current node, and return it.
                fCurrentNode = previousNode;
                return fCurrentNode;
            } else 
                continue accepted_loop;
        }
        // there are no nodes?
        return null;
    }
                
    /** The node is accepted if it passes the whatToShow and the filter. */
    boolean acceptNode(Node node) {
                
        if (fNodeFilter == null) {            
            return ( fWhatToShow & (1 << node.getNodeType()-1)) != 0 ;
        } else {
            return ((fWhatToShow & (1 << node.getNodeType()-1)) != 0 ) 
                && fNodeFilter.acceptNode(node) == NodeFilter.FILTER_ACCEPT;
        }
    } 
    
    /** Return node, if matches or any parent if matches. */
    Node matchNodeOrParent(Node node) {
        // Additions and removals in the underlying data structure may occur
        // before any iterations, and in this case the reference_node is null.
        if (fCurrentNode == null) return null;
        
        // check if the removed node is an _ancestor_ of the 
        // reference node
        for (Node n = fCurrentNode; n != fRoot; n = n.getParentNode()) {
            if (node == n) return n;
        }
        return null;
    }
    
    /** The method nextNode(Node, boolean) returns the next node 
     *  from the actual DOM tree.
     * 
     *  The boolean visitChildren determines whether to visit the children.
     *  The result is the nextNode.
     */
    Node nextNode(Node node, boolean visitChildren) {
            
        if (node == null) return fRoot;

        Node result;
        // only check children if we visit children.
        if (visitChildren) {
            //if hasChildren, return 1st child.
            if (node.hasChildNodes()) {
                result = node.getFirstChild();
                return result;
            }
        }
            
        if (node == fRoot) { //if Root has no kids
            return null;
        }

        // if hasSibling, return sibling
        result = node.getNextSibling();
        if (result != null) return result;
        
                
        // return parent's 1st sibling.
        Node parent = node.getParentNode();
        while (parent != null && parent != fRoot) {
            result = parent.getNextSibling();
            if (result != null) {
                return result;
            } else {
                parent = parent.getParentNode();
            }
                            
        } // while (parent != null && parent != fRoot) {
        
        // end of list, return null
        return null;            
    }
    
    /** The method previousNode(Node) returns the previous node 
     *  from the actual DOM tree.
     */
    Node previousNode(Node node) {
        
        Node result;
        
        // if we're at the root, return null.
        if (node == fRoot) return null;
        
        // get sibling
        result = node.getPreviousSibling();
        if (result == null) {
            //if 1st sibling, return parent
            result = node.getParentNode();
            return result;
        }
        
        // if sibling has children, keep getting last child of child.
        if (result.hasChildNodes()
            && !(!fEntityReferenceExpansion
                && result != null
                && result.getNodeType() == Node.ENTITY_REFERENCE_NODE)) 
       
        {
            while (result.hasChildNodes()) {
                result = result.getLastChild();
            }
        }          
            
        return result;
    }
    
    /** Fix-up the iterator on a remove. Called by DOM or otherwise,
     *  before an actual DOM remove.   
     */
    public void removeNode(Node node) {
        
        // Implementation note: Fix-up means setting the current node properly
        // after a remove.
        
        if (node == null) return;
        
        Node deleted = matchNodeOrParent(node);
        
        if (deleted == null) return;
        
        if (fForward) {
            fCurrentNode = previousNode(deleted);
        } else
        // if (!fForward) 
        {
            Node next = nextNode(deleted, false);
            if (next!=null) {
                // normal case: there _are_ nodes following this in the iterator.
                fCurrentNode = next;
            } else {
                // the last node in the iterator is to be removed, 
                // so we set the current node to be the previous one.
                fCurrentNode = previousNode(deleted);
                fForward = true;
            }
                
        }
        
    }
    
    public void               detach() {
        fDetach = true;
        fDocument.removeNodeIterator(this);
    }
    
}
