/**
* The <code>dbates.Spry.Eval</code> is a set of utilities for evaluating arithmetic expressions.
* In its preferred emobodiment, the class attribute "dbates.Spry.Eval" is added to every tag 
* whose text body should be evaluated. For instance,
* <pre>
* <div>L:<input type="text" id="l" value="3">, W:<input type="text" id="w" value="4">
* 	<span class="dbates:Spry:Eval">{l}*{w}</span></div>
* </pre>
* {x} => substitutes the value of the node whose id is x.
* <p>
* To evaluate all apropriate elements on a page, call {@link dbates.Spry.Eval.evaluate()}.
* <p>
* Optionally, you can pass a set of options to the constructor dbates.Spry.Eval. You must call the
* constructor in the page <head>.
* <p>
* The following are valid options:
* <ul>
* <li>initialValue: The initial value to display on page load (default: 0).
* <li>errorValue: The value to display if evaluation failed (default: 0).
* <li>evaluateFieldOn: An array of event handlers to listen for.
* <li>evaluateOnLoad: <code>true</code> if expressions should be evaluated on page load; <code>false</code> otherwise.
* </ul>
* <p>
* Upon evaluation, <code>dbates.Spry.Eval</code> will assign an id to the ambient container element of the 
* expression if an id is not already present.The assigned id will have the form 
* <code>dbates:Spry:Eval:resultX</code> where X >= 0 in the order in which expressions are encountered during 
* pre-order traversal of the document tree.
* <p>
* For example:
* <span class="dbates:Spry:Eval">2*3</span> => <span id="dbates:Spry:Eval:result0" class="dbates:Spry:Eval">6</span>
* <span class="dbates:Spry:Eval">7*{dbates:Spry:Eval:result0}</span>  => <span id="dbates:Spry:Eval:result1" class="dbates:Spry:Eval">42</span>
*/
var dbates;
if (!dbates) dbates = {};
if (!dbates.Spry) dbates.Spry = {};
if (!dbates.Spry.Util) dbates.Spry.Util = {};
if (!dbates.Spry.Util.EventHandlers) dbates.Spry.Util.EventHandlers = new Array();

