/* 
 * XPathResult 0.1
 *  by Weston Ruter, Shepherd Interactive <http://www.shepherd-interactive.com/>
 *
 */

if(typeof XPathSnapshot == 'undefined')
	var XPathSnapshot;

(function(){
var re = (function(){
	
	function orTogether(list){
		return '('+list.join('|')+')';
	}
	
	var tNameChar = orTogether([
		'\\w',
		'\\d',
		'\\.',
		'-',
		'_',
		':'
		//No CombiningChar | Extender
	]);
	var tNCNameStartChar = orTogether(['\\w', '_']);
	var tNCNameChar = tNameChar.replace(/\|:|:\|/,'');
	var tNCName = tNCNameStartChar + tNCNameChar + '*';
	
	var tLocalPart = tNCName;
	var tPrefix = tNCName;
	var tUnprefixedName = tLocalPart;
	var tPrefixedName = tPrefix + ':' + tLocalPart;
	var tQName = orTogether([tPrefixedName, tUnprefixedName]);
	
	
	var tLiteral = '('+[
		'"[^"]*"',
		"'[^']*'"
	].join('|')+')';
	
	var tDigits = '[0-9]+';
	var tNumber = orTogether([
		tDigits + "(\\." + tDigits + "?)?",
		"\\." + tDigits
	]);
	
	var tOperatorName = orTogether([
		'and', 'or', 'mod', 'div'
	]);
	var tMultiplyOperator = '\\*';
	var tOperator = orTogether([
		tOperatorName,
		tMultiplyOperator,
		'//', '/', '\\|', '\\+', '-', '=', '!=', '<', '<=', '>', '>='	
	]);
	
	var tFunctionName = tQName /*NOTE: NodeType is not removed*/;
	var tVariableReference = '$' + tQName;
	var tNameTest = orTogether([
		'\\*',
		tNCName + ':\\*',
		tQName
	]);
	
	var tNodeType = orTogether([
		'comment',
		'text',
		'processing-instruction',
		'node'
	]);
	
	var tExprWhitespace = orTogether([
		"\u0020",
		"\u0009",
		"\u000D",
		"\u000A"
	]);
	
	var tExprToken = orTogether([
		'\\(', '\\)', '\\[', '\\]', '\\.\\.', '\\.', '@', ',', '::',
		tNameTest,
		tNodeType,
		tOperator,
		tFunctionName,
		//tAxisName, //Axes are not supported (only abbreviated syntax)
		tLiteral,
		tNumber,
		tVariableReference
	]);


	return {
		NameChar:new RegExp('^'+tNameChar+'$'),
		NCNameChar:new RegExp('^'+tNCNameChar+'$'),
		NCName:new RegExp('^'+tNCName+'$'),
		LocalPart:new RegExp('^'+tLocalPart+'$'),
		Prefix:new RegExp('^'+tPrefix+'$'),
		UnprefixedName:new RegExp('^'+tUnprefixedName+'$'),
		PrefixedName:new RegExp('^'+tPrefixedName+'$'),
		QName:new RegExp('^'+tQName+'$'),
		Literal:new RegExp('^'+tLiteral+'$'),
		Digits:new RegExp('^'+tDigits+'$'),
		Number:new RegExp('^'+tNumber+'$'),
		OperatorName:new RegExp('^'+tOperatorName+'$'),
		MultiplyOperator:new RegExp('^'+tMultiplyOperator+'$'),
		Operator:new RegExp('^'+tOperator+'$'),
		FunctionName:new RegExp('^'+tFunctionName+'$'),
		VariableReference:new RegExp('^'+tVariableReference+'$'),	
		NameTest:new RegExp('^'+tNameTest+'$'),
		NodeType:new RegExp('^'+tNodeType+'$'),
		ExprWhitespace:new RegExp('^'+tExprWhitespace+'$'),
		
		//This is the only one that is not anchored and is global
		ExprToken:new RegExp(tExprToken, 'g')
	};


})();


if(XPathSnapshot)
	return;

XPathSnapshot = function(xpath, doc, contextNode, namespaceResolver, _NONATIVE){
    if(!doc)
        doc = window.document;
    if(!contextNode)
		contextNode = doc;
	
	try {
		throw Error();
		if(_NONATIVE)
			throw Error('Native not available, attempting POJ implementation ');
		this._XPathResult = doc.evaluate(xpath, contextNode, namespaceResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
		
		//Define getter and setter on this property
		if(this.__defineGetter__ && this.__defineSetter__){
			this.__defineGetter__('snapshotLength', function(){
				return this._XPathResult.snapshotLength
			});
			this.__defineSetter__('snapshotLength', function(len){
				throw Error('Unable to set snapshotLength');
			});
		}
		else {
			this.snapshotLength = this._XPathResult.snapshotLength;
		}
	}
	//DOM traversal emulation of XPath
	catch(e){
		function _getElementsByTagName(context, ns, name){
			if(document.documentElement.namespaceURI && context.getElementsByTagNameNS && ns)
				return context.getElementsByTagNameNS(ns, name);
			else
				return context.getElementsByTagName(name);
		}
		
		function removeVisitedFlag(nodeList){
			for(var j = 0; j < nodeList.length; j++){
				if(typeof nodeList[j].removeAttribute != 'undefined')
					nodeList[j].removeAttribute('_xpathsnapshotted');
				
				//try {
				//	delete nodeList[j]._xpathsnapshotted;
				//}
				////For MSIE
				//catch(e){
				//	nodeList[j]._xpathsnapshotted = false;
				//	if(nodeList[j].removeAttribute)
				//		nodeList[j].removeAttribute('_xpathsnapshotted');
				//}
			}
		}
		
		
		this._snapshotItems = [];
		var contextNodes = [contextNode];
		
		var tok = xpath.match(re.ExprToken);
		if(!tok)
			throw Error('Syntax error in xpath: ' + xpath);
		//if(tok == '/' || tok == '//')
		//	throw Error('XPathSnaphot: Expected / or // to begin path');
		
		var i = 0;
		
		//Determine where we are supposed to start
		var isImmediateChild = true;
		if(tok[i].substr(0,1) == '/'){
			isImmediateChild = (tok[i] == '/');
			if(i == 0)
				contextNodes = [doc];
			i++;
		}
		
		//Iterate over all tokens
		while(i < tok.length){
			var namespaceURI = null;
			var localName = null;
			
			if(re.UnprefixedName.test(tok[i])){
				localName = tok[i];
			}
			else if(re.PrefixedName.test(tok[i])){
				var _parts = tok[i].split(/:/);
				namespaceURI = namespaceResolver(_parts[0]);
				localName = _parts[1];
			}
			else if(tok[i] == '*'){
				localName = '*';
			}
			else
				throw Error('XPathSnapshot: Unexpected element specified: ' + tok[i] + ', after ' + tok.splice(0, i).join(''));
			i++;
			
			var newContextNodes = [];
			
			//Skip over predicates for now
			var predicate = null;
			if(tok[i] == '['){
				i++;
				
				while(i < tok.length && tok[i] != ']'){
					//Attribute tests with certain operators supported
					if(tok[i] == '@'){
						var attrName;
						if(re.QName.test(tok[++i]))
							attrName = tok[i];
						else
							throw Error('XPathSnapshot: Unsupported attribute identifier: ' + tok[i] + ', after ' + tok.splice(0, i).join(''));
						i++;
						if(re.Operator.test(tok[i])){
							var attrOperator = tok[i++];
							var rvalue = eval(tok[i]);
							
							predicate = function(el){
								switch(attrOperator){
									case '=':
										return el.getAttribute(attrName) == rvalue;
									case '!=':
										return el.getAttribute(attrName) != rvalue;
									case '<':
										return el.getAttribute(attrName) < rvalue;
									case '<=':
										return el.getAttribute(attrName) <= rvalue;
									case '>':
										return el.getAttribute(attrName) > rvalue;
									case '>=':
										return el.getAttribute(attrName) >= rvalue;
									default:
										throw Error('XPathSnapshot: Unsupported operator ' + attrOperator);
								}
							};
						}
						else {
							throw Error('XPathSnapshot: Unsupported operator: ' + attrOperator);
						}
					}
					//Function tests
					else if(re.QName.test(tok[i]) && tok[i+1] == '('){
						var funcName = tok[i];
						i += 2;
						if(tok[i] == '@'){
							var attrName;
							if(re.QName.test(tok[++i]))
								attrName = tok[i];
							else
								throw Error('XPathSnapshot: Unsupported attribute identifier: ' + tok[i]);
							i++;
							var args = [];
							while(i < tok.length && tok[i] != ')'){
								if(re.Literal.test(tok[i]) || re.Number.test(tok[i])){
									args.push(eval(tok[i]))
								}
								else if(tok[i] != ','){
									throw Error('XPathSnapshot: Unsupported argument to function "' + funcName + '": ' + tok[i]);
								}
								i++;
							}
							predicate = function(el){
								var attrValue = el.getAttribute(attrName);
								switch(funcName){
									case 'contains':
										if(args.length != 1)
											throw Error('XPathSnapshot: contains() function requires two arguments');
										return attrValue && attrValue.indexOf(args[0])
									default:
										throw Error('XPathSnapshot: Unsupported function: ' + funcName);
								}
							};
						}
						else {
							throw Error('XPathSnapshot: Expected attribute node');
						}
					}
					else {
						throw Error('XPathSnapshot: Unexpected in predicate: ' + tok[i] + ', after ' + tok.splice(0, i).join(''));
					}
					i++;
				}
				i++;
			}
			
			//Direct descendants: "/"
			if(isImmediateChild){
				//Iterate over all of the context nodes
				for(var j = 0; j < contextNodes.length; j++){
					//Iterate over all direct children of context node to see if they match what's requested
					var children = contextNodes[j].childNodes;
					for(var k = 0; k < children.length; k++){
						var child = children[k];
						if(child.nodeType == 1 && !child.getAttribute('_xpathsnapshotted') &&
						   (localName == '*' || child.localName.toLowerCase() == localName) &&
						   (!namespaceURI || !child.namespaceURI || child.namespaceURI == namespaceURI) &&
						   (!predicate || predicate(child)))
						{
							child.setAttribute('_xpathsnapshotted', 1); //node._xpathsnapshotted = true;
							newContextNodes.push(child);
						}
					}
				}
			}
			//Anywhere underneath "//"
			else {
				//Iterate over all of the context nodes
				for(var j = 0; j < contextNodes.length; j++){
					//Iterate over all descendants of context node to see if they match what's requested
					var children = _getElementsByTagName(contextNodes[j], namespaceURI, localName);
					for(var k = 0; k < children.length; k++){
						var child = children[k];
						if(!predicate || predicate(child) && !child.getAttribute('_xpathsnapshotted')){
							child.setAttribute('_xpathsnapshotted', 1); //node._xpathsnapshotted = true;
							newContextNodes.push(child);
						}
					}
				}
			}
			removeVisitedFlag(contextNodes);
			contextNodes = newContextNodes;
			
			if(!contextNodes.length)
				break;
			
			if(i < tok.length){
				if(tok[i] == '//')
					isDescendant = true;
				else if(tok[i] != '/') //look directly under document
					throw Error('XPathSnapshot: Expected path separator instead of: ' + tok[i]);
			}
			i++;
		}
		
		removeVisitedFlag(contextNodes);
		
		if(contextNodes[0]){
			if(contextNodes[0].sourceIndex) {
				contextNodes.sort(function (a,b) {
					return a.sourceIndex - b.sourceIndex;
				});
			}
			else if (contextNodes[0].compareDocumentPosition) {
				contextNodes.sort(function (a,b) {
					return 3 - (a.compareDocumentPosition(b) & 6);
				});
			}
		}

		this._snapshotItems = contextNodes;
		this.snapshotLength = contextNodes.length;
		
	}
}

XPathSnapshot.prototype._XPathResult = null;
XPathSnapshot.prototype._snapshotItems = [];
XPathSnapshot.prototype.snapshotLength = 0;
XPathSnapshot.prototype.snapshotItem = function(i/*:Number*/){
    if(this._XPathResult)
		return this._XPathResult.snapshotItem(i);
	else
		return this._snapshotItems[i];
}



XPathSnapshot.prototype.toArray = function(){
	if(this._XPathResult){
		var nodes = [];
		for(var i = 0; i < this._XPathResult.snapshotLength; i++)
			nodes.push(this._XPathResult.snapshotItem(i));
		return nodes;
	}
	else
		return this._snapshotItems;
	
}	
	
	
	
})();

