/***********************************************/
/* Script: validator.js                        */
/* Author: Jim Salyer                          */
/***********************************************/

// global variables
var i, j;
var valForms = new Array();
var bgColorNorm = "white";
var bgColorError = "red";
var fgColorNorm = "black";
var fgColorError = "white";
var countBgColor = "gray";
var countFgColor = "white";
var doUpload = false;

//----SETUP FUNCTIONS----
function getObj(objName)
{
	if (document.getElementById)
		return document.getElementById(objName);
	else if (document.all)
		return document.all[objName];
}

function CheckForm(frmName, frmSubmitName)
{
	// set up the object's properties and methods
	this.index = valForms.length;
	this.name = frmName;
	//this.obj = document.forms[frmName];
	this.obj = document.forms[frmName];
	this.btnSubmit = this.obj.elements[frmSubmitName];
	//this.btnSubmit = getObj(frmSubmitName);
	this.dynFields = new Array();
	this.fields = new Array();
	this.addDynField = DynField;
	this.addField = Field;
	this.flag = (!this.obj || !this.btnSubmit);
	
	if (this.flag) return; // get out if there isn't a valid form or submit button present
	
	// set up the validate function for the form's submit button
	if (frmSubmitName.toLowerCase().indexOf("delete") != -1)
		this.btnSubmit.onclick = new Function("validate('" + frmName + "', true);");
	else
		this.btnSubmit.onclick = new Function("validate('" + frmName + "', false);");
		
	// loop through the form's elements and set their colors
	var fld = null;
	for (i=0; i<this.obj.elements.length; i++)
	{
		fld = this.obj.elements[i];
		if (fld.type != "button" && fld.type != "checkbox" && fld.type != "hidden" && fld.type != "radio" && fld.type != "reset" && fld.type != "submit" && fld.style)
		{
			fld.style.backgroundColor = bgColorNorm;
			fld.style.color = fgColorNorm;
		}
	}
}

function DynField(fldName, fldCounterName, fldMaxLimit, fldDisplay)
{
	// handle a non-existent field
	if (this.flag) return;
	if (!valForms[this.index].obj.elements[fldName])
	{
		this.dynFields[this.dynFields.length] = new Object();
		return;
	}
	
	// set up the object's properties and methods
	var dynField = new Object();
	dynField.index = this.dynFields.length;
	dynField.name = fldName;
	dynField.obj = valForms[this.index].obj.elements[fldName];
	dynField.counter = valForms[this.index].obj.elements[fldCounterName];
	dynField.maxLimit = fldMaxLimit;
	dynField.display = fldDisplay;
	dynField.count = 0;
	dynField.value = "";
	
	// set up the object's event handler
	dynField.obj.onkeyup = function(event)
	{
		checkLength(this.name, event);
	};
	dynField.counter.onfocus = new Function("document." + this.name + "." + fldName + ".focus();");
	
	// set up the object's initial value
	dynField.counter.value = dynField.count = fldMaxLimit - dynField.obj.value.length;
	dynField.counter.onkeyup = function()
	{
		var fldItem = getCountField(this.name);
		this.value = fldItem.maxLimit - fldItem.obj.value.length;
	};
	if (dynField.counter.style)
	{
		dynField.counter.style.backgroundColor = countBgColor;
		dynField.counter.style.color = countFgColor;
	}
	this.dynFields[dynField.index] = dynField;
}

function Field(fldName, fldFormat, fldDisplay)
{
	// set up the object's properties and methods
	if (this.flag) return;
	var field = new Object();
	field.index = this.fields.length;
	field.name = fldName;
	field.format = fldFormat;
	field.display = fldDisplay;
	this.fields[field.index] = field;
}

function loadValidator(isSecure)
{
	// load the page securely (if necessary)
	if (isSecure && location.protocol != "https:")
	{
		var loc = location.href.replace(/http:/i, "https:");
		location.href = loc;
	}
	defineFields();
}

//----OBJECT RETRIEVAL FUNCTIONS----
function getForm(frmName)
{
	for (i=0; i<valForms.length; i++)
	{
		if (valForms[i].name == frmName)
			return valForms[i];
	}
	return null;
}

