/**
	Gets tags in the page or under a Tag/Element by CSS Selector.

	See unit tests for support:
	/menu/test/simpleselector.html
	
	version 1.3
*/
Tag = new Object();

document.getBySelector = Tag_getBySelector;
function Tag_getBySelector(sSelector) {	// uses basic CSS selectors to get tags
	var selector = new Selector(sSelector);
	var tags = new Array();
	
	// hack for Mozilla; "body" is a tag but takes no events
	if(sSelector == 'body' && document.body.addEventListener) {	// more selectors than just "body" will get proper results without this fix
		tags.push(window);
		return tags;	// applying selectors to "window" would cause errors
	}
	
	tags.push(this);		// tags is initially just current tag

	var simple;

	while(simple = selector.next()) {
		var tagParents = tags;
		tags = new Array();

		forall(tagParents,
			function(tagParent) {
				if(simple.id) {
					tagParent.getTagById = Tag_getTagById;
					var tag = tagParent.getTagById(simple.id);
					
					if(tag) {
						tag.matchTag = Tag_matchTag;
						if(!simple.tag || tag.matchTag(simple.tag)) {
							tag.hasClassList = Tag_hasClassList;
							if(tag.hasClassList(simple.classList))
								// TODO: filter by classes, not, attrs
								tags.push(tag);
						}
					}
				} else {
					if(simple.tag) {
						tagParent.getTagsByClassList = Tag_getTagsByClassList;
						// TODO: filter by classes, not, attrs
						tags = tags.concat(tagParent.getTagsByClassList(simple.tag, simple.classList));
					}
				}
			}
		);
		
		// eliminate tags not matching attrs
		tags = Tag.attr(simple, tags);
		
		// eliminate matches from the "not" clause
		tags = Tag.PseudoClass.not(simple, tags);

		// choose nth-children instead of tags themselves
		tags = Tag.PseudoClass.nthChild(simple, tags);
	}
		
	return tags;
}

Tag.attr = function(simple, tags) {
	forall(tags,
		function(tag, iTag) {
			forall(simple.attrList,
				function(attr) {
					if(attr.value == null) {
						if(!tag[attr.name])
							tags.splice(iTag, 1);
					} else {
						if(tag[attr.name].toString() != attr.value)
							tags.splice(iTag, 1);
					}
				}
			);
		},
		Array.backwards
	);
	
	return tags;
}

Tag.PseudoClass = new Object();

Tag.PseudoClass.not = function(simple, tags) {
	var not = simple.pseudoClassList.getBy('name', 'not');
	if(not) {
		var sId = not.arg.id;
		var sClassList = not.arg.classList;
		for(var iTag = tags.length - 1; iTag >= 0; iTag--) {
			var tag = tags[iTag];
			
			var bHasId = false;
			var bId = false;
			if(sId) {
				bHasId = true;
						
				if(tag.id == sId)
					bId = true;
			}

			var bHasClass = sClassList.length > 0;
			var bClass = false;
			if(bHasClass) {
				tag.hasClassList = Tag_hasClassList;
				bClass = tag.hasClassList(sClassList);
			}
			
			if(
				(bId && bClass)
				|| (!bHasId && bClass)
				|| (bId && !bHasClass)
			) {
				tags.splice(iTag, 1);
			}
		}
	}
	
	return tags;
}

Tag.PseudoClass.nthChild = function(simple, tags) {
	var nthChild = simple.pseudoClassList.getBy('name', 'nth-child');
	if(nthChild) {
		var parentTags = tags;
		tags = new Array();
		forall(parentTags,
			function(tag) {
				var iChild = 0;
				forall(tag.childNodes,
					function(child) {
						if(child.tagName) {	// is tag, not text node
							iChild++;
							if(iChild == nthChild.arg) {
								tags.push(child);
							}
						}
					}
				);
			}
		);
	}
	
	return tags;
}


function Tag_getTagById(sId) {
	// body doesn't support .getElementById()
	if(this.tagName == 'BODY')
		return document.getElementById(sId);
	
	return this.getElementById(sId);
}

function Tag_matchTag(sTagName) {
	if(!this.tagName)
		return false;
	
	var tagName = this.tagName.toString().toLowerCase();
	return tagName == sTagName;
}

document.getTagsByClassName = Tag_getTagsByClassName;
function Tag_getTagsByClassName(sTag, sClass) {
	var tags = new Array();
	
	if(!this.tagName) {
		if(!this.body)
			// text node - return empty
			return tags;
		
		// document - return document call
		var tagsBase = document.getElementsByTagName(sTag);
	} else {
		// tag - search within tag
		var tagsBase = this.getElementsByTagName(sTag);
	}
	
	for(var i = 0; i < tagsBase.length; i++) {
		var tag = tagsBase[i];
		var sClassList = new String(tag.className);
		
		tag.hasClass = Tag_hasClass;
		if(tag.hasClass(sClass)) {
			tags.push(tag);
		}
	}
	
	return tags;
}

document.getTagsByClassList = Tag_getTagsByClassList;
function Tag_getTagsByClassList(sTag, sClassList) {
	var tags = new Array();

	if(!this.tagName) {
		if(!this.body)
			return tags;
		
		// document
		var tagsBase = document.getElementsByTagName(sTag);
	} else {
		var tagsBase = this.getElementsByTagName(sTag);
	}
	
	for(var i = 0; i < tagsBase.length; i++) {
		var tag = tagsBase[i];
		var sTagClassList = new String(tag.className);
		
		tag.hasClass = Tag_hasClass;
		for(var iTagClass = sClassList.length - 1; iTagClass >= 0 && tag.hasClass(sClassList[iTagClass]); iTagClass--)
			{}
		
		if(iTagClass < 0)
			tags.push(tag);
	}
	
	return tags;
}

function Tag_hasClass(sClass) {
	var sTagClassList = (new String(this.className)).split(' ');
	return sTagClassList.has(sClass);
}

function Tag_hasClassList(classList) {
	var sTagClassList = this.className.toString().split(' ');
	for(var i = classList.length - 1; i >= 0; i--) {
		if(!sTagClassList.has(classList[i]))
			break;
	}
	
	return i < 0;
}

function Tag_getChildTags(sTagName) {
	sTagName = sTagName.toUpperCase();
	var tagChildList = new Array();

	for(var iChild = 0; iChild < this.childNodes.length; iChild++) {
		var tagChild = this.childNodes[iChild];
		if(tagChild.tagName && tagChild.tagName == sTagName)
			tagChildList.push(tagChild);
	}
	
	return tagChildList;
}

function Tag_getText() {
	var text = null;
	
	forall(this.childNodes,
		function(node) {
			if(node.nodeName == '#text')
				text = node;
		},
		Array.backwards
	);
	
	return text;
}

function Tag_getTextString() {
	this.getText = Tag_getText;
	return this.getText().nodeValue;
}

function Tag_nextTag() {
	var nextTag = this.nextSibling;

	while(nextTag && !nextTag.tagName) {
		nextTag = nextTag.nextSibling;
	}

	return nextTag;
}

function Tag_prevTag() {
	var prevTag = this.prevSibling;

	while(prevTag && !prevTag.tagName) {
		prevTag = prevTag.prevSibling;
	}

	return prevTag;
}

function Tag_parentTag() {
	var parentTag = this.parentNode;
	
	while(parentTag && !parentTag.tagName) {
		parentTag = parentTag.parentNode;
	}
	
	return parentTag;
}
