"use strict";

var AXIS = {

	ancestor: function (start, filter, stop) {
		var output = [];
		while ((start = start.parentNode)) {
			if (!filter || filter(start)) {
				output.push(start);
			}
			if (stop && stop(start)) {
				return output;
			}
		}
		return output;
	},

	ancestorOrSelf: function (start, filter, stop) {
		var output = [];
		if (!filter || filter(start)) {
			output.push(start);
		}
		if (stop && stop(start)) {
			return output;
		}
		return output.concat(this.ancestor(start, filter, stop));
	},

	attribute: function (parent, filter, stop) {
		var x, k, output = [];
		if (parent.nodeType === 1 && parent.hasAttributes()) {
			for (x = 0, k = parent.attributes.length; x < k; x++) {
				if (!filter || filter(parent.attributes[x])) {
					output.push(parent.attributes[x]);
				}
				if (stop && stop(parent.attributes[x])) {
					return output;
				}
			}
		}
		return output;
	},

	child: function (parent, filter, stop) {
		var x, k, n, output = [];
		if (parent.hasChildNodes()) {
			for (x = 0, k = parent.childNodes.length; x < k; x++) {
				n = parent.childNodes[x];
				if (!filter || filter(n)) {
					output.push(n);
				}
				if (stop && stop(n)) {
					return output;
				}
			}
		}
		return output;
	},

	descendant: function (parent, filter, stop) {

		// Modern browsers support querySelectorAll('*')
		// While that does return descendants in depth-first, pre-order form,
		// it does not return text nodes.  We must return ALL node types, not
		// just elements.

		// depth-first, pre-order search
		// Non-recursive implementation because older browsers have small
		// stacks.
		var open = this.child(parent),
		closed = [],
		cursor;

		while (open.length) {
			cursor = open.shift();
			open.unshift.apply(open, this.child(cursor));
			if (!filter || filter(cursor)) {
				closed.push(cursor);
			}
			if (stop && stop(cursor)) {
				return closed;
			}
		}

		return closed;
	},

	descendantOrSelf: function (parent, filter, stop) {

		var output = [];
		if (!filter || filter(parent)) {
			output.push(parent);
		}
		if (stop && stop(parent)) {
			return output;
		}

		return output.concat(this.descendant(parent, filter, stop));
	},

	following: function (start, filter, stop) {
		var output = [],
		cursor = start;

		/*@cc_on
			var ie6 = (/MSIE\s6\b/).test(navigator.userAgent);
		@*/

		// Note: IE6 does some weird stuff when the <base> tag is present.
		// Specifically, the tree ends up looking like this:
		// <html>
		//  <head>
		//   <base>
		//    <body>...</body>   <--  Same body element!
		//   </base>
		//  </head>
		//  <body>...</body>     <--  Same body element!
		// </html>

		// The <body> tag is the same tag, just in two places in the tree at
		// the same time.  It is both a descendant and sibling of the <head>.
		// This causes an infinite loop: we crawl out of <body> into <head>,
		// then go back into <body> when we find the <head>.nextSibling.

		// We have to work around this.  At the moment, it seems most logical
		// to detect this situation, and return the results that we would have
		// returned if IE6 were rational: i.e., crawl out of <body> and enter
		// <html>, like most people would expect.

		do {
			// Locate a good starting position by moving up the tree until we
			// encounter a nextSibling, or we run out of room.
			while (cursor.nextSibling === null && cursor.parentNode !== null) {

				/*@cc_on

					if (ie6 &&
						cursor.nodeName.toLowerCase() === 'body' &&
						cursor.parentNode.nodeName.toLowerCase() === 'base') {
						cursor = cursor.parentNode.parentNode;
					}

				@*/

				cursor = cursor.parentNode;
			}

			cursor = cursor.nextSibling;

			if (!cursor) {
				return output;
			}

			output = output.concat(
				this.descendantOrSelf(cursor, filter, stop));

			if (output.length && stop && stop(output[output.length - 1])) {
				return output;
			}

		} while (true);
	},

	followingSibling: function (start, filter, stop) {
		var output = [];
		while ((start = start.nextSibling)) {
			if (!filter || filter(start)) {
				output.push(start);
			}
			if (stop && stop(start)) {
				return output;
			}
		}
		return output;
	},

	parent: function (start, filter) { // no sense in a stop fn here.
		return (start.parentNode && (!filter || filter(start.parentNode))) ?
		[start.parentNode] : [];
	},

	preceding: function (start, filter, stop) {
		var output = [],
		cursor = start,
		children, child;

		do {
			while (cursor.previousSibling === null &&
				cursor.parentNode !== null) {
				cursor = cursor.parentNode;
			}

			cursor = cursor.previousSibling;

			if (!cursor) {
				return output;
			}

			children = this.descendantOrSelf(cursor, filter).reverse();

			if (stop) {
				for (child = 0; child < children.length; child++) {
					output.push(children[child]);
					if (stop(children[child])) {
						return output;
					}
				}
			} else {
				output = output.concat(children);
			}

		} while (true);
	},

	precedingSibling: function (start, filter, stop) {
		var output = [];
		while ((start = start.previousSibling)) {
			if (!filter || filter(start)) {
				output.push(start);
			}
			if (stop && stop(start)) {
				return output;
			}
		}
		return output;
	},

	self: function (start, filter) {
		return (!filter || filter(start)) ? [start] : [];
	}

};
