// < business rules engine >

function initializeForm(formId) {
	logger.enterMethod("initializeForm("+formId+")");

	if(window.defineFormBehaviours) {
		defineFormBehaviours();
	} else {
		logger.error("Page does not have method defineFormBehaviours()");
	}

	// register a event handler that is invoked when the visitor submits the form
	var form = document.getElementById(formId);
	if (form != undefined) {
		// only register the event handler if there is a form (this is not the case on a Thank-You page).
		form.onsubmit = validateFormBeforeSubmit;
		// for each submit button register an event handler
		var inputs = form.getElementsByTagName("input");
		for (var i = 0; i < inputs.length; i++) {
			if (inputs[i].type == "submit") {
				inputs[i].onclick = registerSubmitButton;
			}
		}
	}

	logger.exitMethod();
}

function registerSubmitButton(mozEvent) {
	submitButton = isIE ? event.srcElement : mozEvent.target;
}

function validateFormBeforeSubmit() {
	// if the previous button was pressed then do not validate any fields
	// (navigating to the previous page should always be possible)
	if (submitButton && submitButton.name == "PreviousBtn") {
		return true;
	}


	var doValidation = true;
	if(isIE && !submitButton) { // dynamically determine action when no submitButton is set (when form is submitted using enter key and only a single text type input exists)
		var form = isIE ? event.srcElement : mozEvent.target;

		var actionInput = document.createElement("input");
		actionInput.setAttribute("type", "hidden");
		
		// default action:
		actionInput.setAttribute("name", "NextBtn");
		actionInput.setAttribute("value", "Next");

		// find first form field of type='submit' that will be the default action when enter is pressed 
		// and no submit button was triggered
		var inputs = form.getElementsByTagName("input");
		for (var i = 0; i < inputs.length; i++) {
			if (inputs[i].type == "submit") {
				actionInput.setAttribute("name", inputs[i].name);
				actionInput.setAttribute("value", inputs[i].value);

				if(inputs[i].name == 'PreviousBtn') {
					doValidation = false;
				}
				break;
			}
		}
		form.appendChild(actionInput);
	}
	

	var result = true;
	if(doValidation) {
		// iterate over all registered fields
		// also determine the message in the alert box consisting of
		// error messages of fields for which no error container exists
		var result = true;
		for (var fieldIndex = 0; fieldIndex < fieldNames.length; fieldIndex++) {
			var fieldName = fieldNames[fieldIndex];
			var field = engine.getFieldForFieldName(fieldName);

			// if the field is relevant then invoke its validate and prepare behaviour behaviour
			if (engine.getRelevanceForField(field)) {
				engine.clearErrorMsgForFieldName(fieldName);
				engine.invokeBehaviourOnField("Validate", field);
				if (engine.existsErrorMsgForFieldName(fieldName)) {
					result = false;
				}
			}
		}
	}
	
	return result;
}

function triggerFieldBehaviour(mozEvent) {
	var field;
	if (isIE) {
		field = event.srcElement;
	} else {
		field = mozEvent.target;
	}
	var fieldRef = field.id;

	// trigger the behaviour on fields that are dependent on the one that caused the trigger
	var dependentFields = dependencies[fieldRef];
	if (dependentFields == undefined && field.name != undefined) {
		// hack: to get radiobuttons to trigger dependent fields, look up the real fieldRef by it's name
		fieldRef = field.name;
		dependentFields = dependencies[fieldRef];
	}
	triggerDependentFields(fieldRef);
}
function triggerDependentFields(fieldRef) {
	var dependentFields = dependencies[fieldRef];
	logger.info("Field "+fieldRef+" is being triggered -> dependent fields are "+dependentFields);
	if (dependentFields != undefined) {
		var fields = dependentFields.match(/\b[^\s]+\b/g);
		for (var i=0; i < fields.length; i++) {
			invokeBehaviourForFieldTree(fields[i]);
			if ((GetValueBehaviours[fields[i]] != undefined) || (GetDisplayValueBehaviours[fields[i]] != undefined)) {
				triggerDependentFields(fields[i]);
			}
		}
	}
}