function getDynField(fldName)
{
	for (i=0; i<valForms.length; i++)
	{
		for (j=0; j<valForms[i].dynFields.length; j++)
		{
			if (valForms[i].dynFields[j].name == fldName)
				return valForms[i].dynFields[j];
		}
	}
	return null;
}

function getCountField(fldName)
{
	for (i=0; i<valForms.length; i++)
	{
		for (j=0; j<valForms[i].dynFields.length; j++)
		{
			if (valForms[i].dynFields[j].counter.name == fldName)
				return valForms[i].dynFields[j];
		}
	}
	return null;
}

function getField(fldName)
{
	for (i=0; i<valForms.length; i++)
	{
		for (j=0; j<valForms[i].fields.length; j++)
		{
			if (valForms[i].fields[j].name == fldName)
				return valForms[i].fields[j];
		}
	}
	return null;
}

function getValue(arr, str)
{
	var val = "";
	
	// get the value of the specified array item
	for (var i=0; i<arr.length; i++)
	{
		if (arr[i].indexOf(str) != -1)
		{
			val = arr[i].split("=")[1];
			return val;
		}
	}
	return val;
}

function toggleBackground(fld, isError)
{
	// toggle the given field's background color
	if (fld.type == "checkbox" || fld.type == "radio" || fld.type == "hidden") return;
	if (isError && fld.style)
	{
		fld.style.backgroundColor = bgColorError;
		fld.style.color = fgColorError;
	}
	else if (!isError && fld.style)
	{
		fld.style.backgroundColor = bgColorNorm;
		fld.style.color = fgColorNorm;
	}
}

//----DYNAMIC LENGTH CHECKING FUNCTIONS----
function isSpecial(key)
{
	// see if the key given is a special (non-character) key
	var specKeys = [8, 35, 36, 37, 38, 39, 40, 46];
	for (i=0; i<specKeys.length; i++)
		if (key == specKeys[i])
			return true;
	return false;
}

function checkLength(fldName, evt)
{
	// get the key that was pressed
	var keyPressed;
	if (window.event)
		keyPressed = event.keyCode;
	else
		keyPressed = evt.which;
		
	// derive the appropriate length checking and displaying fields and values
	var fldItem = getDynField(fldName);
	var fld = fldItem.obj;
	var fldCounter = fldItem.counter;
	var maxLimit = fldItem.maxLimit;
	var display = fldItem.display;
	
	// check the field's value length
	if (fld.value.length >= maxLimit && !isSpecial(keyPressed))
	{
		fldCounter.focus();
		alert('You have reached the maximum length allowed for ' + display + '.\nClick at the end of the text or somewhere in the text and use the backspace key to edit.');
		if (fldItem.count == 0)
		{
			fld.value = fld.value.substring(maxLimit, 0);
			fldItem.value = fld.value;
			fldItem.count = 1;
		}
		else
			fld.value = fldItem.value;
		fldCounter.value = maxLimit - fld.value.length;
	}
	else
	{
		fldItem.count = 0
		fldCounter.value = maxLimit - fld.value.length;
	}
}

//----CREDIT CARD CHECKING FUNCTIONS----
function getDigitsOnly(s)
{
	// strip the digits out of the credit card number
	var digitsOnly = "";
	var c;
	for (var i=0; i<s.length; i++)
	{
		c = s.charAt(i);
		if (!isNaN(c) && c != " ")
			digitsOnly += c;
	}
	return digitsOnly;
}

function luhnCheck(fld)
{
	var cardNumber = getDigitsOnly(fld.value);
	var cardNumberLength = cardNumber.length;
	var product, productIndex;
	var sum = 0;
	var check = false;
	
	// check the given credit card number using the Lugn algorithm
	for (var c=cardNumberLength-1; c>0; c--)
	{
		sum += parseInt(cardNumber.charAt(c));
		c--;
		
		product = new String((cardNumber.charAt(c) * 2));
		for (var pc=0; pc<product.length; pc++)
			sum += parseInt(product.charAt(pc));
	}
	check = (sum % 10 == 0);
	
	if (check)
	{
		fld.value = cardNumber;
		return true;
	}
	return false;
}

