/*=:project
		parseSelector 2.0
		
	=:description
		Provides an extensible way of parsing CSS selectors against a DOM in 
		JavaScript.

  =:file
  	Copyright: 2006 Mark Wubben.
  	Author: Mark Wubben, <http://novemberborn.net/>
   		
	=:license
		* This software is licensed and provided under the CC-GNU LGPL
		* See <http://creativecommons.org/licenses/LGPL/2.1/>
	
	=:notes
		* The parsing of CSS selectors as streams has been based on Dean Edwards
			excellent work with cssQuery. See <http://dean.edwards.name/my/cssQuery/>
			for more info.
*/

var parseSelector = (function() {
	var SEPERATOR = /\s*,\s*/
	
	function parseSelector(selector, node) {
		node = node || document.documentElement;
		var selectors = selector.split(SEPERATOR);
		var result = [];
		
		for(var i = 0; i < selectors.length; i++) {
			var nodes = [node];
			var stream = toStream(selectors[i]);
			for(var j = 0;j < stream.length;) {
				var token = stream[j++];
				var filter = stream[j++];
				var args = '';
				if(stream[j] == '(') {
					while(stream[j++] != ')' && j < stream.length) args += stream[j];
					args = args.slice(0, -1);
				}
				nodes = select(nodes, token, filter, args);
			}
			result = result.concat(nodes);
		}
		
		return result;
	}

	var WHITESPACE = /\s*([\s>+~(),]|^|$)\s*/g;
	var IMPLIED_ALL = /([\s>+~,]|[^(]\+|^)([#.:@])/g;
	var STANDARD_SELECT = /^[^\s>+~]/;
	var STREAM = /[\s#.:>+~()@]|[^\s#.:>+~()@]+/g;
		
	function toStream(selector) {
		var stream = selector.replace(WHITESPACE, '$1')
												 .replace(IMPLIED_ALL, '$1*$2');
		if(STANDARD_SELECT.test(stream)) stream = ' ' + stream;
    return stream.match(STREAM) || [];
	}
	
	function select(nodes, token, filter, args) {
/*alert('"'+token+'"\n'+filter+'\n'+args+'\n'+nodes);*/
		return (selectors[token]) ? selectors[token](nodes, filter, args) : [];
	}
	
	util = {
		toArray: function(enumerable) {
			var a = [];
			for(var i = 0; i < enumerable.length; i++) a.push(enumerable[i]);
			return a;
		}
	};
	
	dom = {
		isTag: function(node, tag) {
			return (tag == '*') || (
				tag.toLowerCase() == node.nodeName.toLowerCase().replace(':html', '')
			);
		},
	
		previousSiblingElement: function(node) {
			do node = node.previousSibling; while(node && node.nodeType != 1);
			return node;
		},
	
		nextSiblingElement: function(node) {
			do node = node.nextSibling; while(node && node.nodeType != 1);
			return node;
		},
	
		hasClass: function(name, node) {
			return (node.className || '').match('(^|\\s)'+name+'(\\s|$)');
		},
	
		getByTag: function(tag, node) {
			/*	IE5.x does not support document.getElementsByTagName("*")
				therefore we're falling back to element.all */
			if(tag == '*' && node.all != null) return node.all;
			return node.getElementsByTagName(tag);
		}
	};
	
	selectors = {
		'#': function(nodes, filter) {
			for(var i = 0; i < nodes.length; i++) {
				if(nodes[i].getAttribute('id') == filter)	return [nodes[i]];
			}
			return [];
		},

		' ': function(nodes, filter) {
			var result = [];
			for(var i = 0; i < nodes.length; i++) {
				result = result.concat(util.toArray(dom.getByTag(filter, nodes[i])));
			}
			return result;
		},
		
		'>': function(nodes, filter) {
			var result = [];
			for(var i = 0, node; i < nodes.length; i++) {
				node = nodes[i];
				for(var j = 0, child; j < node.childNodes.length; j++) {
					child = node.childNodes[j];
					if(child.nodeType == 1 && dom.isTag(child, filter)) {
						result.push(child);
					}
				}
			}
			return result;
		},

		'.': function(nodes, filter) {
			var result = [];
			for(var i = 0, node; i < nodes.length; i++) {
				node = nodes[i];
				if(dom.hasClass([filter], node)) result.push(node);
			}
			return result;
		}, 
				
		':': function(nodes, filter, args) {
			return (pseudoClasses[filter]) ? pseudoClasses[filter](nodes, args) : [];
		}
		
	};
	
	parseSelector.selectors			= selectors;
	parseSelector.pseudoClasses = {};
	parseSelector.util 				  = util;
	parseSelector.dom 				  = dom;
		
	return parseSelector;
})();