// This function is invoked by dependent fields that are triggered. For the field having the supplied name it invokes
// the relevance behaviour. If the field is found to be relevant then the prepare behaviour is invoked and the
// function is recursively called for each child field.
function invokeBehaviourForFieldTree(fieldName) {
	logger.enterMethod("invokeBehaviourForFieldTree('" + fieldName + "')");

	var field = engine.getFieldForFieldName(fieldName);
	if (field) {

		// handle Relevant behaviour
		var isRelevant = engine.invokeBehaviourOnField("Relevant", field);
		if (isRelevant != undefined) {
			engine.setRelevanceForField(field, isRelevant);
		} else {
			engine.clearRelevanceForField(field);
		}

		// prepare the field if it's relevant and recursively call this method for all child fields
		if (isRelevant == undefined || isRelevant) {

			// handle Prepare behaviour
			engine.invokeBehaviourOnField("Prepare", field);

			// recursively call this method for all child fields (if any)
			var children = fieldChildren[fieldName];
			if (children != undefined) {
				var fields = children.match(/\b[^\s]+\b/g);
		        for (var childIndex = 0; childIndex < fields.length; childIndex++) {
		        	invokeBehaviourForFieldTree(fields[childIndex]);
		        }
			}
		}
    } else {
		logger.error("Invoked behaviour on an unknown field: " + fieldName);
    }

	logger.exitMethod();
}

var dependencies = new Array();

// this array contains the names of all fields on the current form page.
var fieldNames = new Array();

// this array contains for each field a list of child fields
var fieldChildren = new Array();

// this array contains for each field its parent
var fieldParent = new Array();

// the HTML element of the submit button that was pressed.
var submitButton;

function addDependency(toFieldRef, fromFieldRef) {
	logger.enterMethod("addDependency('"+fromFieldRef+"', '"+toFieldRef+"')");
	if (fromFieldRef != toFieldRef) {
		var fromField = engine.getFieldForFieldName(fromFieldRef);
		if (fromField) {
			// add the dependency to the dependencies array if it's not already in there
			if (dependencies[fromFieldRef] == undefined) {
				dependencies[fromFieldRef] = "";
			}
			if (dependencies[fromFieldRef].indexOf(toFieldRef+" ") == -1) {
				dependencies[fromFieldRef] += toFieldRef + " ";
			}

			// add our custom handler to the source fromField
			var localName = getLocalNameInLowerCase(fromField);
			var type = (localName == "input" ? fromField.type.toLowerCase() : null);
			if (localName == "select" || localName == "textarea" ||
			        ((localName == "input") && (type == "file" || type == "text" || type == "password"))) {
				logger.info("Register event handler for onchange event of field: " + fromFieldRef);
				fromField.onchange = triggerFieldBehaviour;
			} else if (localName == "input") {
				logger.info("Register event handler for onclick event of field: " + fromFieldRef);
				fromField.onclick = triggerFieldBehaviour;
			} else if (localName == "tr" || localName == "div") {
				// add event handlers for the radio buttons, because we do not
				// generate explicit dependencies for this field
				var childInputs = fromField.getElementsByTagName("input");
				for (var i = 0; i < childInputs.length; i++) {
					var current = childInputs[i];
					if ((current.type.toLowerCase() == "radio") &&
					        (current.name == fromFieldRef) &&
					        (current.name != current.id)) {
						logger.info("Register event handler for onclick event of field: " + current.id);
						current.onclick = triggerFieldBehaviour;
					}
				}
			} else {
				logger.warning("No event handler is registered for field: " + fromFieldRef);
			}
		} else {
			logger.warning("addDependency: could not find fromField '"+fromFieldRef+"'");
		}
	}
	logger.exitMethod();
}

// registers a parent-child relation ship between two fields; the first parameter contains the name of the parent field
// and the second one the name of the child field.
function addFieldHeritage(parentFieldName, childFieldName) {
	logger.enterMethod("addFieldHeritage('" + parentFieldName + "', '" + childFieldName + "')");

	// check if the supplied names do not refer to the same field.
	if (parentFieldName == childFieldName) {
		logger.error("invalid call to addFieldHeritage: parent and child refer to the same field.");
	}

    // check if there already exists an entry for the parent in the fieldChildren array (create one if not)
	if (fieldChildren[parentFieldName] == undefined) {
		fieldChildren[parentFieldName] = "";
	}

	// only add the relation to the fieldChildren array if it has not been registered before
	if (fieldChildren[parentFieldName].indexOf(childFieldName + " ") == -1) {
	    fieldChildren[parentFieldName] += childFieldName + " ";
	}

	// check if there already exists an entry for the child in the fieldParent array
	if (fieldParent[childFieldName] == undefined) {
		fieldParent[childFieldName] = parentFieldName;
	}

	logger.exitMethod();
}