//----THE MAIN VALIDATION FUNCTION----
function validate(frmName, doConfirm)
{
	// working variables
	var i, j;
	var frmItem, frm;
	var fld, fldItem;
	var hasErrors = false, doCheck = false;
	var message = "There were errors on the form:\n";
	var checks;
	var pattern;
	
	frmItem = getForm(frmName);
	frm = frmItem.obj;
	
	// only check the fields defined in the fields array
	for (i=0; i<frmItem.fields.length; i++)
	{
		// get the field and trim its value of any leading or trailing spaces
		fldItem = frmItem.fields[i];
		if (!frm.elements[fldItem.name]) continue;
		fld = frm.elements[fldItem.name];
		doCheck = false;
		if (fld.value)
		{
			tempValue = fld.value;
			pattern = /^(\s*)$/;
		
		  // check for all spaces
		  if (pattern.test(tempValue))
			{
		    tempValue = tempValue.replace(pattern, '');
		    if (tempValue.length == 0) fld.value = tempValue;
		  }
		    
		  // check for leading and trailing spaces
		  pattern = /^(\s*)([\W\w]*)(\b\s*$)/;
		  if (pattern.test(tempValue))
		     tempValue = tempValue.replace(pattern, '$2');
		 	fld.value = tempValue;
			
			// handle leading and trailing spaces in a textarea
			tempValue = escape(fld.value);
			pattern = /^(%20)*([\W\w]*)(%20$)*/;
			if (pattern.test(tempValue))
				tempValue = tempValue.replace(pattern, '$2');
			fld.value = unescape(tempValue);
		}
		
		// check the field for being required
		if (fldItem.format.indexOf("req") != -1)
		{
			// check for a value in the field
			doCheck = true;
			if (fld.length && !fld.type)
			{
				// validate an array of check boxes with the same name
				var isChecked = false;
				for (j=0; j<fld.length; j++)
				{
					if (fld[j].checked)
					{
						isChecked = true;
						break;
					}
				}
				if (!isChecked)
				{
					doCheck = false;
					hasErrors = true;
					message += "\nYou must select an item for " + fldItem.display + ".";
					toggleBackground(fld, true);
				}
				else
					toggleBackground(fld, false);
			}
			else if ((fld.type == "checkbox" && !fld.checked) || fld.value == "" || (!fld.value && fld.options[fld.selectedIndex].value == ""))
			{
				doCheck = false;
				hasErrors = true;
				message += "\nYou must enter a value in " + fldItem.display + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		else if (fld.value != "") 
			doCheck = true;
		else if (fld.value == "")
			toggleBackground(fld, false);
		
		checks = fldItem.format.split(","); // get the field's formatting options
		if (doCheck && fldItem.format.indexOf("comp") != -1)
		{
			// compare two fields for the same value
			var cmpName = getValue(checks, "comp");
			chkItem = getField(cmpName);
			chk = frm.elements[chkItem.name];
			
			if (chkItem)
			{
				if (fld.value != chk.value)
				{
					doCheck = false;
					hasErrors = true;
					message += "\nThe fields " + fldItem.display + " and " + chkItem.display + " must contain the same value.";
					toggleBackground(fld, true);
				}
				else
					toggleBackground(fld, false);
			}
		}
		if (fldItem.format.indexOf("dep") != -1)
		{
			// if one field contains a value, this one becomes required
			var cmpName = getValue(checks, "dep");
			chkItem = getField(cmpName);
			chk = frm.elements[chkItem.name];
			
			if (chk.value.length > 0)
			{
				if (fld.value.length == 0)
				{
					doCheck = false;
					hasErrors = true;
					message += "\nYou must enter a value in " + fldItem.display + " since " + chkItem.display + " contains a value.";
					toggleBackground(fld, true);
				}
				else
					toggleBackground(fld, false);
			}
		}
		if (doCheck && fldItem.format.indexOf("numeric") != -1)
		{
			// check for a numeric value
			if (isNaN(fld.value))
			{
				doCheck = false;
				hasErrors = true;
				message += "\nYou have entered a non-numeric value in " + fldItem.display + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("alphanum") != -1)
		{
			// check for an alphanumeric value
			pattern = /\W/g;
			if (pattern.test(fld.value))
			{
				doCheck = false;
				hasErrors = true;
				message += "\nThe value of " + fldItem.display + " must only contain alphanumeric characters.";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("intrange") != -1)
		{
			// check for an integer range value
			var bounds = getValue(checks, "intrange").split("-");
			var num = parseInt(fld.value);
			if (num < bounds[0] || num > bounds[1])
			{
				doCheck = false;
				hasErrors = true;
				message += "\nThe value of " + fldItem.display + " must be between " + bounds[0] + " and " + bounds[1] + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("decrange") != -1)
		{
			// check for a decimal range value
			var bounds = getValue(checks, "decrange").split("-");
			var num = parseFloat(fld.value);
			if (num < bounds[0] || num > bounds[1])
			{
				doCheck = false;
				hasErrors = true;
				message += "\nThe value of " + fldItem.display + " must be between " + bounds[0] + " and " + bounds[1] + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("daterange") != -1)
		{
			// check for a date range value
			var bounds = getValue(checks, "daterange").split("-");
			var valDate = Date.parse(fld.value);
			var minDate = Date.parse(bounds[0]);
			var maxDate = Date.parse(bounds[1]);
			if (valDate < minDate || valDate > maxDate)
			{
				doCheck = false;
				hasErrors = true;
				message += "\nThe value of " + fldItem.display + " must be between " + bounds[0] + " and " + bounds[1] + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("minlen") != -1)
		{
			// check for a minimum length
			if (fld.value.length < getValue(checks, "minlen"))
			{
				doCheck = false;
				hasErrors = true;
				message += "\nThe length of " + fldItem.display + " must be greater than or equal to " + getValue(checks, "minlen") + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("maxlen") != -1)
		{
			// check for a maximum length
			if (fld.value.length > getValue(checks, "maxlen"))
			{
				doCheck = false;
				hasErrors = true;
				message += "\nThe length of " +  fldItem.display + " must be less than or equal to " + getValue(checks, "maxlen") + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("nowhite") != -1)
		{
			// check for whitespace characters
			pattern = /\s/g;
			if (pattern.test(fld.value))
			{
				doCheck = false;
				hasErrors = true;
				message += "\n" + fldItem.display + " must not contain whitespace characters.";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("ccdate") != -1)
		{
			// identify the separator character within the given date
			var sep;
			if (fld.value.indexOf("/") != -1)
				sep = "/";
			else if (fld.value.indexOf("-") != -1)
				sep = "-";
				
			// split the date into its parts
			var dateParts = fld.value.split(sep);
			var checkDate = new Date(dateParts[0] + "/1/" + dateParts[1]);
			var currDate = new Date();
			var isOldDate = (checkDate.getFullYear() < currDate.getFullYear() || (checkDate.getFullYear() >= currDate.getFullYear() && checkDate.getMonth() < currDate.getMonth()));
			
			// if there are date errors, process accordingly
			if (isOldDate || dateParts.length != 2 || dateParts[0] < 1 || dateParts[0] > 12)
			{
				doCheck = false;
				hasErrors = true;
				message += "\nAn invalid expiration date has been entered in " + fldItem.display + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		else if (doCheck && fldItem.format.indexOf("date") != -1)
		{
			// format the field appropriately before validating it
			fld.value = fld.value.replace(/(\d)(th|st|rd)/gi, "$1");
			fld.value = fld.value.replace(/,/g, "");
			fld.value = fld.value.replace(/-/g, "/");
			
			// identify the separator character within the given date
			var sep;
			if (fld.value.indexOf("/") != -1) 
				sep = "/";
			else if (fld.value.indexOf(" ") != -1)
				sep = " ";
			
			// get a derived date for validation
			var checkDate = new Date(fld.value);
			var derivedDate = checkDate.getDate();
			var dateParts = fld.value.split(sep);
						
			// if there are date errors, process accordingly
			if (dateParts.length != 3 || derivedDate != dateParts[1])
			{
				doCheck = false;
				hasErrors = true;
				message += "\nAn invalid date has been entered in " + fldItem.display + ".";
				toggleBackground(fld, true);
			}
			else
			{
				fld.value = (checkDate.getMonth() + 1) + "/" + checkDate.getDate() + "/" + checkDate.getFullYear();
				toggleBackground(fld, false);
			}
		}
		if (doCheck && fldItem.format.indexOf("time") != -1)
		{
			// check for valid time
			if (fldItem.format.indexOf("miltime") != -1)
				pattern = /^(0?[1-9]|1[0-9]|2[0-4]):([0-5]\d)$/i;
			else
				pattern = /^(0?[1-9]|1[0-2]):([0-5]\d) ?(am|pm)?$/i;
			if (!pattern.test(fld.value))
			{
				doCheck = false;
				hasErrors = true;
				message += "\nYou have entered an invalid time in " + fldItem.display + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("ccnum") != -1)
		{
			// check for a valid credit card number
			var isValid = luhnCheck(fld);
			if (!isValid)
			{
				doCheck = false;
				hasErrors = true;
				message += "\nYou have entered an invalid credit card number in " + fldItem.display + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("email") != -1)
		{
			// check for a valid e-mail address
			pattern = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*\.(\w{2}|(com|net|org|edu|int|mil|gov|arpa|biz|aero|name|coop|info|pro|museum))$/i;
			if (!pattern.test(fld.value))
			{
				doCheck = false;
				hasErrors = true;
				message += "\nAn invalid e-mail address has been entered in " + fldItem.display + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("ssn") != -1)
		{
			// check for a valid social security number
			fld.value = getDigitsOnly(fld.value);
			pattern = /^\d{9}$/;
			if (!pattern.test(fld.value))
			{
				doCheck = false;
				hasErrors = true;
				message += "\n You have entered an invalid social security number in " + fldItem.display + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("usphone") != -1)
		{
			// check for a valid US phone number
			fld.value = getDigitsOnly(fld.value);
			pattern = /^\d{10}$/;
			if (!pattern.test(fld.value)) 
			{
				doCheck = false;
				hasErrors = true;
				message += "\nYou have entered an invalid US phone number in " + fldItem.display + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("intphone") != -1)
		{
			// check for a valid international phone number
			fld.value = getDigitsOnly(fld.value);
			pattern = /^\d(\d|-){7,20}/;
			if (!pattern.test(fld.value))
			{
				doCheck = false;
				hasErrors = true;
				message += "\nYou have entered an invalid international phone number in " + fldItem.display + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("uszip") != -1)
		{
			// check for a valid US zip code
			pattern = /^\d{5}(-\d{4})?$/i;
			if (!pattern.test(fld.value))
			{
				doCheck = false;
				hasErrors = true;
				message += "\nYou have entered an invalid US zip code in " + fldItem.display + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("ukzip") != -1)
		{
			// check for a valid UK zip code
			pattern = /^[a-z]{1,2}[\da-z]{1,2} ?\d[a-z][a-z]$/i;
			if (!pattern.test(fld.value))
			{
				doCheck = false;
				hasErrors = true;
				message += "\nYou have entered an invalid UK zip code in " + fldItem.display + ".";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
		if (doCheck && fldItem.format.indexOf("ext") != -1)
		{
			// check for a valid file extension
			//var exts = "bin|cad|gif|gz|hqx|jpg|jpeg|lgz|pdf|png|svg|swf|tar|zip";
			var exts = getValue(checks, "exts");
			var fileParts = fld.value.split(".");
			if (fileParts.length < 2 || exts.indexOf(fileParts[fileParts.length - 1]) == -1)
			{
				doCheck = false;
				hasErrors = true;
				message += "\nThe file you entered in " + fldItem.display + " has an invalid extension.";
				toggleBackground(fld, true);
			}
			else
				toggleBackground(fld, false);
		}
	}
	
	// don't submit and display a message if errors were found
	if (hasErrors)
		alert(message);
	else
	{
		// handle submitting special characters and submit
		for (i=0; i<frmItem.fields.length; i++)
		{
			if (!frm.elements[frmItem.fields[i].name]) continue;
			fld = frm.elements[frmItem.fields[i].name];
			if (fld.type == "file") doUpload = true;
			if (fld.value)
			{
				fld.value = fld.value.replace(/'/g, "'");
				fld.value = fld.value.replace(/"/g, "&quot;");
			}
		}
		if ((doConfirm && confirm("Press OK to process deletion.")) || 
			!doConfirm)
		{
			if (doUpload) openUploadDialog();
			frm.submit();
			//alert("The form submitted successfully!");
		}
	}
}

function defineFields() {}
