/* documentation at bottom of this document */

	addLoadEvent(initializeValidation);		
	
	// create global variables used for validation on this page
		 var dataTypes = new Array();
		 var disallowedCharacters = new Array();
		 var patterns = new Array();
		 var existingStyles = new Array();
		 var sysmsg="";
		 var msg="";
		 var warningMessage = "";
		 var componentMessage = "";
		 var aSynchFlag=true;
		 var currFieldRef;
		 
	function initializeValidation()
		{
		 addValidation();  // add validation to the forms and preserve existing onsubmits
		 setUpVariables(); // set up all the pattern values, etc
		}
	
	function validateThisForm(formReference)
		{
			var formReference=this;
			
			msg="";
			warningMessage="";
			componentMessage="";
			
			//get rid of all flags and restore styles
			clearErrorMessages(formReference);
			
			//so we can restore it after it validates
			getCurrentStyle(formReference);
			
			/* Go through the form.  Hidden fields with name="required" can each have the following attributes: 
			
			   1.  value - the name of the field on the form that is required
			   2.  class (optional) - the type of data required, which can be:
			   		a. anything - any non-blank value
			   		b. url - a valid URL
			   		c. email - a valid email address
			   		d. integer - an integer 
			   		e. parmlist - a URI parameter list
			   		f. s1url - a valid S1URL value for Cold Fusion e.g. #local.s1vrsnurl#/blahblah
			   		g. uid - a valid UID xxnnne   where x is a letter, n is a numeric, and e is either a letter or a number
			   		h. dt - mm/dd/yyyy date; does not check e.g. Feb 30
			   		i. regexp - a user-defined regular expression
			   3.  title - the message to appear if the field fails validation
			   4.  lang - if class=regexp, the regular expression, without / and /
			*/
			   		
			// go through each element on the form...
			for(var i=0;i<formReference.elements.length;i++)
				{	
					// if the field is a stopvalidation field, stop all validation for this case
					if(formReference.elements[i].name && (formReference.elements[i].name.toLowerCase()=="stopvalidation"))
						{
							break;
						}
						
					// if its name is "required" or "optional" it will tell us about a field on the form that's required....
					// send this validation element to the validateSingleField function
					if(formReference.elements[i].name && (formReference.elements[i].name.toLowerCase()=="required" || formReference.elements[i].name.toLowerCase()=="optional"))
						{ 				
							msg+=validateSingleField(formReference.elements[i]);
						}	
						
					// to ensure that 'at least' n number of a given list have to have some non-blank value
					else if(formReference.elements[i].name && (formReference.elements[i].name.toLowerCase()=="atleast"))
						{	var howMany, fieldArray, totalMatches=0;
							if(formReference.elements[i].lang)
								{
									howMany = formReference.elements[i].lang;
								}
							else 
								{
									howMany = 1;
								}
							if (formReference.elements[i].value)
								{
									fieldArray = formReference.elements[i].value.split(",");
								}
							for (var j=0;j<fieldArray.length;j++)
								{
									if(!formReference.elements[fieldArray[j]])
										{
											sysmsg += "System: Field " + formReference.elements[j].value + " not defined for at least clause \n";
										}
									else
										{	
											if (!formReference.elements[fieldArray[j]].value)
												{
													var valueToMatch=arrayToList(formReference.elements[fieldArray[j]]);	
												}
											else 
												{
													var valueToMatch = formReference.elements[fieldArray[j]].value;
												}
											if (valueToMatch.match(/\S/) != null)
												{
													totalMatches++;													
												}
										}
								}
							
							if (totalMatches < formReference.elements[i].lang)
								{
									msg += formReference.elements[i].title + "\n";
									for (var j=0;j<fieldArray.length;j++)
										{	
											insertWarning(formReference.elements[fieldArray[j]], "error");
										}
								}
						}		
						
					// group inputs into classes; this says inputs from at least x classes must be chosen (default 1)
					else if(formReference.elements[i].name && (formReference.elements[i].name.toLowerCase()=="atleastbyclass"))
						{	var howMany, fieldClassArray, totalMatches=0;
							if(formReference.elements[i].lang)
								{
									howMany = formReference.elements[i].lang;
								}
							else 
								{
									howMany = 1;
								}
							if (formReference.elements[i].value)
								{
									fieldClassArray = formReference.elements[i].value.split(",");
								}
							for (var j=0;j<fieldClassArray.length;j++)
								{
									if(getElementsByClassName(formReference, "input", fieldClassArray[j]).length == 0)
										{
											sysmsg += "System: Field " + formReference.elements[j].value + " not defined \n";
										}
									else
										{	
											var valueToMatch=arrayToList(getElementsByClassName(formReference, "input", fieldClassArray[j]));																							
											if (valueToMatch.match(/\S/) != null)
												{
													totalMatches++;													
												}
										}
								}
							
							if (totalMatches < formReference.elements[i].lang)
								{
									for (z=0;z<fieldClassArray.length;z++)
										{
											fieldsToMark = getElementsByClassName(formReference,"input",fieldClassArray[z]);
											for (fieldRef=0;fieldRef<fieldsToMark.length;fieldRef++)
												{
													insertWarning(fieldsToMark[fieldRef], "error");
												}
										}
									msg += formReference.elements[i].title + "\n";									
								}
						}								
						
					// now check for fields that are mutually exclusive					
					else if(formReference.elements[i].name && (formReference.elements[i].name.toLowerCase()=="exclusive"))
						{	
							var mutuallyExclusiveFields = new Array();
							var mutuallyExclusiveFieldsChecked = new Array();
							if(formReference.elements[i].value)
								{
									mutuallyExclusiveFields = formReference.elements[i].value.split(",");
								}
							for (var j=0;j<mutuallyExclusiveFields.length;j++)
								{
									var fieldsInClass = getElementsByClassName(formReference, "input", mutuallyExclusiveFields[j]);
									mutuallyExclusiveFieldsChecked[j] = false;
									for (k=0;k<fieldsInClass.length;k++)
										{
										 	if (fieldsInClass[k].checked || (fieldsInClass[k].type != "checkbox" && fieldsInClass[k].type != "radio" && fieldsInClass[k].value && fieldsInClass[k].value!=""))
												{	
													mutuallyExclusiveFieldsChecked[j] = true;
												}
										}
								}
							
						
							// now loop through and see which set has elements checked
							var totalChecked = 0;
							for (var x=0; x<mutuallyExclusiveFieldsChecked.length; x++)
								{
									if (mutuallyExclusiveFieldsChecked[x] == true)
										{
											totalChecked++
										}
								}
							if (totalChecked > 1)
								{									
									if(formReference.elements[i].title)
										{
											msg += formReference.elements[i].title + "\n";											
										}
									else
										{
											msg += "Mutually exclusive fields chosen \n";
										}
									for (y=0;y<mutuallyExclusiveFields.length;y++)
										{
											fieldsToAlert=getElementsByClassName(formReference, "input", mutuallyExclusiveFields[y]);															
											for (z=0;z<fieldsToAlert.length;z++)
												{
													insertWarning(fieldsToAlert[z],"error","#");																	
												}
										}
								}
						}	
						
						
						
					// conditional logic, e.g. if (fieldX matches regexp1 and fieldY matches regexp2) OR (field z matches regexp3) 
					// then validate field A according to rule M
					else if(formReference.elements[i].name && (formReference.elements[i].name.toLowerCase()=="conditional"))
						{	
							if(formReference.elements[i].getAttribute('conditionalfield') == false || formReference.elements[i].getAttribute('conditionalvalue') == false )
								{
									sysmsg += "System: Conditional field or conditional value not defined \n";								
								}
							else
								{
									conditionalFieldsOR = formReference.elements[i].getAttribute('conditionalfields').split("|");
									conditionalValuesOR = formReference.elements[i].getAttribute('conditionalvalues').split("|");	
																	
									conditionalResults=new Array();								
																	
									if (conditionalFieldsOR[0] == "" || conditionalValuesOR[0] == "" || conditionalValuesOR.length != conditionalFieldsOR.length)
										{
											sysmsg += "System: Conditional fields or conditional values out of synch or don't have values \n";
										}
									else
										{
											for(j=0;j<conditionalFieldsOR.length;j++)
												{
													conditionalFieldSegmentAND = conditionalFieldsOR[j].split("&");
													conditionalValueSegmentAND = conditionalValuesOR[j].split("&");
													if (conditionalFieldSegmentAND[0] == "" || conditionalValueSegmentAND[0] == "" || conditionalFieldSegmentAND.length != conditionalValueSegmentAND.length)
															{
																sysmsg += "System: Conditional fields AND condition " + j +  " is out of synch \n";
															}
													else
															{
																var meetsANDcondition = true;
																for (k=0;k<conditionalFieldSegmentAND.length;k++)
																	{
																		if(formReference.elements[conditionalFieldSegmentAND[k]])
																			{
																				var valueToMatch;
																				
																				valueToMatch = getFieldValue(formReference.elements[conditionalFieldSegmentAND[k]]);
																					
																				//alert("matching " + valueToMatch + " and " + conditionalValueSegmentAND[k]);
																				matchExpression = new RegExp(conditionalValueSegmentAND[k]);
																				if(valueToMatch.match(matchExpression) == null)
																					{
																						meetsANDcondition = false;
																					}
																				// now set the result for this OR condition in the array
																				conditionalResults[j] = meetsANDcondition;
																			}
																		else
																			{
																				sysmsg += "System: Conditional fields AND field " + conditionalFieldSegmentAND[k] +  " not found \n";
																			}
																	}
																																	
																var meetsAnyCondition = false;																	
															}
														
												}
											
											
											for (var x=0;x<conditionalResults.length;x++)
												{
													if (conditionalResults[x] == true)
														{
															meetsAnyCondition = true;
														}
												}
											
											if(meetsAnyCondition)
												{	
													msg += validateSingleField(formReference.elements[i]);													
												}
										}							
								}							
						}												
					
					
					
					// no matter what the field function, check for disallowed character control
					for(var x=0;x<disallowedCharacters.length;x++)		
						{	
							if(formReference.elements[i].getAttribute(disallowedCharacters[x][0]) && 
							   formReference.elements[i].getAttribute(disallowedCharacters[x][0]) == disallowedCharacters[x][1])	
							{
								if(formReference.elements[i].value.match(disallowedCharacters[x][2]) != null)
							 	 	{
								 	 insertWarning(formReference.elements[i],disallowedCharacters[x][0],disallowedCharacters[x][4]);
								 	 msg += disallowedCharacters[x][3] + "\n";											 	
							 	 	}
				 	 		}
		 	 			}
					}
				
			if (msg && msg != "")
				{	
					alert(msg);
					warningMessage=msg;
					return false;
				}
			else if (sysmsg && sysmsg != "")
				{
					alert(sysmsg);
					return true;				
				}		
			
			return true;
		}

		

	function addValidation()
		{
		 var existingFunction;
		 /* add validation to all forms in the application */
		 for(var i=0;i<document.forms.length;i++)
			{
			 addEvent(document.forms[i], "submit", validateThisForm);
			}
		}	
	
		
	function clearErrorMessages(formReference)
		{
		 //error flags are in a span with a class called autoError...get rid of all of them
		 var nodes=getElementsByClassName(formReference, "span", "autoError")
		 restoreStyles(formReference);
		 
		 for(var i=0; i<nodes.length; i++)
		 	{
			 nodes[i].parentNode.removeChild(nodes[i]);
			}
		}
		
			
	function insertWarning(formField, level, symbol, suppressDOMAdditions)
		{
		  // The level field is currently not used.  Can be used to e.g. print different color warning
		  //  for severe vs warning messages, etc.
		  
		  if(!suppressDOMAdditions) {suppressDOMAdditions = false;}
		  
		  // if it's not a simple form field (e.g. is a radio button or checkbox array), we have to get each
		  // element (checkbox, radio button) and call the function for it.  
		  if(typeof(formField.value)!= "string") 
		     {
			   for(var i=0;i<formField.length;i++)
			   	{			
				 insertWarning(formField[i], level, symbol, suppressDOMAdditions);
			   	}
			 }
		  
		  else
		  	{
			  if(suppressDOMAdditions != true)
			  	{
				  // create a nice little span package with the error inside
				  var newSpan = document.createElement("span");
				  newSpan.className="autoError";
				  newSpan.style.color="red";
				  newSpan.style.fontFamily="arial";
				  newSpan.style.fontWeight="bold";
				  newSpan.style.fontSize="8pt";
				  newSpan.style.backgroundColor="#FFFF00";
				  //depending on the type of error, it may be a different character				  
				  if(!symbol) {symbol=" ! ";}
				  var newText = document.createTextNode(symbol);
	
				  newSpan.appendChild(newText);
				  
				  // find the parent container of the form field			 
				  parentNode = formField.parentNode;			
				  			  
				  //insert the span before the next sibling of the form field (e.g. right after the form field) 			  
				  parentNode.insertBefore(newSpan, formField.nextSibling);	
			  	}
				
			  // add a background color
			  formField.style.backgroundColor = "#FFFFDD";
			  return;
	  		}
		}	
	
	function arrayToList(arrayName)
		{
		 // turns a list of array values (such as from a series of checkboxes) into a comma-delimited list.
	 	 var valueToReturn=""
		 for(var i=0;i<arrayName.length;i++)
		 	{
			 // only add it to the list if checked!
			 if(arrayName[i].checked)
			 	{
			 	 valueToReturn=valueToReturn + arrayName[i].value + ",";
		 		}
		 	 //multiple select boxes might also be an issue, have to check their values as well
		 	 else if (arrayName[i].options && arrayName[i].selectedIndex != -1 && arrayName[i].options[arrayName[i].selectedIndex].value != "")
		 	 	{
			 	 valueToReturn = valueToReturn + arrayName[i].options[arrayName[i].selectedIndex].value + ",";
		 	 	}		 	 	
		 	}
		 // hack off the last comma
		 valueToReturn=valueToReturn.substring(0,valueToReturn.length-1);	
		 return valueToReturn;
		}	
	

	function validateSingleField(fieldRef)
		{	var fieldmsg="";		
			formReference = fieldRef.form;
			if(fieldRef.value && fieldRef.className)
		  	{	
			  // loop through the different patterns defined above
			  if(fieldRef.className.toLowerCase() == "component")
				{
					fieldmsg += validateWithComponent(fieldRef);					
				}
			  else
			 	{
				  for(var patternNumber=0;patternNumber<patterns.length;patternNumber++)
				  	{
					  // if the pattern matches the one given in the 'required' hidden field...
					  if (patterns[patternNumber][0] == fieldRef.className.toLowerCase())
					  	{		 
						  // if the field doesn't exist, build a system message, but don't prevent submission.
						  if(!formReference.elements[fieldRef.value])
						  	{
							  sysmsg += "System: Field " + fieldRef.value + " not defined \n";
						  	}
						  // then compare the actual field's value with the regexp.  If it doesn't match...
						  else
						  	{
						  	  valueToMatch = getFieldValue(formReference.elements[fieldRef.value]);						 	
						  	  // set the pattern to match; use the user-supplied pattern if RegExp
						  	  var patternToMatch=patterns[patternNumber][1];
							 						
						  	  if (patterns[patternNumber][0].toLowerCase()=="regexp" && fieldRef.lang)
						  	  	{										  	 
							  	 patternToMatch = new RegExp();
							  	 patternToMatch = fieldRef.lang;										  	 
						  	  	}		
							 				  	  
							  if (valueToMatch.match(patternToMatch) == null)
							  	{
								  // don't add to the message if the field is optional and left blank.
								  if (fieldRef.name.toLowerCase()=="optional" && valueToMatch=="")
								   {}
								  else
								   {								  
								  	// otherwise add to the message											 
								  	fieldmsg += fieldRef.title + "\n";											  
								  	// insert warning symbol, but not if the validation field specifies not to 
									if(fieldRef.getAttribute("messageonly") == false || fieldRef.getAttribute("messageonly") != "true")
								  		{
											insertWarning(formReference.elements[fieldRef.value], "error");
										}
									// otherwise, just pass "true" to suppress DOM additions
									else
										{
											insertWarning(formReference.elements[fieldRef.value], "error",false,true);
										}
							   	   }
							  	}
						  	}
					  	}
				  	}
				}
		  	}
			
			return fieldmsg;
		}	
		
		
	function validateWithComponent(fieldRef)
		{ 
			formReference = fieldRef.form;
			if(!window.addToAjaxActionsRunNow)
			{
				alert("Systems: Must include Ajax initialization script to run Ajax actions \n");
				return "";
			}
			
	
			if(!fieldRef.getAttribute("componentname") || !fieldRef.getAttribute("componentmethod"))
			{
				alert("Systems: Must define component name and function for ajax validation");
				return "";
			}
			else
			{
				aSynchFlag=false;
				currFieldRef=fieldRef;
				var component = fieldRef.getAttribute("componentname");
				var method = fieldRef.getAttribute("componentmethod");
				var guifunction = "completeSynchValidation";
				var xmlData = "<fieldvalue>" + getFieldValue(formReference.elements[fieldRef.value]) + "</fieldvalue>";
				addToAjaxActionsRunNow(component, method, guifunction, xmlData);								
				if (req.status != 200) 
				{
					return "Invalid Ajax Request";
				}
				if(componentMessage && componentMessage != "")
				{	
					insertWarning(formReference.elements[fieldRef.value], "error");					
					return componentMessage;											
				}
				else
				{
					return "";
				}
			}	
		}	
		
		
	function completeSynchValidation()
		{			
			var isValid = functionXML.getElementsByTagName("valid")[0].firstChild.nodeValue;
			if(isValid=="true")
				{
					componentMessage = "";
				}
			else
				{	
					if(functionXML.getElementsByTagName("message").length > 0 && functionXML.getElementsByTagName("message")[0].firstChild && functionXML.getElementsByTagName("message")[0].firstChild.nodeValue != "")
						{
							componentMessage = functionXML.getElementsByTagName("message")[0].firstChild.nodeValue + " \n";
						}
					else
						{
							if (currFieldRef.getAttribute("title"))
								{
									componentMessage = currFieldRef.getAttribute("title") + " \n";
								}
							else
								{
									componentMessage = ""
								}
						}
				}
			
			
		}
		
	function setUpVariables()
		{		
				// To add a new type of validator, give it a name here -- this matches the property on the form field....
				dataTypes[0]="anything";
				dataTypes[1]="url"; 
				dataTypes[2]="email";
				dataTypes[3]="integer";  // positive only 
				dataTypes[4]="parmlist"; 
				dataTypes[5]="s1url";
				dataTypes[6]="regexp"; // a regular expression, provide by the user via the lang attribute
				dataTypes[7]="uid";
				dataTypes[8]="dt"; // date in mm/dd/yyyy format
				
								// now we create a 2-d array of patterns.  First column is the datatype from above...
								// don't touch this...
								 	for(var i=0; i<dataTypes.length;i++)
									{
									 patterns[i]=new Array();
									 patterns[i][0] = dataTypes[i];
									}
							
				/* now add your regular expression to correspond to the validation type above */
				patterns[0][1] = /\S/;
				patterns[1][1] = /http[s]?\:[\S]{1,}/;
				patterns[2][1] = /^([a-zA-Z0-9_\-])+(\.([a-zA-Z0-9_\-])+)*@((\[(((([0-1])?([0-9])?[0-9])|(2[0-4][0-9])|(2[0-5][0-5])))\.(((([0-1])?([0-9])?[0-9])|(2[0-4][0-9])|(2[0-5][0-5])))\.(((([0-1])?([0-9])?[0-9])|(2[0-4][0-9])|(2[0-5][0-5])))\.(((([0-1])?([0-9])?[0-9])|(2[0-4][0-9])|(2[0-5][0-5]))\]))|((([a-zA-Z0-9])+(([\-])+([a-zA-Z0-9])+)*\.)+([a-zA-Z])+(([\-])+([a-zA-Z0-9])+)*))$/;
				patterns[3][1] = /^\d+$/;  // positive only
				patterns[4][1] = /^[a-zA-Z0-9&=_%\.]{1,}$/;
				patterns[5][1] = /^#[a-zA-Z0-9&=_%\.]{1,}#\/?/;	
				patterns[6][1] = / /;      // the user supplies the regExp for this one
				patterns[7][1] = /^[0-9A-Za-z]{2}[0-9]{3}[0-9A-Za-z]{1}$/;
				patterns[8][1] = /^\d{1,2}(\-|\/|\.)\d{1,2}\1\d{4}$/;
				
				
				
				//now set up all disallowed characters
				//each element of the array contains another array with
				// a) the property on the form field that indicates a character is disallowed.  Also the type of
				//    error passed to the field marker function-- this defaults to "error" in the function, and controls
				//    the symbol placed by the field.
				// b) the value of the property indicating it should be disallowed
				// c) the regexp sequence to disallow (must use the / and / delimiters)
				// d) the message to print if it is encountered
				// e) the symbol to use next to the field.  Defaults to !
				
				disallowedCharacters[0]=new Array();
				disallowedCharacters[0][0] = "crlf";
				disallowedCharacters[0][1] = "disallow";
				disallowedCharacters[0][2] = /\n/;
				disallowedCharacters[0][3] = "Remove CR/LF characters from fields marked <";
				disallowedCharacters[0][4] = " < "
				
				disallowedCharacters[1]=new Array();
				disallowedCharacters[1][0] = "bullets";
				disallowedCharacters[1][1] = "disallow";
				disallowedCharacters[1][2] = /[\u2022\u2023]/;
				disallowedCharacters[1][3] = "Remove bullets from the fields marked *";
				disallowedCharacters[1][4] = " * ";
		}
		
		
	// so if we change the appearance of a field, we can change it back
	function getCurrentStyle(formReference)
		{
			// for now, saving just the border properties
			for(var i=0;i<formReference.elements.length;i++)
				{	
				 	existingStyles[i]= getStyle(formReference.elements[i],"backgroundColor");
				}			
		}	
		
	// change it back
 	function restoreStyles(formReference)
 		{
	 	 for(var i=0;i<formReference.elements.length;i++)
			{
			 if(!existingStyles[i]) existingStyles[i]="";
			 formReference.elements[i].style.backgroundColor = existingStyles[i];				 					
			}		
 		}
		
			
	//not currently being used
	function getStyleObject(objectRef) 
	{
		if(objectRef.currentStyle) return objectRef.currentStyle;
		else return false;
	} 


	function getFieldValue(fieldRef)
	{
		var theValue;
		
		// if it's an array of checkboxes or radio buttons
		if (!fieldRef.value)
			{
				theValue=arrayToList(fieldRef);
			}
		else if (fieldRef.type && (fieldRef.type == "checkbox" || fieldRef.type == "radio"))
			{
				if(fieldRef.checked)
					{
						theValue = fieldRef.value;
					}
				else
					{
						theValue = "";
					}
			}
		else
			{
				theValue=fieldRef.value;
			}
		return theValue;
	}