function addFieldName(fieldName) {
	logger.enterMethod("addFieldName('" + fieldName + "')");
	fieldNames[fieldNames.length] = fieldName;
	logger.exitMethod();
}

function BusinessRulesEngine() {
}
// invokes the specified behaviour of the given field. Returns the result of the last rule that it executed.
BusinessRulesEngine.prototype.invokeBehaviourOnField = function(behaviour, field) {
	logger.enterMethod("invokeBehaviourOnField('"+behaviour+"', "+field.id+")");
	var result;

	// determine the XML for the behaviour being invoked
	var behaviourXML;
	var behaviourArray = eval(behaviour+"Behaviours");
	for (var behaviourFieldID in behaviourArray) {
		if (behaviourFieldID == field.id) {
			behaviourXML = behaviourArray[field.id];
			break; // break from for loop
		}
	}
	if (behaviourXML) {
		var rules = BusinessRulesEngine_getRulesFromXML(behaviourXML);
		if (rules) {
			logger.info("Executing "+rules.length+" rules for "+behaviour+" behaviour on field " + field.id+" ("+field+")");
			for (var i = 0; i < rules.length; i++) {
				var rule = rules.item(i);
				if (rule) {
					logger.info("Invoking rule "+i+": "+serializeNode(rule));
					result = this.execute(rule, field);
				}
			}
		}
	}
	logger.exitMethod();
	return result;
}
BusinessRulesEngine.prototype.execute = function(ruleElement, field) {
	// call the dispatcher method that has (hopefully) been generated based on the registered business rules
	return executeBusinessRule(ruleElement, field);
}
BusinessRulesEngine.prototype.getParameters = function(rule, field) {
	var result = new Array();
	var parameters = rule.getElementsByTagName("*");
	if (parameters == null || parameters.length == 0) {
		if (rule.firstChild) {
			// TODO: we probably should return the first *non-empty* child
			result.push(rule.firstChild);
		}
	} else {
		for (var i=0; i < parameters.length; i++) {
			if (parameters.item(i).parentNode == rule) {
				result.push(parameters.item(i));
			}
		}
	}
	// add a function to the result to make it compatible with ParameterList on the server
	result.getStringValue = function(index) {
		return engine.getStringParameter(field, this[index]);
	}
	result.getIntValue = function(index) {
		return engine.getIntParameter(field, this[index]);
	}
	result.getIntValue = function(index, defaultValue) {
		return engine.getIntParameter(field, this[index], defaultValue);
	}
	result.getDoubleValue = function(index) {
		return engine.getDoubleParameter(field, this[index]);
	}
	result.getDoubleValue = function(index, defaultValue) {
		return engine.getDoubleParameter(field, this[index], defaultValue);
	}
	result.getDateValue = function(index) {
		return engine.getDateParameter(field, this[index]);
	}
	result.getBooleanValue = function(index) {
		return engine.getBooleanParameter(field, this[index]);
	}
	return result;
}
BusinessRulesEngine.prototype.getStringParameter = function(field, rule) {
	var result;
	var value = engine.execute(rule, field);
	if (value) {
		result = value;
	}
	return result;
}
BusinessRulesEngine.prototype.getIntParameter = function(field, rule) {
	var result;
	var value = engine.execute(rule, field);
	if (value) {
		result = new Number(value);
	}
	return result;
}
BusinessRulesEngine.prototype.getIntParameter = function(field, rule, defaultValue) {
	var result = defaultValue;
	var value = engine.execute(rule, field);
	if (value) {
		result = new Number(value);
	}
	return result;
}
BusinessRulesEngine.prototype.getDoubleParameter = function(field, rule) {
	var result;
	var value = engine.execute(rule, field);
	if (value) {
		if (value instanceof Number) {
			result = value;
		} else if (!isNaN(parseFloat(value, 10))) {
			result = new Number(value);
		} else {
			logger.error("Unable to parse parameter '" + value + "' as a double.");
		}
	} else {
		logger.error("No value is specified for this parameter, so it can't be converted to a double");
	}
	return result;
}
BusinessRulesEngine.prototype.getDoubleParameter = function(field, rule, defaultValue) {
	var result = defaultValue;
	var value = engine.execute(rule, field);
	if (value) {
		if (value instanceof Number) {
			result = value;
		} else if (!isNaN(parseFloat(value, 10))) {
			result = new Number(value);
		}
	}
	return result;
}
BusinessRulesEngine.prototype.getDateParameter = function(field, rule) {
	var result;
	var value = engine.execute(rule, field);
	if (value instanceof Date) {
		result = value;
	} else if (value) {
		// similar to the WebForms Server we assume that the string representation
		// of the date is of the form yyyy-MM-dd
		if (value.search("^\d{4}-\d{1,2}-\d{1,2}$")) {
			var yearMonthSeparator = value.indexOf("-");
			var monthDaySeparator = value.lastIndexOf("-");
			var year = parseInt(value.substr(0, 4), 10);
			var month = parseInt(value.substr(yearMonthSeparator + 1, monthDaySeparator - 1), 10);
			var day = parseInt(value.substr(monthDaySeparator + 1), 10);
			result = new Date(year, month - 1, day);
		} else {
			logger.error("Unable to parse parameter '" + value + "' as a date. It must be of the form yyyy-MM-dd.");
		}
	} else {
		logger.error("No value is specified for this parameter, so it can't be converted to a date");
	}
	return result;
}
BusinessRulesEngine.prototype.getBooleanParameter = function(field, rule) {
	var result;
	var value = engine.execute(rule, field);
	if (value != undefined) {
		if (typeof value == 'string') {
			result = "true" == value;
		} else if (typeof value == 'boolean') {
			result = value;
		}
	}
	return result;
}
BusinessRulesEngine.prototype.getFieldForFieldName = function(fieldName) {
	return this.getForm().getFieldForFieldName(fieldName);
}
BusinessRulesEngine.prototype.getValueForFieldName = function(fieldName) {
	return this.getForm().getValueForFieldName(fieldName);
}
BusinessRulesEngine.prototype.containsValueForFieldName = function(fieldName, value) {
	return this.getForm().containsValueForFieldName(fieldName, value);
}
BusinessRulesEngine.prototype.setValueForFieldName = function(fieldName, value) {
	return this.getForm().setValueForFieldName(fieldName, value);
}
BusinessRulesEngine.prototype.clearErrorMsgForFieldName = function(fieldName) {
	return BusinessRulesEngine_clearErrorMsgForFieldName(fieldName);
}
BusinessRulesEngine.prototype.setErrorMsgForFieldName = function(fieldName, errorMsg) {
	return BusinessRulesEngine_setErrorMsgForFieldName(fieldName, errorMsg);
}
BusinessRulesEngine.prototype.existsErrorMsgForFieldName = function(fieldName) {
	return BusinessRulesEngine_existsErrorMsgForFieldName(fieldName);
}
BusinessRulesEngine.prototype.getErrorMsgForFieldName = function(fieldName) {
	return BusinessRulesEngine_getErrorMsgForFieldName(fieldName);
}
BusinessRulesEngine.prototype.showField = function(field) {
	return BusinessRulesEngine_showField(field);
}
BusinessRulesEngine.prototype.hideField = function(field) {
	return BusinessRulesEngine_hideField(field);
}
BusinessRulesEngine.prototype.getLabelOfItemForFieldName = function(itemValue, fieldName) {
	return BusinessRulesEngine_getLabelOfItemForFieldNameFromHTML(itemValue, fieldName);
}
BusinessRulesEngine.prototype.setRelevanceForField = function(field, relevant) {
	return BusinessRulesEngine_setRelevanceForField(field, relevant);
}
BusinessRulesEngine.prototype.clearRelevanceForField = function(field) {
	return BusinessRulesEngine_clearRelevanceForField(field);
}
BusinessRulesEngine.prototype.getRelevanceForField = function(field) {
	return BusinessRulesEngine_getRelevanceForField(field);
}

