/**
 * This script will provide client side validation for all form controls on a
 * page. If a given control wants to be validated, it must be decorated with an
 * attribute that indicates the type of validation it wants
 * 
 * Attribute Validation Description
 * 
 * required must have a value pattern must meet the regex defined in the value
 * for the attribute usage: pattern="javascript regex" phonenumber must be a
 * valid phone number email must be a valid email
 * 
 * USAGE:
 * 
 * <input type="text" name="firstName" required/>
 * 
 * A text box decorated with the required attribute, as above, will be
 * automatically validated by this script.
 * 
 * 
 * This script will do two things if validation fails. First, it will set the
 * CSS class to "invalid". When the errors are fixed, the class will be changed
 * to "valid". If you don't need this, don't define the CSS values. WARNING: if
 * you define other CSS class names for the elements, you'll want to turn this
 * off or your own classes will be overridden. But it seems uncommon to assign
 * class names to invididual form controls.
 * 
 * Additionally, this script will add a control type specific validation error
 * message to the side of the control. Note, the layout must be somewhat
 * compatiable ( i.e. CSS based, see formbuilder feature's HTML templates ) for
 * this DHMTL to work.
 * 
 * This validation is defined in an "anonymous" function that invokes itself as
 * it's scanned by the browser's document parser. It will register itself to run
 * when the document is fully loaded. When it runs, it scans all of the form
 * controls on the page, looking for validation attributes, and registering
 * appropriate onChange validation methods.
 * 
 * This script is based on a script from David Flanagan's The Definitive Guide
 * to Javascript.
 * 
 * Note: all of these functions are defined within the "closure" of the anonymous 
 * function that init's the validation system.  Functions that should be exposed as
 * public, i.e. accessible to other javascript, are exported into the "validate"
 * namespace.
 * 
 */
