/*******************************************************************************
 * Copyright (c) 2001, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *     
 *******************************************************************************/
package org.eclipse.wst.xml.core.internal.document;



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

/**
 * NodeIteratorImpl class
 */
public class NodeIteratorImpl implements NodeIterator {
	private NodeFilter filter = null;
	private Node nextNode = null;

	private Node rootNode = null;
	private int whatToShow = NodeFilter.SHOW_ALL;

	/**
	 * NodeIteratorImpl constructor
	 * 
	 * @param rootNode
	 *            org.w3c.dom.Node
	 */
	NodeIteratorImpl(Node rootNode, int whatToShow, NodeFilter filter) {
		this.rootNode = rootNode;
		this.nextNode = rootNode;
		this.whatToShow = whatToShow;
		this.filter = filter;
	}

	/**
	 */
	private final boolean acceptNode(Node node) {
		if (this.whatToShow != NodeFilter.SHOW_ALL) {
			if (node == null)
				return false;
			short nodeType = node.getNodeType();
			switch (this.whatToShow) {
				case NodeFilter.SHOW_ELEMENT :
					if (nodeType != Node.ELEMENT_NODE)
						return false;
					break;
				case NodeFilter.SHOW_ATTRIBUTE :
					if (nodeType != Node.ATTRIBUTE_NODE)
						return false;
					break;
				case NodeFilter.SHOW_TEXT :
					if (nodeType != Node.TEXT_NODE)
						return false;
					break;
				case NodeFilter.SHOW_CDATA_SECTION :
					if (nodeType != Node.CDATA_SECTION_NODE)
						return false;
					break;
				case NodeFilter.SHOW_ENTITY_REFERENCE :
					if (nodeType != Node.ENTITY_REFERENCE_NODE)
						return false;
					break;
				case NodeFilter.SHOW_ENTITY :
					if (nodeType != Node.ENTITY_NODE)
						return false;
					break;
				case NodeFilter.SHOW_PROCESSING_INSTRUCTION :
					if (nodeType != Node.PROCESSING_INSTRUCTION_NODE)
						return false;
					break;
				case NodeFilter.SHOW_COMMENT :
					if (nodeType != Node.COMMENT_NODE)
						return false;
					break;
				case NodeFilter.SHOW_DOCUMENT :
					if (nodeType != Node.DOCUMENT_NODE)
						return false;
					break;
				case NodeFilter.SHOW_DOCUMENT_TYPE :
					if (nodeType != Node.DOCUMENT_TYPE_NODE)
						return false;
					break;
				case NodeFilter.SHOW_DOCUMENT_FRAGMENT :
					if (nodeType != Node.DOCUMENT_FRAGMENT_NODE)
						return false;
					break;
				case NodeFilter.SHOW_NOTATION :
					if (nodeType != Node.NOTATION_NODE)
						return false;
					break;
				default :
					return false;
			}
		}
		if (this.filter != null) {
			return (this.filter.acceptNode(node) == NodeFilter.FILTER_ACCEPT);
		}
		return true;
	}

	/**
	 * Detaches the <code>NodeIterator</code> from the set which it iterated
	 * over, releasing any computational resources and placing the iterator in
	 * the INVALID state. After <code>detach</code> has been invoked, calls
	 * to <code>nextNode</code> or <code>previousNode</code> will raise
	 * the exception INVALID_STATE_ERR.
	 */
	public void detach() {
		this.rootNode = null;
		this.nextNode = null;
		this.filter = null;
	}

	/**
	 * The value of this flag determines whether the children of entity
	 * reference nodes are visible to the iterator. If false, they and their
	 * descendants will be rejected. Note that this rejection takes precedence
	 * over <code>whatToShow</code> and the filter. Also note that this is
	 * currently the only situation where <code>NodeIterators</code> may
	 * reject a complete subtree rather than skipping individual nodes. <br>
	 * <br>
	 * To produce a view of the document that has entity references expanded
	 * and does not expose the entity reference node itself, use the
	 * <code>whatToShow</code> flags to hide the entity reference node and
	 * set <code>expandEntityReferences</code> to true when creating the
	 * iterator. To produce a view of the document that has entity reference
	 * nodes but no entity expansion, use the <code>whatToShow</code> flags
	 * to show the entity reference node and set
	 * <code>expandEntityReferences</code> to false.
	 */
	public boolean getExpandEntityReferences() {
		// not supported
		return false;
	}

	/**
	 * The <code>NodeFilter</code> used to screen nodes.
	 */
	public NodeFilter getFilter() {
		return this.filter;
	}

	/**
	 */
	private final Node getNextNode() {
		if (this.nextNode == null)
			return null;
		Node oldNext = this.nextNode;
		Node child = this.nextNode.getFirstChild();
		if (child != null) {
			this.nextNode = child;
			return oldNext;
		}
		for (Node node = this.nextNode; node != null && node != this.rootNode; node = node.getParentNode()) {
			Node next = node.getNextSibling();
			if (next != null) {
				this.nextNode = next;
				return oldNext;
			}
		}
		this.nextNode = null;
		return oldNext;
	}

	/**
	 */
	private final Node getPreviousNode() {
		if (this.nextNode == this.rootNode)
			return null;
		Node prev = null;
		if (this.nextNode == null) {
			prev = this.rootNode; // never null
		} else {
			prev = this.nextNode.getPreviousSibling();
			if (prev == null) {
				this.nextNode = this.nextNode.getParentNode();
				return this.nextNode;
			}
		}
		Node last = prev.getLastChild();
		while (last != null) {
			prev = last;
			last = prev.getLastChild();
		}
		this.nextNode = prev;
		return this.nextNode;
	}

	/**
	 * The root node of the <code>NodeIterator</code>, as specified when it
	 * was created.
	 */
	public Node getRoot() {
		return this.rootNode;
	}

	/**
	 * This attribute determines which node types are presented via the
	 * iterator. The available set of constants is defined in the
	 * <code>NodeFilter</code> interface. Nodes not accepted by
	 * <code>whatToShow</code> will be skipped, but their children may still
	 * be considered. Note that this skip takes precedence over the filter, if
	 * any.
	 */
	public int getWhatToShow() {
		return this.whatToShow;
	}

	/**
	 * Returns the next node in the set and advances the position of the
	 * iterator in the set. After a <code>NodeIterator</code> is created,
	 * the first call to <code>nextNode()</code> returns the first node in
	 * the set.
	 * 
	 * @return The next <code>Node</code> in the set being iterated over, or
	 *         <code>null</code> if there are no more members in that set.
	 * @exception DOMException
	 *                INVALID_STATE_ERR: Raised if this method is called after
	 *                the <code>detach</code> method was invoked.
	 */
	public Node nextNode() throws DOMException {
		for (Node node = getNextNode(); node != null; node = getNextNode()) {
			if (acceptNode(node))
				return node;
		}
		return null;
	}

	/**
	 * Returns the previous node in the set and moves the position of the
	 * <code>NodeIterator</code> backwards in the set.
	 * 
	 * @return The previous <code>Node</code> in the set being iterated
	 *         over, or <code>null</code> if there are no more members in
	 *         that set.
	 * @exception DOMException
	 *                INVALID_STATE_ERR: Raised if this method is called after
	 *                the <code>detach</code> method was invoked.
	 */
	public Node previousNode() throws DOMException {
		for (Node node = getPreviousNode(); node != null; node = getPreviousNode()) {
			if (acceptNode(node))
				return node;
		}
		return null;
	}
}
