/*******************************************************************************
 * Copyright (c) 2004, 2009 Tasktop Technologies 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:
 *     Tasktop Technologies - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.internal.tasks.ui.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

/**
 * @author Steffen Pingel
 */
public class TreeWalker {

	public enum Direction {
		UP, DOWN
	};

	public static abstract class TreeVisitor {

		public abstract boolean visit(Object object);

	}

	private Direction direction = Direction.DOWN;

	private final TreeViewer treeViewer;

	private final Tree tree;

	private boolean expandNodes;

	public TreeWalker(TreeViewer treeViewer) {
		this.treeViewer = treeViewer;
		this.tree = treeViewer.getTree();
	}

	public Direction getDirection() {
		return direction;
	}

	public boolean getExpandNodes() {
		return expandNodes;
	}

	private TreePath getTreePath(TreeItem item) {
		List<Object> path = new ArrayList<Object>();
		do {
			// the tree is probably not fully refreshed at this point
			if (item.getData() == null) {
				return null;
			}
			path.add(0, item.getData());
			item = item.getParentItem();
		} while (item != null);
		return new TreePath(path.toArray());
	}

	public void setDirection(Direction direction) {
		this.direction = direction;
	}

	public void setExpandNodes(boolean expandNodes) {
		this.expandNodes = expandNodes;
	}

	private TreePath visitChildren(TreeViewer viewer, TreePath itemPath, TreeItem item, TreeVisitor visitor) {
		boolean restoreCollapsedState = false;
		// expand item
		try {
			if (getExpandNodes()) {
				boolean expandedState = item.getExpanded();
				if (!expandedState) {
					restoreCollapsedState = true;
					viewer.setExpandedState(itemPath, true);
				}
			}

			TreeItem[] children = item.getItems();
			if (children.length > 0 && children[0].getData() != null) {
				TreePath childPath = visitItems(viewer, itemPath, children, null, visitor);
				if (childPath != null) {
					return childPath;
				}
			}

		} finally {
			if (restoreCollapsedState) {
				// restore item state
				viewer.setExpandedState(itemPath, false);
			}
		}

		return null;
	}

	private TreePath visitItems(TreeViewer viewer, TreePath parentPath, TreeItem[] items, TreeItem visitedItem,
			TreeVisitor visitor) {
		if (direction == Direction.UP) {
			Collections.reverse(Arrays.asList(items));
		}

		boolean found = (visitedItem == null);
		for (TreeItem item : items) {
			if (!found) {
				if (item == visitedItem) {
					found = true;
				}
			} else {
				TreePath itemPath = parentPath.createChildPath(item.getData());

				if (direction == Direction.DOWN) {
					if (visitor.visit(item.getData())) {
						return itemPath;
					}
				}

				TreePath childPath = visitChildren(viewer, itemPath, item, visitor);
				if (childPath != null) {
					return childPath;
				}

				if (direction == Direction.UP) {
					if (visitor.visit(item.getData())) {
						return itemPath;
					}
				}
			}
		}

		// visit parent siblings
		if (visitedItem != null) {
			TreeItem parent = visitedItem.getParentItem();
			if (parent != null) {
				if (direction == Direction.UP) {
					if (visitor.visit(parent.getData())) {
						return parentPath;
					}
				}

				return visitSiblings(viewer, parent, visitor);
			}
		}

		return null;
	}

	private TreePath visitSiblings(TreeViewer viewer, TreeItem item, TreeVisitor visitor) {
		TreeItem[] siblings;
		TreePath path;
		TreeItem parent = item.getParentItem();
		if (parent != null) {
			path = getTreePath(parent);
			if (path == null) {
				return null;
			}
			siblings = parent.getItems();
		} else {
			path = TreePath.EMPTY;
			siblings = viewer.getTree().getItems();
		}
		return visitItems(viewer, path, siblings, item, visitor);
	}

	public TreePath walk(TreeVisitor visitor, TreeItem startItem) {
		TreePath path = null;
		if (startItem != null) {
			if (direction == Direction.DOWN) {
				path = getTreePath(startItem);
				if (path != null) {
					path = visitChildren(treeViewer, path, startItem, visitor);
				}
			}
			if (path == null) {
				path = visitSiblings(treeViewer, startItem, visitor);
			}
		} else {
			path = visitItems(treeViewer, TreePath.EMPTY, tree.getItems(), null, visitor);
		}

		return path;
	}

}