BusinessRulesEngine.prototype.form = new Form();
BusinessRulesEngine.prototype.getForm = function() {
	return this.form;
}

// create an instance of the engine for reference within the javascript code
var engine = new BusinessRulesEngine();

function Form() {
}
Form.prototype.invokeBehaviourOnField = function(engine, behaviour, data, field) {
	return engine.invokeBehaviourOnField(behaviour, field)
}
// Returns the field element with the given fieldName as id or null if no such field element exists.
Form.prototype.getFieldForFieldName = function(fieldName) {
	var result = null;
	if (isIE) {
		// For Internet Explorer we cannot use the getElementById() function because it does not always return
		// the expected result. Consider for example the following HTML fragment:
		//
		// <html>
		//   <head></head>
		//   <body onload="alert(document.getElementById('p2').real);">
		//     <input type="hidden" id="p42" name="p2" real="2">name="p2"</input>
		//     <select id="p2" real="3">id="p2"</select>
		//   </body>
		// </html>
		//
		// One would expect that the value 3 is shown in the alert box (because the select element has id 'p2'),
		// however IE selects the input element instead! Although the id and name attributes share the same
		// namespace (which would explain a part of it) one would still expect that the id attribute has a higher
		// priority. To work around this issue we use the getElementsByName method which returns a collection
		// of fields. Within this collection we look for the element with the correct id.
		var fields = document.getElementsByName(fieldName);
		for (var fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
			if (fields[fieldIndex].id == fieldName) {
				result = fields[fieldIndex];
				break;
			}
		}
	} else {
		// for other browsers we assume that the getElementById is correctly implemented (for FireFox this is the case).
		result = document.getElementById(fieldName);
	}
	return result;
}
// Returns the current value for the given field
Form.prototype.getValueForField = function(engine, data, field) {
	return this.getValueForFieldName(field.id);
}
// Returns the current value for the field with the given name
Form.prototype.getValueForFieldName = function(fieldName) {
	logger.enterMethod("getValueForFieldName('"+fieldName+"')");

	var result;

	// first invoke the GetValue behaviour for the field (if defined)
	var field = engine.getForm().getFieldForFieldName(fieldName);
	if (field) {
		// determine the value of the field by executing it's GetValue rule
		result = engine.invokeBehaviourOnField("GetValue", field);
		if (result == undefined) {
			// the result is undefined: look up the value in the HTML
			result = BusinessRulesEngine_getValueForFieldNameFromHTML(fieldName);
		}
	} else {
		logger.error("Undefined field: could not find field named '"+fieldName+"'");
	}

	logger.info("getValueForFieldName('"+fieldName+"')="+result);
	logger.exitMethod();
	return result;
}

