/*---------------------------------------------------------------------------------

Usage

For the field's name= value enter the following properties like this:

"Name;required/optional;datatype;min;max"

Name:		The name of the field

required/
optional:	Whether the field is required or optional.

Datatype:	Used for validation. currently supported values are:
			'email' - checks for name@company.com format
			'card' - checks for a valid credit card.
			'text' - checks for alpha chars and whitespace only
			'text-num' - checks for alpha chars, numbers, or whitespace
			'numeric' - checks for numbers only
			'number' - checks for a number (int or float) in the range min-max (see below)
			'alphanumeric' - checks for letters and numbers
			'file' - matches any valid filename (windows)
			'imagefile' - checks for a valid filename with an extension of .jpg, .jpeg or .gif
			'worddoc' - ditto for .doc
			'pdffile' - ditto for .pdf
			'zipfile' - ditto for .zip
			'nohtml' - checks that the input does not contain html.
			'postcode' - checks for valid australian postcode. you must include the optional postcodes.js
			'ausdate' - checks for a valid australian date. dd/mm/yy(yy) or dd-mm-yy(yy). If it doesn't find these it will attempt a date.parse to see if it is a date string.
			'notype' - don't check the datatype. Use this for checkboxes, radio buttons, select boxes etc.
			'noquotes' - allows anything except quotes

Min:		An int to specify an minimum length for the field. To make a field an exact length,
			make the max of the field equal to this value.
			If the datatype is 'number', checks that the number is greater than this.
			If it is a multi-select box, checks that at least this many selections are made.

Max:		An int to specify an maximum length for the field. A non-positive value means no max length.
			If the datatype is 'number', checks that the number is less than this.
			If it is a multi-select box, checks that less than this many selections are made.


Notes:
- If a select box is marked as required, it checks to see that the selected option has a value
other than "null". So if you are including options that are simply titles, separators or prompts
like 'Select an option...', make sure you explicitely set value="null" for those options.
- For multiple select boxes, you can use the min/max values to make sure a particular number
of options are selected. As for single selects, options are not considered selected if their value="null"


---------------------------------------------------------------------------------*/



function isCreditCard(cardNumber) {
  // Encoding only works on cards with less than 19 digits
  if (cardNumber.length > 19) return false;

  sum = 0; mul = 1; l = cardNumber.length;
  for (j = 0; j < l; j++) {
    digit = cardNumber.substring(l-j-1,l-j);
    tproduct = parseInt(digit ,10)*mul;
    if (tproduct >= 10) sum += (tproduct % 10) + 1;
    else sum += tproduct;
    if (mul == 1) mul++;
    else mul--;
  }

  return ((sum % 10) == 0);
}


var g_bError = false;
var g_iFirstError = -1;
var g_sError = 'Sorry, there were problems with your form:\n\n';
var g_bPostCodeValidatorIncluded;

function addError(sMessage, iPosition) {
	g_sError += sMessage;
	if (!g_bError) {
		g_iFirstError = iPosition;
		g_bError = true;
	}
}