dbates.Spry.Eval = function(opts)
{
	dbates.Spry.Util.EventHandler.addEventHandler(window, "load", function () { dbates.Spry.Eval.init(opts); });
};
dbates.Spry.Eval.CLASS_NAME = "dbates:Spry:Eval";
dbates.Spry.Eval.NodeArray = new Array();
dbates.Spry.Eval.errorValue = 0;
dbates.Spry.Eval.init = function(opts)
{
	if (opts && opts.errorValue)
		dbates.Spry.Eval.errorValue = opts.errorValue;
		
	nodes = dbates.Spry.Util.getNodesByFunc(document,function (node) { return (dbates.Spry.Util.hasClassNamed(node, dbates.Spry.Eval.CLASS_NAME)); });
	var numNodes = nodes.length;
	var count = 0;
	for (var i = 0; i < numNodes; ++i) {
		var expr = dbates.Spry.Eval.getExpressionForNode(nodes[i]);
		if (!expr) {
			var obj = dbates.Spry.Eval.createObjectForNode(nodes[i])
			if (!obj)
				continue;
		
			if (opts && opts.evaluateFieldOn) {
				var evalOpts = opts.evaluateFieldOn;
				var numEvaluationOpts = evalOpts.length;
				for (var j = 0; j < numEvaluationOpts; ++j)
					dbates.Spry.Eval.addEventListener(nodes[i], evalOpts[j]);
			}
			dbates.Spry.Eval.NodeArray.push(obj);
			if (opts && opts.evaluateOnLoad)
				dbates.Spry.Eval.setNodeValue(nodes[i], dbates.Spry.Eval.evaluateStr(dbates.Spry.Eval.getNodeValue(nodes[i])));
			else if (opts && opts.initialValue)
				dbates.Spry.Eval.setNodeValue(nodes[i], opts.initialValue);
			else
				dbates.Spry.Eval.setNodeValue(nodes[i], 0);
			
			try{
				if (!nodes[i].hasAttribute("id"))
					nodes[i].setAttribute("id", dbates.Spry.Eval.CLASS_NAME+":result_"+(count++));
			} catch (e) {}
		}
	}
};
dbates.Spry.Eval.evaluate = function(element)
{
	element = (element)? $(element) : document;
	nodes = dbates.Spry.Util.getNodesByFunc(element,function (node) { return (dbates.Spry.Util.hasClassNamed(node, dbates.Spry.Eval.CLASS_NAME)); });
	var numNodes = nodes.length;
	for (var i = 0; i < numNodes; ++i) {
		var expr = dbates.Spry.Eval.getExpressionForNode(nodes[i]);
		if (expr) {
			dbates.Spry.Eval.setNodeValue(nodes[i], dbates.Spry.Eval.evaluateStr(expr));
		} else {
			var obj = dbates.Spry.Eval.createObjectForNode(nodes[i])
			if (!obj)
				continue;
			dbates.Spry.Eval.NodeArray.push(obj);
			dbates.Spry.Eval.setNodeValue(nodes[i], dbates.Spry.Eval.evaluateStr(dbates.Spry.Eval.getNodeValue(nodes[i])));
		}
		var elementHandler = dbates.Spry.Util.EventHandler.getEventHandler(nodes[i], "change");
		if (elementHandler)
			elementHandler.handleEvent();
	}
};
dbates.Spry.Eval.createObjectForNode = function(node)
{
	var obj = new Object();
	if (!obj)
		return null;
	obj[0] = node;
	obj[1] = dbates.Spry.Eval.getNodeValue(node);
	return obj;
};
dbates.Spry.Eval.getExpressionForNode = function(node)
{
	for (var i = 0; i < dbates.Spry.Eval.NodeArray.length; ++i) {
		var obj = dbates.Spry.Eval.NodeArray[i];
		if (obj && obj[0] == node)
			return obj[1];
	}
	return null;
};
dbates.Spry.Eval.setExpressionForNode = function(node, expr)
{
	for (var i = 0; i < dbates.Spry.Eval.NodeArray.length; ++i) {
		var obj = dbates.Spry.Eval.NodeArray[i];
		if (obj && obj[0] == node){
			obj[1] = expr;
			break;
		}
	}
};
dbates.Spry.Eval.evaluateStr = function(regionStr)
{
	try {
		var result = eval(dbates.Spry.Eval.sub_params(regionStr));
		if (isNaN(result))
			result = dbates.Spry.Eval.errorValue;
		return result;
	} catch (e) { return dbates.Spry.Eval.errorValue; }
};
dbates.Spry.Eval.sub_params = function(regionStr)
{
	if (!regionStr)
		return "";

	var resultStr = "";
	var re = new RegExp("\\{([^\\}:]+::)?[^\\}]+\\}", "g");
	var startSearchIndex = 0;
	while (startSearchIndex < regionStr.length) {
		var reArray = re.exec(regionStr);
		if (!reArray || !reArray[0]) {
			resultStr += regionStr.substr(startSearchIndex, regionStr.length - startSearchIndex);
			return resultStr;
		}
		if (reArray.index != startSearchIndex)
			resultStr += regionStr.substr(startSearchIndex, reArray.index - startSearchIndex);
		var dsName = "";
		if (reArray[0].search(/^\{[^}:]+::/) != -1)
			dsName = reArray[0].replace(/^\{|::.*/g, "");
		var fieldName = reArray[0].replace(/^\{|.*::|\}/g, "");
		
		var ref = null;
		if (dsName != "") {
			var formObj = eval("document."+dsName);
			if (eval("document."+dsName))
				ref = eval("document."+dsName+"."+fieldName);
		} else 
			ref = $(fieldName);
			
		if (ref && ref.nodeName == "INPUT") {
			var _value = parseFloat(ref.value.toString().replace(/\$|\,/g,''));
			resultStr += (isNaN(_value))? 0 : _value;
		} else 	if (ref) {
			var _value = parseFloat(ref.innerHTML.toString().replace(/\$|\,/g,''));
			resultStr += (isNaN(_value))? 0 : _value;
		} else 	if (fieldName == "pi") {
			resultStr += "3.14159265358979323844";
		} else if (fieldName == "e") {
			resultStr += "2.71828182845904523536";
		} else {
			resultStr += "0";
		}
		
		if (startSearchIndex == re.lastIndex) {
			var leftOverIndex = reArray.index + reArray[0].length;
			if (leftOverIndex < regionStr.length)
				resultStr += regionStr.substr(leftOverIndex);

			break;
		}
		startSearchIndex = re.lastIndex;
	}
	return resultStr;
};
dbates.Spry.Eval.getNodeValue = function (node)
{
	if (!node)
		return null;
	
	var result;
	if (node.nodeName == "INPUT" || node.nodeName == "TEXTAREA")
		result = node.value;
	else if (node.nodeName == "SELECT")
		result = node.options[node.selectedIndex].value;
	else if (dbates.Spry.Util.hasClassNamed(node, dbates.Spry.Eval.CLASS_NAME))
		result = node.innerHTML;
	return result;
};
dbates.Spry.Eval.setNodeValue = function (node, value)
{
	if (!node)
		return;
	
	if (node.nodeName == "INPUT" || node.nodeName == "TEXTAREA")
		node.value = value;
	else if (node.nodeName == "SELECT")
		return;
	else if (dbates.Spry.Util.hasClassNamed(node, dbates.Spry.Eval.CLASS_NAME))
		node.innerHTML = value;
};
dbates.Spry.Eval.addEventListener = function(element, eventType)
{
	if (!element)
		return "";
	var regionStr = dbates.Spry.Eval.getNodeValue(element);
	if (!regionStr)
		return "";
	var resultStr = "";
	var re = new RegExp("\\{([^\\}:]+::)?[^\\}]+\\}", "g");
	var startSearchIndex = 0;
	while (startSearchIndex < regionStr.length) {
		var reArray = re.exec(regionStr);
		if (!reArray || !reArray[0]) {
			resultStr += regionStr.substr(startSearchIndex, regionStr.length - startSearchIndex);
			return resultStr;
		}
		if (reArray.index != startSearchIndex)
			resultStr += regionStr.substr(startSearchIndex, reArray.index - startSearchIndex);
		var dsName = "";
		if (reArray[0].search(/^\{[^}:]+::/) != -1)
			dsName = reArray[0].replace(/^\{|::.*/g, "");
		var fieldName = reArray[0].replace(/^\{|.*::|\}/g, "");
		
		var ref = null;
		if (dsName != "") {
			var formObj = eval("document."+dsName);
			if (eval("document."+dsName))
				ref = eval("document."+dsName+"."+fieldName);
		} else 
			ref = $(fieldName);
			
		if (ref && ref.nodeName == "INPUT") {
			var elementHandlerObj = dbates.Spry.Util.EventHandler.addEventHandler(element, eventType, null, true);
			var handlerObj = dbates.Spry.Util.EventHandler.addEventHandler(ref, eventType, function(u) { dbates.Spry.Eval.evaluate(element); }, false);
			handlerObj.addEventListener(function(u) { elementHandlerObj.handleEvent(); });
		} else if (ref && dbates.Spry.Util.hasClassNamed(ref, dbates.Spry.Eval.CLASS_NAME))
			dbates.Spry.Util.EventHandler.addEventHandler(ref, eventType, function(u) { dbates.Spry.Eval.evaluate(element); }, true);
		
		startSearchIndex = re.lastIndex;
	}
};
/* HELPER ROUTINES. */
dbates.Spry.Eval.getTokensFromStr = function(str)
{
	if (!str)
		return null;
	return str.match(/{[^}]+}/g);
};
dbates.Spry.Util.getNodesByFunc = function(root, func)
{
	var nodeStack = new Array;
	var resultArr = new Array;
	var node = root;

	while (node) {
		if (func(node))
			resultArr.push(node);

		if (node.hasChildNodes()) {
			nodeStack.push(node);
			node = node.firstChild;
		} else {
			if (node == root)
				node = null;
			else
				try { node = node.nextSibling; } catch (e) { node = null; };
		}
		
		while (!node && nodeStack.length > 0) {
			node = nodeStack.pop();
			if (node == root)
				node = null;
			else
				try { node = node.nextSibling; } catch (e) { node = null; }
		}
	}
	
	if (nodeStack && nodeStack.length > 0)
		// Error.
		return;

	return resultArr;
};
dbates.Spry.Eval.getElementsByClassName = function(node, className)
{
	var result = new Array();
	var elemList = node.childNodes;
	if (elemList) {
		for (var i = 0; i < elemList.length; ++i) {
			if (elemList.item(i).className == className)
				result.push(elemList.item(i));
		}
	}
	return result;
};
dbates.Spry.Eval.getElementsByClassAndTag = function(node, className, tagName)
{
	if (!document.getElementsByTagName)
		return;
	if (!tagName)
		var tagName = '*';
	if (!node)
		var node = document;
	
	var result = new Array();
	var elemList = node.getElementsByTagName(tagName);
	if (elemList) {
		for (var i = 0; i < elemList.length; ++i) {
			if (elemList.item(i).className == className)
				result.push(elemList.item(i));
		}
	}
	return result;
};
dbates.Spry.Util.addEventListener = function(element, eventType, handler, capture)
{
	try
	{
		element = $(element);
		if (element.addEventListener)
			element.addEventListener(eventType, handler, capture);
		else if (element.attachEvent)
			element.attachEvent("on" + eventType, handler);
	}
	catch (e) {}
};
dbates.Spry.Util.hasClassNamed = function(node, className)
{
	if (!node)
		return;
		
	try{
		var classes = node.className.split(' ');
		var num_classes = classes.length;
		for (var i = 0; i < num_classes; ++i)
			if (classes[i] == className)
				return true;
		return false;
	} catch(e) { return false}
};
/* Event Handler. */
dbates.Spry.Util.EventHandler = function (element, eventType)
{
	this.element = element;
	this.eventType = eventType;
	this.isAttached = false;
	this.listeners = new Array();
};
dbates.Spry.Util.EventHandler.prototype.addEventListener = function(handler)
{
	this.listeners.push(handler);
};
dbates.Spry.Util.EventHandler.prototype.attach = function()
{
	if (this.isAttached)
		return;
	
	var _this = this;
	dbates.Spry.Util.addEventListener(this.element, this.eventType, function () { _this.handleEvent(); }, false);
	this.isAttached = true;
};
dbates.Spry.Util.EventHandler.prototype.handleEvent = function()
{
	var num_listeners = this.listeners.length;
	for (var i = 0; i < num_listeners; ++i) {
		var listener = this.listeners[i];
		listener(this.element);
	}
};
dbates.Spry.Util.EventHandler.getEventHandler = function(element, eventType)
{
	var num_handlers = dbates.Spry.Util.EventHandlers.length;
	for (var i = 0; i < num_handlers; ++i) {
		if (dbates.Spry.Util.EventHandlers[i].element == element && 
			dbates.Spry.Util.EventHandlers[i].eventType == eventType)
			return dbates.Spry.Util.EventHandlers[i];
	}
	return null;
};
dbates.Spry.Util.EventHandler.addEventHandler = function(element, eventType, handler, isVirtualEvent)
{
	var handlerObj = dbates.Spry.Util.EventHandler.getEventHandler(element, eventType);
	if (!handlerObj) {
		handlerObj = new dbates.Spry.Util.EventHandler(element, eventType);
		dbates.Spry.Util.EventHandlers.push(handlerObj);
	}
	if (handlerObj && handler)
		handlerObj.addEventListener(handler);
	if (!isVirtualEvent)
		handlerObj.attach();
	return handlerObj;
};
/* Other routines. */
function $()
{
	var elements = new Array();
	
	for (var i = 0; i < arguments.length; i++)
	{
		var element = arguments[i];
		if (typeof element == 'string')
			element = document.getElementById(element);
		
		if (arguments.length == 1)
			return element;
		
		elements.push(element);
	}
	
	return elements;
};