/**
 * 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.  You can override the default validation error message
 * that is displayed by specifying the invalidMessage attribute on the control.
 * This is most useful for supplying instructions to the user about how to match
 * a pattern such as a phone number.  If the user left the field blank, it will
 * show "Required field.", but if they enter something that doesn't match the
 * validation pattern, they will get the more specific message. 
 * 
 * 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 old handler 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];
			// JQuery's date picker monopolizes the onchange function so we can
			// not use the onchange function as a way to determine if this is a
			// date field
			var isDate = e.getAttribute("date") != null;
			if (((e.type == "text" || e.type == "textarea") && (e.onchange == validateRegexOnChange
					|| e.onchange == validatePhoneNumberOnChange || e.onchange == validateEmailOnChange
					|| isDate))
					|| ((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 date = textbox.getAttribute("date") != 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 (date) {
			addOnChangeHandler(textbox, validateDateOnChange);
		}
		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;

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

		applyValidityStyles(textfield, valid);
	}

	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);

	}
	
	function validateDateOnChange() {
		var textfield = this;
		var dateText = this.value;
		var required = textfield.getAttribute("required") != null;
		var valueEntered = isValueNonEmpty(dateText);
		var valid = true;
		
		if (valueEntered) { 
			try {
				// Note this doesn't fail with an exception if dateText is empty,
				// so the exception isn't sufficient to valdate that the value was
				// given.
				jQuery.datepicker.parseDate('mm/dd/yy', dateText);
			} catch (e) {
				valid = false;
			}
		} else if (required) {
			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];


            //this can be either an array or a single box
			if (checkboxGroup.length == undefined)
            {
                // set error message on first one
                markInvalid(checkboxGroup);
                checkboxGroup.valid = false;

            }
            else
            {
                // 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) {
		var checkboxGroup = input.form[input.name];

        //we need to check if we have or many checkboxes
        if (checkboxGroup.length == undefined)
        {
            markValid(checkboxGroup);
            checkboxGroup.valid = true;
        }
        else
        {
            if (input.valid == false) {
                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 date = input.getAttribute("date") != null;
		var invalidMessage = input.getAttribute("invalidMessage") != null;
		var phonenumber = input.getAttribute("phonenumber") != null;
		var valueEntered = isValueNonEmpty(input.value);

		if (required && !valueEntered)
			return "Required field.";
		else if (invalidMessage)
			return input.getAttribute("invalidMessage");
		else if (email)
			return "Enter a valid email address.";
		else if (phonenumber)
			return "Enter a valid phone number.";
		else if (date)
			return "Enter a valid date (mm/dd/yyyy).";
		else
			return "Required field.";
	}

	function checkboxGroupIsChecked(checkboxs) {
		//we need to check if we have or many checkboxes
        if (checkboxs.length == undefined)
        {
            return checkboxs.checked == true;
        }
        else
        {
            var checked = false;
            for ( var i = 0; i < checkboxs.length; i++) {
                if (checkboxs[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();

});