function validateForm(oForm) {

	g_bError = false;
	g_iFirstError = -1;
	g_sError = 'Please ensure that you have entered the correct information \ninto the following form fields prior to sending the form:\n\n';

	var aValidatedRadios = new Array();


	// regular expressions
	var reValidEmail = /^.+\@.+\..+$/;

	var reWhitespace = /^\s+$/;
	var reInteger = /^\d+$/;
	var reAlphaOrSpace = /^[a-zA-Z\s]+$/;
	var reAlphanumeric = /^[a-zA-Z0-9]+$/;
	var reAlphaNumSpace = /^[a-zA-Z0-9\s]+$/;
	var reImagefile = /^[^\\\/:\*\?"<>\|]+\.(jpg|jpeg|gif)$/;
	var reFile = /^[^\\\/:\*\?\"<>\|]+$/;
	var reWordfile = /^[^\\\/:\*\?\"<>\|]+\.doc$/;
	var rePdfFile = /^[^\\\/:\*\?\"<>\|]+\.pdf$/;
	var reZipFile = /^[^\\\/:\*\?\"<>\|]+\.zip$/;
	var reHTML = /^.*(<.+>).*$/;
	var reQuotes = /[\'\"]/


	// iterate through the form
	for (i=0; i < oForm.elements.length; i++) {

		var bElemReq = false;
		var sElemType = '';
		var iElemMin = 0;
		var iElemMax = 0;
		var sElemName = '';

		oElem = oForm.elements[i];

		if ((oElem.type != 'button') && (oElem.type != 'submit') && (oElem.type != 'reset')) {

			// parse the field's name to find out the required properties
			aProperties = oForm.elements[i].name.split(';');

			sElemName = aProperties[0];
			bElemReq = (aProperties[1] == 'required') ? true : false;
			sElemType = (aProperties[2] != null) ? aProperties[2] : "notype";
			iElemMin = (aProperties[3] != null) ? Number(aProperties[3]) : 0;
			iElemMax = (aProperties[4] != null) ? Number(aProperties[4]) : 0;


			// test what type of form element we are working with
			switch(oElem.type) {

				case 'checkbox':
					if (bElemReq && !oElem.checked) {
						addError(sElemName + ' must be checked.\n', i);
					}
					break;

				case 'radio':
					if (bElemReq) {
						// make sure we haven't validated this radio button set already
						var bRadioValidated = false;
						for (j=0; j < aValidatedRadios.length; j++) {
							if (aValidatedRadios[j] == oElem.name) {
								bRadioValidated = true;
								break;
							}
						}
						if (!bRadioValidated) {
							var bRadioChecked = false;
							var aRadioValues = oForm.elements[oElem.name]; // get all radio buttons with the same name
							for (j=0; j < aRadioValues.length; j++) {
								if (aRadioValues[j].checked) {
									bRadioChecked = true;
									break;
								}
							}
							if (!bRadioChecked) {
								addError(sElemName + ' needs an option selected.\n', i);
							}
							//i = i + aRadioValues.length - 1; // skip the other elements in the radio group (assuming they are next to each other!!)
							aValidatedRadios.push(oElem.name) // add this set of radios to the list of validated radios
						}
					}
					break;

				case 'select-one':
					if (bElemReq) {
						var bSelection = false;
						if (oElem.selectedIndex != -1) {
							if (oElem.options[oElem.selectedIndex].text != "") {
								if (oElem.options[oElem.selectedIndex].value != "null") {
									bSelection = true;
								}
							}
						}
						if (!bSelection) {
							addError(sElemName + ' needs an option selected.\n', i);
						}
					}
					break;

				case 'select-multiple':
					var aSelections = new Array();
					for (j=0; j < oElem.options.length; j++) {
						if (oElem.options[j].selected) {
							if (oElem.options[j].text != "") {
								if (oElem.options[j].value != "null") {
									aSelections.push(oElem.options[j]);
								}
							}
						}
					}
					if (aSelections.length > 0) {
						if (aSelections.length < iElemMin) {
							addError(sElemName + ' needs at least ' + iElemMin + ' options selected, you selected ' + aSelections.length + '.\n', i);
						}
						if (aSelections.length > iElemMax) {
							addError(sElemName + ' can have a maximum of ' + iElemMax + ' options selected, you selected ' + aSelections.length + '.\n', i);
						}
					} else if (bElemReq) {
						addError(sElemName + ' needs at least ' + iElemMin + ' options selected.\n', i);
					}
					break;

				default:
					// it must be a textfield of some sort

					// test if the field has something in it
					if ((oElem.value.length > 0) && !reWhitespace.test(oElem.value))  {

						// test if the field is less than the minimum length (if there is one)
						if ((sElemType != "number") && (oElem.value.length < iElemMin)) {
							addError(sElemName + ' must be at least ' + iElemMin + ' characters long. You entered ' + oElem.value.length + '.\n', i);
						}


						// test if the field is more than the maximum length (if there is one)
						if ((sElemType != "number") && (iElemMax > 0) && (oElem.value.length > iElemMax)) {
							addError(sElemName + ' must be less than ' + iElemMax + ' characters. You entered ' + oElem.value.length + '.\n', i);
						}


						// test if the input matches the required datatype for the field
						switch (sElemType) {

							case "email":
								if (!reValidEmail.test(oElem.value)) {
									addError(sElemName + ' should be in the format \'name@company.com\'.\n', i);
								}
								break;

							case "card":
								if (!reInteger.test(oElem.value)) {
									addError(sElemName + ' should contain numbers only, without any spaces or dashes.\n', i);
								} else if (!isCreditCard(oElem.value)) {
									addError(sElemName + ' is not a valid credit card number.\n', i);
								}
								break;

							case "text":
								if (!reAlphaOrSpace.test(oElem.value)) {
									addError(sElemName + ' should contain letters only.\n', i);
								}
								break;

							case "text-num":
								if (!reAlphaNumSpace.test(oElem.value)) {
									addError(sElemName + ' should contain letters, numbers and whitespace only.\n', i);
								}
								break;

							case "numeric":
								if (!reInteger.test(oElem.value)) {
									addError(sElemName + ' should contain numerals only.\n', i);
								}
								break;

							case "number":
								oElem.value = oElem.value.replace('$', ''); // in case this is a price field, strip any $ signs
								var iNum = parseFloat(oElem.value);
								if (isNaN(iNum)) {
									addError(sElemName + ' should contain a number.\n', i);
								} else if ((iNum < iElemMin) || (iNum > iElemMax)) {
									addError(sElemName + ' should contain a number in the range ' + iElemMin + '-' + iElemMax + '.\n', i);
								}
								break;

							case "alphanumeric":
								if (!reAlphanumeric.test(oElem.value)) {
									addError(sElemName + ' should contain letters and numbers only.\n', i);
								}
								break;

							case "imagefile":
								var sFile = oElem.value.substr(oElem.value.lastIndexOf('\\') + 1);
								if (!reImagefile.test(sFile)) {
									addError(sElemName + ' should be a valid filename in JPG or GIF format (*.jpg, *.jpeg, or *.gif).\n', i);
								}
								break;

							case "file":
								if (!reFile.test(oElem.value)) {
									addError(sElemName + ' should be a valid filename (it cannot contain \\ / : * ? " < > or |).\n', i);
								}
								break;

							case "worddoc":
								if (!reWordfile.test(oElem.value)) {
									addError(sElemName + ' should be a valid filename in MS Word format (*.doc).\n', i);
								}
								break;

							case "pdffile":
								if (!rePdfFile.test(oElem.value)) {
									addError(sElemName + ' should be a valid filename in PDF format (*.pdf).\n', i);
								}
								break;

							case "zipfile":
								if (!reZipFile.test(oElem.value)) {
									addError(sElemName + ' should be a valid filename in ZIP format (*.zip).\n', i);
								}
								break;

							case "nohtml":
								if (reHTML.test(oElem.value)) {
									addError(sElemName + ' can not contain HTML code.\n', i);
								}
								break;

							case "postcode":
								if (!reInteger.test(oElem.value)) {
									addError(sElemName + ' should contain numerals only.\n', i);
								} else if (g_bPostCodeValidatorIncluded) {
									if (!isValidPostCode(oElem.value)) {
										addError(sElemName + ' is not a valid Australian postcode.\n', i);
									}
								}
								break;

							case "ausdate":
								var aDate = '', iDay, iMonth, iYear, oDate = '', bInvalid = false;
								if (oElem.value.indexOf('/') != -1) {
									aDate = oElem.value.split('/');
								} else if (oElem.value.indexOf('-') != -1) {
									aDate = oElem.value.split('-');
								}

								if (aDate == '') {
									//attempt to parse date as a string
									oDate = new Date(oElem.value);
									var sMonth = oDate.toString().substr(4,3).toLowerCase();
									if ((oElem.value.indexOf(oDate.getDate()) == -1) || (oElem.value.indexOf(oDate.getFullYear()) == -1) || (oElem.value.toLowerCase().indexOf(sMonth) == -1)) {
										bInvalid = true;
									} else {
										// it is valid, but fix up the date so it is in the expected format
										oElem.value = oDate.getDate() + '/' + (oDate.getMonth()+1) + '/' + oDate.getFullYear();
									}
								} else {
									//attempt to parse date as dd/mm/yy
									iDay = aDate[0];
									iMonth = aDate[1] - 1;
									iYear = aDate[2];

									if (iYear.length < 4) { // if year is only two digits, default to 20yy
										iYear = parseInt(iYear) + 2000;
									}

									oDate = new Date(iYear, iMonth, iDay);
									if ((parseInt(oDate.getDate()) != parseInt(iDay)) || (parseInt(oDate.getMonth()) != parseInt(iMonth)) || ((parseInt(oDate.getYear()) != parseInt(iYear)) && (parseInt(oDate.getFullYear()) != parseInt(iYear)))) {
										bInvalid = true;
									}
								}

								if (bInvalid) {
									addError(sElemName + ' should be in the format dd/mm/yy(yy), and must be a real date (eg. 30 Feb is invalid).\n', i);
								}

								break;

							case "noquotes":
								if (reQuotes.test(oElem.value)) {
									addError(sElemName + ' cannot contain single or double quotes.\n', i);
								}
								break;

							case "notype":
								// do nothing
								break;

							default:
								alert('Validation error: Unknown data type specified for ' + sElemName + ' field.');
						}

					} else if (bElemReq) {
						addError(sElemName + ' is required.\n', i);
					}

			} // end field type switch
		}
	}

	// If there is errors, alert the user, otherwise let the form submit
	if (g_bError) {
		alert(g_sError);
		oForm.elements[g_iFirstError].focus();
		oForm.elements[g_iFirstError].select();
		return false;

	} else {
		// make sure we rename the form elements coherently so as not to confuse the asp
		for (i=0; i < oForm.elements.length; i++) {
			oForm.elements[i].name = oForm.elements[i].name.split(';')[0];
			replaceChars(oForm.elements[i]);
		}
		return true;
	}

}
function replaceChars(control) {
out = "'"; // replace this
add = "`"; // with this
var temp = "" + control.value; // temporary holder

while (temp.indexOf(out)>-1) {
pos= temp.indexOf(out);
temp = "" + (temp.substring(0, pos) + add + 
temp.substring((pos + out.length), temp.length));
}
control.value = temp;
}