// Returns true if the specified value is selected in a multi-valued field.
Form.prototype.containsValueForFieldName = function(fieldName, value) {
	logger.enterMethod("containsValueForFieldName('" + fieldName + "', '" + value + "')");

	var values = BusinessRulesEngine_getValuesForFieldNameFromHTML(fieldName);
	var result = false;
	for (var valueIndex = 0; valueIndex < values.length; valueIndex++) {
		if (values[valueIndex] == value) {
			result = true;
			break;
		}
	}

	logger.info("containsValueForFieldName: result = " + result);

	logger.exitMethod();
	return result;
}

// Sets the value of the given field to the given value
Form.prototype.setValueForField = function(engine, data, field, value) {
	return this.setValueForFieldName(field.id, value);
}
// Sets the value of the field with the given fieldName to the given value
Form.prototype.setValueForFieldName = function(fieldName, value) {
	logger.enterMethod("setValueForFieldName('"+fieldName+"', '"+value+"')");
	var inputs = document.getElementsByTagName("input");
	var i;
	for (i=0; i < inputs.length; i++) {
		var name = inputs[i].getAttribute("name");
		if (name == fieldName) {
			var inputType = inputs[i].type;
			if ((inputType == "text") || (inputType == "hidden")) {
				inputs[i].value = value;
			} else if ((inputType == "radio") || (inputType == "checkbox")) {
				logger.info("inputs[i].checked = ("+inputs[i].value+" == "+value+")");
				inputs[i].checked = (inputs[i].value == value);
			} else {
				logger.error("setValueForFieldName: fields with type '"+inputType+"' are not supported");
			}
		}
	}

	var field = engine.getFieldForFieldName(fieldName);
	if (field != null) {
		if (getLocalNameInLowerCase(field) == "select") {
			// check the select element
			var options = field.getElementsByTagName("option");
			for (i = 0; i < options.length; i++) {
				logger.info("options[i].selected = (" + options[i].value + " == " + value + ")");
				options[i].selected = (options[i].value == value);
			}
		} else if (getLocalNameInLowerCase(field) == "textarea") {
			field.value = value;
		}
	}
	logger.exitMethod();
}