$( function() {

	function init() {
		// Loop through all forms in the document
		for ( var i = 0; i < document.forms.length; i++) {
			var f = document.forms[i]; // the form we're working on now

			// Assume, for now, that this form does not need any validation
			var needsValidation = false;
			var handlerAdded = false;

			// Now we need to register all the elements in the form for
			// validation if they need it
			// Note, we can handle the elements one at a time for several of the
			// form control types,
			// but for checkboxes and radioboxes, we'll need to do something
			// special

			var checkboxGroupNames = new Array();
			for (j = 0; j < f.elements.length; j++) {
				var e = f.elements[j];

				// Start off by making a valid property and setting it to true,
				// as all fields must begin as valid. We will use this to keep
				// track of a fields state.
				e.valid = true;

				if (e.type == "text" || e.type == "textarea") {
					needsValidation = processTextInput(e);
				} else if (e.type == "select-one"
						|| e.type == "select-multiple") {
					needsValidation = processNonTextInput(e);
				} else if (e.type == "checkbox") {
					needsValidation = processNonTextInput(e);
				}

				if (needsValidation && !handlerAdded) {
					//we need to save the old handler, if there was one, and write a new function
					// that invokes the oldhanlder after our's
					if (f.onsubmit) {
						f.existingOnSubmit = f.onsubmit;
						f.validateOnSubmit = validateOnSubmit;
						f.onsubmit = function() {
							f.validateOnSubmit();
							f.existingOnSubmit();
						}
					} else {
						f.onsubmit = validateOnSubmit;
					}
					handlerAdded = true;

				}

			}

		}
	}

	/* Handles the submission by processing all of the validation method for all of the 
	 * form elements in the form who's being submitted.  This is written as an onsubmit
	 * hanlder.  Another method can be called from other javascript to do the same thing. 
	 */

	function validateOnSubmit() {

		var valid = true;
		for ( var i = 0; i < this.elements.length; i++) {
			var e = this.elements[i];
			if (((e.type == "text" || e.type == "textarea") && (e.onchange == validateRegexOnChange
					|| e.onchange == validatePhoneNumberOnChange || e.onchange == validateEmailOnChange))
					|| ((e.type == "select-one" || e.type == "select-multiple") && e.onchange == validateSelectOnChange)
					|| (e.type == "checkbox" && e.onchange == validateCheckboxOnChange)) {

				e.onchange();
				if (e.className == "invalid")
					valid = false;
			}
		}

		return valid;

	}

	/* Specific registration methods */

	function processTextInput(textbox) {

		var pattern = textbox.getAttribute("pattern");
		var required = textbox.getAttribute("required") != null;
		var phonenumber = textbox.getAttribute("phonenumber") != null;
		var email = textbox.getAttribute("email") != null;

		// phone numbers aren't done weith regex's
		// so we will add dedicated handler
		if (phonenumber) {
			addOnChangeHandler(textbox, validatePhoneNumberOnChange);
			return true;
		}
		if (email) {
			addOnChangeHandler(textbox, validateEmailOnChange);
			return true;
		}
		//set the pattern for interally defined patterns
		if (required && !pattern) {
			pattern = "\\S";
			textbox.setAttribute("pattern", pattern);
		}
		if (pattern) {
			addOnChangeHandler(textbox, validateRegexOnChange);
			return true;
		}

	}

	function processNonTextInput(input) {
		var required = input.getAttribute("required") != null;

		var needsValidation = false;
		if (required) {
			needsValidation = true;
			setOnChangeByType(input);
		}
		return needsValidation;
	}

	function setOnChangeByType(input) {

		if (input.type == "select-one" || input.type == "select-multiple") {
			addOnChangeHandler(input, validateSelectOnChange);
		} else if (input.type == "checkbox") {
			addOnChangeHandler(input, validateCheckboxOnChange);
		}
		return;

	}
	/*
	 * Validation methods for various types.
	 */

	function validateSelectOnChange() {
		if (this.type == "select-one") {
			if (this.selectedIndex == 0)
				markInvalid(this);
			else
				markValid(this);
		} else {
			if (this.selectedIndex == -1)
				markInvalid(this);
			else
				markValid(this);
		}

	}

	function validateCheckboxOnChange() {
		//get the array of checkboxes with the same name from the form object
		var checkboxGroupAsArray = this.form[this.name];

		var checked = checkboxGroupIsChecked(checkboxGroupAsArray);

		if (!checked)
			markCheckboxGroupInvalid(this);
		else
			markCheckboxGroupValid(this);
	}

	String.prototype.trim = function() {
		return this.replace(/^\s*/, "").replace(/\s*$/, "");
	}

	/* 
	 * This function validates a field using the pattern.  If the 
	 * field is invalid it will change the style and add an error span
	 * with a field appropriate error message.  
	 */
	function validateRegexOnChange() {
		var textfield = this;
		var pattern = textfield.getAttribute("pattern");
		var value = this.value;
		var required = textfield.getAttribute("required") != null;
		var valueEntered = (value != null) && (value.trim() != "");
		var valid = true;
		var isEmail = textfield.getAttribute("email") != null;

		// INVALID
		if (required || (valueEntered && isEmail)) {
			if (value.search(pattern) == -1)
				valid = false;
		}

		if (valid)
			markValid(textfield);
		else
			markInvalid(textfield);
	}

	function validateEmailOnChange() {
		var textfield = this;
		var email = this.value;
		var required = textfield.getAttribute("required") != null;
		var valueEntered = isValueNonEmpty(email);
		var valid = true;

		if (valueEntered) {
			valid = jcv_checkEmail(email);
		} else if (required) {
			valid = false;
		}

		applyValidityStyles(textfield, valid);
	}

	function validatePhoneNumberOnChange() {
		var textfield = this;
		var phoneNumber = this.value;
		var required = textfield.getAttribute("required") != null;
		var valueEntered = isValueNonEmpty(phoneNumber);
		var valid = true;

		if (required || valueEntered) {
			if (!checkInternationalPhone(phoneNumber))
				valid = false;
		}

		applyValidityStyles(textfield, valid);

	}

	/* Phone number util functions */

	function isInteger(s) {
		var i;
		for (i = 0; i < s.length; i++) {
			// Check that current character is number.
			var c = s.charAt(i);
			if (((c < "0") || (c > "9")))
				return false;
		}
		// All characters are numbers.
		return true;
	}

	function cleanUp(s) {
		var i;
		var returnString = "";
		var validCharsToRemove = "()- +";
		// Search through string's characters one by one.
		// If character is not in bag, append to returnString.
		for (i = 0; i < s.length; i++) {
			// Check that current character isn't whitespace.
			var c = s.charAt(i);
			if (validCharsToRemove.indexOf(c) == -1)
				returnString += c;
		}
		return returnString;
	}

	function checkInternationalPhone(strPhone) {

		s = cleanUp(strPhone);
		return (isInteger(s) && s.length >= 10);
	}

	function isValueNonEmpty(value) {
		return (value != null) && (value.trim() != "")
	}

	/* these methods help change the state, manage the error span, and error styles of 
	 * validated elements. 
	 */
	function markInvalid(input) {

		if (input.valid == true) {
			input.valid = false;
			input.className = "invalid";
			// create the errorSpan
			var errorSpan = document.createElement("span");
			var errorMessage = getErrorMessage(input);
			errorSpan.appendChild(document.createTextNode(errorMessage));

			// TO BE READYPORTAL WIDE APPLICABLE, THIS NEEDS TO BUILD THE ERROR
			// CLASS NAME
			// BY INTROSPECTING THE STYLE CLASS NAME OF THE ASSOCIATED FIELD
			errorSpan.className = "feature-formbuilder-error";
			input.parentNode.appendChild(errorSpan);
		}
	}

	/*
	 * This one is being added so I can call it from the captcha fields.  Ultimately, we need to intergrate
	 * Catpcha validation into the framework in general, both in the UI compiler stuff as well as this validation
	 * framework.
	 * 
	 * Here, I'm just passing in my own error message.
	 * 
	 */
	function markInvalidWithSpecificMessage(input, errorMessage) {

		if (input.valid == true) {
			input.valid = false;
			input.className = "invalid";
			// create the errorSpan
			var errorSpan = document.createElement("span");
			errorSpan.appendChild(document.createTextNode(errorMessage));

			// TO BE READYPORTAL WIDE APPLICABLE, THIS NEEDS TO BUILD THE ERROR
			// CLASS NAME
			// BY INTROSPECTING THE STYLE CLASS NAME OF THE ASSOCIATED FIELD
			errorSpan.className = "feature-formbuilder-error";
			input.parentNode.appendChild(errorSpan);
		}
	}
	 
	/*EXPORT THE PREVIOUS FUNCTION AS A PUBLIC FUNCTION */
	 
	window.Validate = {};
	window.Validate.markInvalid = markInvalidWithSpecificMessage;

	function markValid(input) {
		if (input.valid == false) {
			input.valid = true;
			input.className = "valid";
			// remove the error span
			var errorSpan = input.parentNode.lastChild;
			input.parentNode.removeChild(errorSpan);
		}
	}

	function markCheckboxGroupInvalid(input) {
		if (input.valid == true) {
			var checkboxGroup = input.form[input.name];

			// set error message on first one
			markInvalid(checkboxGroup[0]);

			// mark all as invalid
			for ( var i = 0; i < checkboxGroup.length; i++) {
				checkboxGroup[i].valid = false;
			}
		}
	}

	function markCheckboxGroupValid(input) {
		if (input.valid == false) {
			var checkboxGroup = input.form[input.name];
			markValid(checkboxGroup[0]);
			// mark all as valid
			for ( var i = 0; i < checkboxGroup.length; i++) {
				checkboxGroup[i].valid = true;
			}
		}
	}

	function getErrorMessage(input) {
		var required = input.getAttribute("required") != null;
		var email = input.getAttribute("email") != null;
		var phonenumber = input.getAttribute("phonenumber") != null;

		if (email)
			return "Enter a valid email address."
		if (phonenumber)
			return "Enter a valid phone number."
		if (required)
			return "Required field."
	}

	function checkboxGroupIsChecked(array) {
		var checked = false;
		for ( var i = 0; i < array.length; i++) {
			if (array[i].checked == true) {
				checked = true;
				break;
			}
		}
		return checked;
	}

	function applyValidityStyles(textfield, valid) {
		if (valid)
			markValid(textfield);
		else
			markInvalid(textfield);
	}

	/*
	 * This method allows us to add an onchange handler in a non-destructive fashion.  By
	 * default Javascript would write over the existing handler, but we will preserve the old one,
	 * and wrap it in a new anonymous function, with our validation code, and put it back
	 * on the handler.
	 */
	function addOnChangeHandler(element, handler) {
		//if an onchange already exists, create new wrapper function around both the old and the new 
		if (element.onchange) {
			element.existingHandler = element.onchange;
			element.validateMe = handler;
			element.onchange = function() {
				element.validateMe();
				element.existingHandler();
			}
		} else {
			element.onchange = handler;
		}
	}
	 
	 init();

});
