/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.core.internal.watson;

import org.eclipse.core.internal.dtree.AbstractDataTreeNode;
import org.eclipse.core.internal.dtree.DataTreeNode;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;

/**
 * A class for performing operations on each element in an element tree.
 * For example, this can be used to print the contents of a tree.
 * <p>
 * When creating an ElementTree iterator, an element tree and root path must be
 * supplied.  When the <code>iterate()</code> method is called, a visitor object
 * must be provided.  The visitor is called once for each node of the tree.  For
 * each node, the visitor is passed the entire tree, the object in the tree at
 * that node, and a callback for requesting the full path of that node.
 * <p>
 * <b>Example:</b>
 <code><pre>
 // printing a crude representation of the poster child
 IElementContentVisitor visitor=
     new IElementContentVisitor() {
   public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
     System.out.println(requestor.requestPath() + " -> " + elementContents);
     return true;
   }
 });
 ElementTreeIterator iterator = new ElementTreeIterator(tree, Path.ROOT);
 iterator.iterate(visitor);
 </pre></code>
 */
public class ElementTreeIterator implements IPathRequestor {
	//for path requestor
	private String[] segments = new String[10];
	private int nextFreeSegment;

	/* the tree being visited */
	private ElementTree tree;

	/* the root of the subtree to visit */
	private IPath path;

	/* the immutable data tree being visited */
	private DataTreeNode treeRoot;

	/**
	 * Creates a new element tree iterator for visiting the given tree starting
	 * at the given path.
	 */
	public ElementTreeIterator(ElementTree tree, IPath path) {
		this.tree = tree;
		this.path = path;
		//treeRoot can be null if deleted concurrently
		//must copy the tree while owning the tree's monitor to prevent concurrent deletion while creating visitor's copy
		synchronized (tree) {
			treeRoot = (DataTreeNode) tree.getDataTree().safeCopyCompleteSubtree(path);
		}
	}

	/**
	 * Iterates through the given element tree and visit each element (node)
	 * passing in the element's ID and element object.
	 */
	private void doIteration(DataTreeNode node, IElementContentVisitor visitor) {
		//push the name of this node to the requestor stack
		if (nextFreeSegment >= segments.length) {
			grow();
		}
		segments[nextFreeSegment++] = node.getName();

		//do the visit
		if (visitor.visitElement(tree, this, node.getData())) {
			//recurse
			AbstractDataTreeNode[] children = node.getChildren();
			int len = children.length;
			for (int i = 0; i < len; i++) {
				doIteration((DataTreeNode) children[i], visitor);
			}
		}

		//pop the segment from the requestor stack
		nextFreeSegment--;
		if (nextFreeSegment < 0)
			nextFreeSegment = 0;
	}

	/**
	 * Method grow.
	 */
	private void grow() {
		//grow the segments array
		int oldLen = segments.length;
		String[] newPaths = new String[oldLen * 2];
		System.arraycopy(segments, 0, newPaths, 0, oldLen);
		segments = newPaths;
	}

	/**
	 * Iterates through this iterator's tree and visits each element in the
	 * subtree rooted at the given path.  The visitor is passed each element's
	 * data and a request callback for obtaining the path.
	 */
	public void iterate(IElementContentVisitor visitor) {
		if (path.isRoot()) {
			//special visit for root element to use special treeData
			if (visitor.visitElement(tree, this, tree.getTreeData())) {
				if (treeRoot == null)
					return;
				AbstractDataTreeNode[] children = treeRoot.getChildren();
				int len = children.length;
				for (int i = 0; i < len; i++) {
					AbstractDataTreeNode node = children[i];
					if (node instanceof DataTreeNode) {
						doIteration((DataTreeNode) node, visitor);
					}
				}
			}
		} else {
			if (treeRoot == null)
				return;
			push(path, path.segmentCount() - 1);
			doIteration(treeRoot, visitor);
		}
	}

	/**
	 * Push the first "toPush" segments of this path.
	 */
	private void push(IPath pathToPush, int toPush) {
		if (toPush <= 0)
			return;
		for (int i = 0; i < toPush; i++) {
			if (nextFreeSegment >= segments.length) {
				grow();
			}
			segments[nextFreeSegment++] = pathToPush.segment(i);
		}
	}

	@Override
	public String requestName() {
		if (nextFreeSegment == 0)
			return ""; //$NON-NLS-1$
		return segments[nextFreeSegment - 1];
	}

	@Override
	public IPath requestPath() {
		if (nextFreeSegment == 0)
			return Path.ROOT;
		int length = nextFreeSegment;
		for (int i = 0; i < nextFreeSegment; i++) {
			length += segments[i].length();
		}
		StringBuilder pathBuf = new StringBuilder(length);
		for (int i = 0; i < nextFreeSegment; i++) {
			pathBuf.append('/');
			pathBuf.append(segments[i]);
		}
		return new Path(null, pathBuf.toString());
	}
}