// parses the XML fragment and returns the resulting nodelist. Note that ruleXML doesn't need to be a full XML document.
function BusinessRulesEngine_getRulesFromXML(ruleXML) {
	var ruleDOM = parseXML("<dummyRoot>"+ruleXML+"</dummyRoot>");
	var result;
	if (ruleDOM != undefined) {
		result = ruleDOM.documentElement.childNodes;
	}
	return result;
}

// given an element node this function returns the tagname in lowercase.
function getLocalNameInLowerCase(elementNode) {
	if (elementNode && elementNode.nodeType == ELEMENT_NODE) {
		return elementNode.tagName.toLowerCase();
	}
	logger.error("getLocalNameInLowerCase(elementNode): Invalid node supplied.");
	return "";
}

function getRuleIdentifier(ruleNode) {
	if (ruleNode && ruleNode.nodeType == ELEMENT_NODE) {
		return isIE ? ruleNode.baseName: ruleNode.localName;
	}
	return "";
}

function BusinessRulesEngine_getValuesForFieldNameFromHTML(fieldName) {
	enterMethod("BusinessRulesEngine_getValuesForFieldNameFromHTML('" + fieldName + "')");

	var result = new Array();

	// first look up all the input elements whose 'name' attribute equals the supplied fieldName
	var inputs = document.getElementsByTagName("input");
	for (var inputIndex = 0; inputIndex < inputs.length; inputIndex++) {
		var current = inputs[inputIndex];
		if (current.getAttribute("name") == fieldName) {
			var inputType = current.type;
			if ((inputType == "radio") || (inputType == "checkbox")) {
				if (current.checked) {
					result.push(current.value);
				}
			} else if ((inputType == "text") || (inputType == "hidden") || (inputType == "password") || (inputType == "file")) {
				result.push(current.value);
			} else {
				logger.error("BusinessRulesEngine_getValuesForFieldNameFromHTML: fields with type '" + inputType + "' are not supported.");
			}
		}
	}

	// next look up the select element having the supplied fieldName as id
	var field = engine.getFieldForFieldName(fieldName);
	if (field != null) {
		if (getLocalNameInLowerCase(field) == "select") {
			var options = field.getElementsByTagName("option");
			for (var optionIndex = 0; optionIndex < options.length; optionIndex++) {
				if (options[optionIndex].selected) {
					result.push(options[optionIndex].value);
				}
			}
		} else if (getLocalNameInLowerCase(field) == "textarea") {
			result.push(field.value);
		}
	}

	exitMethod();
	return result;
}

// Retrieves the value of the field with fieldName from the HTML of the form
function BusinessRulesEngine_getValueForFieldNameFromHTML(fieldName) {
	enterMethod("BusinessRulesEngine_getValueForFieldNameFromHTML('" + fieldName + "')");

	// TODO: allow field type developer to hook into this getValue mechanism for custom field types
	// TODO: make a list with all textarea elements that have the requested name

	var values = BusinessRulesEngine_getValuesForFieldNameFromHTML(fieldName);
	var result;
	if (values.length == 0) {
		result = undefined;
	} else {
		result = values[0];
	}

	exitMethod();

	return result;
}

function BusinessRulesEngine_getLabelOfItemForFieldNameFromHTML(itemValue, fieldName) {
	enterMethod("BusinessRulesEngine_getLabelOfItemForFieldNameFromHTML('" + itemValue + "', '" + fieldName + "')");

	var result;
	var field = engine.getFieldForFieldName(fieldName);
	if (field != null) {
		if (getLocalNameInLowerCase(field) == "select") {
			var options = field.getElementsByTagName("option");
			for (var optionIndex = 0; optionIndex < options.length; optionIndex++) {
				if (options[optionIndex].value == itemValue) {
					result = getTextValue(options[optionIndex]);
					break;
				}
			}
		} else {
			logger.error("BusinessRulesEngine_getLabelOfItemForFieldNameFromHTML does not support elements with local name: " + getLocalNameInLowerCase(field));
		}
	} else {
		logger.error("Could not find field with id: " + fieldName);
	}

	exitMethod();
	return result;
}

