/*******************************************************************************
 * @license
 * Copyright (c) 2012, 2013 VMware, Inc. and others.
 * All Rights Reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 *
 * Contributors:
 *     Andrew Eisenberg (VMware) - initial API and implementation
 ******************************************************************************/

/*global define esprima */
define([
], function() {

	return {

		/**
		 * Generic AST visitor.  Visits all children in source order, if they have a range property.
		 *
		 * @param node The AST node to visit
		 * @param {rhsVisit:Boolean,...} context any extra data required to pass between operations.  Set rhsVisit to true if the rhs of
		 * assignments and variable declarators should be visited before the lhs
		 * @param operation function(node, context, [isInitialOp]) an operation on the AST node and the data.  Return falsy if
		 * the visit should no longer continue. Return truthy to continue.
		 * @param [postoperation] (optional) function(node, context, [isInitialOp]) an operation that is exectuted after visiting the current node's children.
		 * will only be invoked if operation returns true for the current node
		 */
		visit: function(node, context, operation, postoperation) {
			var i, key, child, children;

			if (operation(node, context, true)) {
				// gather children to visit
				children = [];
				for (key in node) {
					if (key !== "range" && key !== "errors" && key !== "target" && key !== "extras" && key !== "comments") {
						child = node[key];
						if (child instanceof Array) {
							for (i = 0; i < child.length; i++) {
								if (child[i] && child[i].hasOwnProperty("type")) {
									children.push(child[i]);
								} else if (key === "properties") {
									// might be key-value pair of an object expression
									// in old versions of the parser, the 'properties' property did not have a 'type' or a 'range'
									// so we must explicitly visit the children here.
									// in new versions of the parser, this is fixed, and this branch will never be taken.
									if (child[i].hasOwnProperty("key") && child[i].hasOwnProperty("value")) {
										children.push(child[i].key);
										children.push(child[i].value);
									}
								}
							}
						} else {
							if (child && child.hasOwnProperty("type")) {
								children.push(child);
							}
						}
					}
				}

				if (children.length > 0) {
					// sort children by source location
					// children with no source location are visited first
					children.sort(function(left, right) {
						if (left.range && right.range) {
							return left.range[0] - right.range[0];
						} else if (left.range) {
							return 1;
						} else if (right.range) {
							return -1;
						} else {
							return 0;
						}
					});

					// visit children in order
					for (i = 0; i < children.length; i++) {
						this.visit(children[i], context, operation, postoperation);
					}
				}
				if (postoperation) {
					postoperation(node, context, false);
				}
			}
		}
	};
});