function BusinessRulesEngine_clearErrorMsgForFieldName(fieldName) {
	enterMethod("BusinessRulesEngine_clearErrorMsgForFieldName('"+fieldName+"')");
	// append err to the end of the fieldName to find the error container
	var errorContainer = document.getElementById(fieldName+".err");
	if (errorContainer) {
		// append errormsg to the end of the fieldName to find the error message container
		var errorMsgContainer = document.getElementById(fieldName+".errormsg");
		if (errorMsgContainer) {
			// remove the text nodes in the error message container
			while (errorMsgContainer.firstChild != undefined) {
				errorMsgContainer.removeChild(errorMsgContainer.firstChild);
			}
		} else {
			logger.error("Unable to find error message container: '"+fieldName+".errormsg'");
		}
		// hide the error container
		errorContainer.style.display = "none";
	} else {
		logger.error("Unable to find error container: '" + fieldName + ".err'");
	}
	exitMethod();
}
function BusinessRulesEngine_setErrorMsgForFieldName(fieldName, errorMsg) {
	enterMethod("BusinessRuleEngine_setErrorMsgForFieldName('"+fieldName+"', '"+errorMsg+"')");
	// append err to the end of the fieldName to find the error container
	var errorContainer = document.getElementById(fieldName+".err");
	if (errorContainer) {
		// append errormsg to the end of the fieldName to find the error message container
		var errorMsgContainer = document.getElementById(fieldName+".errormsg");
		if (errorMsgContainer) {
			// replace the text nodes in the error message container with the new errorMsg
			while (errorMsgContainer.firstChild != undefined) {
				errorMsgContainer.removeChild(errorMsgContainer.firstChild);
			}
			errorMsgContainer.appendChild(document.createTextNode(errorMsg));
		} else {
			logger.error("Unable to find error message container: '"+fieldName+".errormsg'");
		}
		// show the error container
		errorContainer.style.display = "block";
	} else {
		logger.error("Unable to find error container: '"+fieldName+".err'");
	}

	exitMethod();
}

function BusinessRulesEngine_existsErrorMsgForFieldName(fieldName) {
	return engine.getErrorMsgForFieldName(fieldName).length > 0;
}

function BusinessRulesEngine_getErrorMsgForFieldName(fieldName) {
	var errorContainer = document.getElementById(fieldName + ".err");
	if (errorContainer) {
		var errorMessageContainer = document.getElementById(fieldName + ".errormsg");
		if (errorMessageContainer) {
			return getTextValue(errorMessageContainer);
		} else {
			logger.error("Unable to find error message container: '" + fieldName + ".errormsg'");
			return "";
		}
	} else {
		logger.error("Unable to find error container: '" + fieldName + ".err'");
		return "";
	}
}

function BusinessRulesEngine_showField(field) {
	field.style.display = "block";
}

function BusinessRulesEngine_hideField(field) {
	field.style.display = "none";
}

function BusinessRulesEngine_setRelevanceForField(field, relevant) {
	if (relevant) {
		BusinessRulesEngine_showField(field);
	} else {
		BusinessRulesEngine_hideField(field);
		BusinessRulesEngine_clearErrorMsgForFieldName(field.id);
	}
}

function BusinessRulesEngine_clearRelevanceForField(field) {
	BusinessRulesEngine_showField(field);
}

function BusinessRulesEngine_getRelevanceForField(field) {
	enterMethod("BusinessRulesEngine_getRelevanceForField('"+field.id+"')");
	var relevant = field.style.display != "none";
	if (relevant && (fieldParent[field.id] != undefined)) {
		var parent = engine.getFieldForFieldName(fieldParent[field.id]);
		relevant = BusinessRulesEngine_getRelevanceForField(parent);
	}
	exitMethod();
	return relevant;
}

var data = new Object();


// </ business rules engine >
