/*******************************************************************
*
*	regex can be functions, regular expressions, or built-in
*	constants starting with RX_
*
********************************************************************/


/*******************************************************************
*	function validateCompleteForm
*	@param object objForm - <form>
*	@param string strErrorClass
*	@returns bool
*
*	This function validates the entire form as a whole, and alerts
*	the user of all the errors
*
*	Checks validity of all elements in objForm based on the
*	attributes of the elements. See Documentation for valid
*	attributes.
*
*	If all elements are valid, returns true.
*
*	If any elements are invalid, the elements' classes will be set
*	to strErrorClass, and return false.
*
********************************************************************/
function validateCompleteForm(objForm, strErrorClass)
{
	return _validateInternal(objForm, strErrorClass, 0);
}


/*******************************************************************
*	function validateStandard
*	@param object objForm - <form>
*	@param string strErrorClass
*	@returns bool
*
*	This function validates each field separately, and alerts
*	the user of the first error, and then returns false.
*
*	Checks validity of all elements in objForm based on the
*	attributes of the elements. See Documentation for valid
*	attributes.
*
*	If all elements are valid, returns true.
*
*	If any elements are invalid, the element's class will be set
*	to strErrorClass, and return false.
*
********************************************************************/
function validateStandard(objForm, strErrorClass)
{
	return _validateInternal(objForm, strErrorClass, 1);
}

function _validateInternal(form, strErrorClass, nErrorThrowType)
{
	var strErrorMessage = "";
	var objFirstError = null;

	if(nErrorThrowType == 0)
	{
		strErrorMessage = (form.err) ? form.err : _getLanguageText("err_form");
	}

	var fields = _GenerateFormFields(form);

	for(var i = 0; i < fields.length; i++)
	{
		var field = fields[i];
		if(!field.IsValid(fields))
		{
			field.SetClass(strErrorClass);
			if(nErrorThrowType == 1)
			{
				_throwError(field);
				return false;
			}
			else
			{
				if(objFirstError == null)
				{
					objFirstError=field;
				}
				strErrorMessage=_handleError(field,strErrorMessage);
				bError=true;
			}
		}
		else
		{
			field.ResetClass();
		}
	}

	if(objFirstError!=null){
		alert(strErrorMessage);
		objFirstError.element.focus();
		return false;
	}

	return true;
}

/**********************************************************************
*	function _getLanguageText (id)
*	@param string id
*	@returns string
*
*	Returns a default error message
*	-err_form
*	-err_enter
*	-err_select
*
***********************************************************************/
function _getLanguageText(id)
{
	switch(id)
	{
		case "err_form":
			strResult = "Please enter/select values for the following fields:\n\n";
			break;
		case "err_enter":
			strResult = "Please enter a valid \"%FIELDNAME%\"";
			break;
		case "err_select":
			strResult = "Please select a valid \"%FIELDNAME%\"";
			break;
	}

	return strResult;
}

/*************************************************************************
*	function _GenerateFormFields
*	@param object form - <form>
*	@returns array
*
*
*	Generates and returns an array of the fields inside the form.
*	If no elements, the returned array will be blank. No error thrown
*
**************************************************************************/
function _GenerateFormFields(form)
{
	var arr = new Array();

	/** Go through the entire element array of the form **/
	for(var i = 0; i < form.length; i++)
	{
		/** Check to see if the element name already exists in the array **/
		var element = form.elements[i];
		var index = _getElementIndex(arr, element);

		if(index == -1)
		{
			/** If the element does not exist, add it to the array **/
			var temp = new Field(element,form);
			temp.ResetClass();
			arr[arr.length] = temp;
		}
		else
		{
			/** If the element does exist, take the higher priority attributes, and save them **/
			arr[index].Merge(element);
		}
	}

	/** Return the resulting array **/
	return arr;
}

/******************************************************************
*	function _getElementIndex
*	@param array arr
*	@param object element
*	@returns int
*
*	Searches for a duplicate in the array arr by element name.
*	Returns the index of the element with a duplicate name.
*	If no duplicate found, returns -1
*
*******************************************************************/
function _getElementIndex(arr,element)
{
	/** If the element has no name, it can't be checked for duplicity **/
	if(element.name)
	{
		/** Case insensitive search by name through the entire array of created elements **/
		var elementName=element.name.toLowerCase();
		for(var i = 0; i < arr.length; i++)
		{
			/** If the checking element has no name, it can't be checked for duplicity **/
			if(arr[i].element.name)
			{
				/** If the names are the same, return the index **/
				if(arr[i].element.name.toLowerCase() == elementName)
				{
					return i;
				}
			}
		}
	}

	/** Default return if no duplicates found **/
	return -1;
}

/*****************************************************************************
*	function Field(element,form)
*	@param object element - <input> <textarea>
*	@param object form - <form>
*
*	Creates an object with the same attributes as the passed in element.
*	Removes unneeded attributes from the created object.
*****************************************************************************/
function Field(element,form)
{
	this.type = element.type;
	this.element = element;
	this.exclude = element.exclude || element.getAttribute('exclude');
	this.err = element.err || element.getAttribute('err');
	this.required = _parseBoolean(element.required || element.getAttribute('required'));
	this.displayname = element.displayname || element.getAttribute('displayname');
	this.elements = new Array();
	switch(this.type)
	{
		case "textarea":
		case "password":
		case "text":
		case "file":
			this.value = element.value;
			this.minLength = element.minlength || element.getAttribute('minlength');
			this.maxLength = element.maxlength || element.getAttribute('maxlength');
			this.regex = this._getRegEx(element);
			this.minValue = element.minvalue || element.getAttribute('minvalue');
			this.maxValue = element.maxvalue || element.getAttribute('maxvalue');
			this.equals = element.equals || element.getAttribute('equals');
			this.callback = element.callback || element.getAttribute('callback');
			this.ifID = element.ifID || element.getAttribute('ifID');
			this.ifVal = element.ifVal || element.getAttribute('ifVal');
			break;

		case "select-one":
		case "select-multiple":
			this.values = new Array();
			for(var i = 0; i < element.options.length; i++)
			{
				if(element.options[i].selected == true && element.options[i].value != "")
				{
					this.values[this.values.length] = element.options[i].value;
				}
			}

			this.min = element.min || element.getAttribute('min');
			this.max = element.max || element.getAttribute('max');
			this.equals = element.equals || element.getAttribute('equals');
			this.ifID = element.ifID || element.getAttribute('ifID');
			this.ifVal = element.ifVal || element.getAttribute('ifVal');
			break;

		case "checkbox":
			this.min = element.min || element.getAttribute('min');
			this.max = element.max || element.getAttribute('max');

		case "radio":
			this.required = _parseBoolean(this.required || element.getAttribute('required'));
			this.values = new Array();
			if(element.checked)
			{
				this.values[0] = element.value;
			}
			this.elements[0] = element;
			break;
	}
}

/***************************************************************************
*	Field.prototype.Merge (element)
*	@param Field element
*	@returns void
*
*	Preserves the most important attributes of  2 elements with the same
*	name. Does not save values.
****************************************************************************/
Field.prototype.Merge = function (element)
{
	/** If either element is required, make the merged element required **/
	if(_parseBoolean(element.getAttribute('required'))) { this.required = true; }

	/** Set the rest of the attributes based on whether the attribute is present **/
	if(!this.err) { this.err = element.getAttribute('err'); }
	if(!this.equals) { this.equals = element.getAttribute('equals'); }
	if(!this.callback) { this.callback = element.getAttribute('callback'); }
	if(!this.displayname) { this.displayname = element.getAttribute('displayname'); }
	if(!this.max) { this.max = element.getAttribute('max'); }
	if(!this.min) { this.min = element.getAttribute('min'); }
	if(!this.regex) { this.regex = this._getRegEx(element); }
	if(!this.ifID) {this.ifID = element.getAttribute('ifID'); }
	if(!this.ifVal) {this.ifVal = element.getAttribute('ifVal'); }

	/** If the element is a checkbox, or a radio button, make sure the value is
		preserved in the checked element **/
	if(element.checked) { this.values[this.values.length] = element.value; }

	/** Not sure what this does **/
	this.elements[this.elements.length] = element;
}

/***************************************************************************
*	Field.prototype.IsValid (arrFields)
*	@param array arrFields
*	@returns bool
*
*	Checks to see whether a field is valid, according to all the attribute
*	tags specifying cases of validity.
*	If all is valid, return true. If not, return false.
***************************************************************************/
Field.prototype.IsValid = function(arrFields)
{
	switch(this.type)
	{
		case "textarea":
		case "password":
		case "text":
		case "file":
			return this._ValidateText(arrFields);
		case "select-one":
		case "select-multiple":
		case "radio":
		case "checkbox":
			return this._ValidateGroup(arrFields);
		default:
			return true;
	}
}

/********************************************************************
*	Field.prototype.SetClass (newClassName)
*	@param String newClassName
*	@returns void
*
*	Sets the class of the field(s) to newClassName. Saves the old
*	class in the attribute oldClassName for later recall
*********************************************************************/
Field.prototype.SetClass = function(newClassName)
{
	/** Do not change the class of buttons **/
	if(this.type != "button" && this.type != "submit" && this.type != "reset")
	{
		/** If the field has multiple elements (radio, select, etc.) **/
		if(this.elements && this.elements.length > 0)
		{
			/** For each element **/
			for(var i = 0; i < this.elements.length; i++)
			{
				/** Only set the new class and the old class if the class is not new already **/
				if(this.elements[i].className != newClassName)
				{
					/** Preserve the old class name for later recall **/
					this.elements[i].oldClassName = this.elements[i].className;
					this.elements[i].className = newClassName;
					this.elements[i].hasOldClassName = '1';
				}
			}
		}
		else
		{
			/** Only set the new class and the old class if the class is not new already **/
			if(this.element.className != newClassName)
			{
				/** Preserve the old class name for later recall **/
				this.element.oldClassName = this.element.className;
				this.element.className = newClassName;
				this.element.hasOldClassName = '1';
			}
		}
	}
}

/*********************************************************************************
*	Field.prototype.ResetClass ()
*	@returns void
*
*	Resets the class name of the calling Field to what it was prior to the call
*	to SetClass (newClassName). If no call to SetClass (newClassName) has been
*	made, nothing will happen.
**********************************************************************************/
Field.prototype.ResetClass = function()
{
	/** Do not change the class of buttons **/
	if(this.type != "button" && this.type != "submit" && this.type != "reset")
	{
		/** If the field has multiple elements (radio, select, etc.) **/
		if(this.elements && this.elements.length > 0)
		{
			/** For each element **/
			for(var i = 0; i < this.elements.length; i++)
			{
				/** If the class name has been changed **/
				if(this.elements[i].hasOldClassName == '1')
				{
					/** Set the class name to the old one**/
					this.elements[i].className=this.elements[i].oldClassName;
				}

				/** If the class name has not been changed yet (no old one) do nothing **/
			}
		}
		else
		{
			/** If the class name has been changed **/
			if(this.element.hasOldClassName == '1')
			{
				this.element.className=this.element.oldClassName;
			}

			/** If the class name has not been changed yet (no old one) do nothing **/
		}
	}
}

/*******************************************************************************
*	Field.prototype._getRegEx (element)
*	@param Field element
*	@returns null || string
*
*	Checks the regex attribute of the element.
*
*	If no regex attribute, return null
*
*	If regex attribute is a function, return the function name
*
*	If regex attribute is a built-in regular expression, return the string
*
*	If regex attribute is an actual regular expression, return the regular
*	expression without the enclosing slashes (if any)
*********************************************************************************/
Field.prototype._getRegEx = function(element)
{
	/** Get the 'regex' attribute from the element **/
	regex = element.regex || element.getAttribute('regex');

	/** If no such attribute, return null **/
	if(regex == null)
	{
		return null;
	}

	retype = typeof(regex);

	/** If the regex attribute is a function, return the function name **/
	if(retype.toUpperCase() == "FUNCTION")
	{
		return regex;
	}
	else
	{
		/** regex is either a string for built-in regular expressions,
			or its own regular expression to be evaluated. **/
		if((retype.toUpperCase() == "STRING") && (regex.substring(0,3) != "RX_"))
		{
			nBegin=0;
			nEnd=0;

			if(regex.charAt(0) == "/")
			{
				nBegin = 1;
			}

			if(regex.charAt(regex.length - 1) == "/")
			{
				nEnd = -1;
			}

			/** If regular expression, return all the meat of the expression (no '/'s) **/
			return new RegExp(regex.slice(nBegin, nEnd));
		}
		else
		{
			/** If built-in regular expression, return the String representation **/
			return regex;
		}
	}
}

/*********************************************************************************
*	Field.prototype._ValidateText (arrFields)
*	@param array arrFields
*	@returns bool
*
*	Checks value against text field parameters - minValue, maxValue, equals,
*	required, minLength, maxLength, regex, callback
*
*	If all of the tests pass, return true.
*	If any of the tests fail, return false.
*
**********************************************************************************/
Field.prototype._ValidateText = function(arrFields)
{
	if (this.required && this.callback)
	{
		nCurId = this.element.id ? this.element.id : "";
		nCurName = this.element.name ? this.element.name : "";
		eval("bResult = " + this.callback + "('" + nCurId + "', '" + nCurName + "', '" + this.value + "');");
		/** If the function callback, with signature function(id, name, value) returns false, return false **/
		if(bResult == false)
		{
			return false;
		}
	}

	/** If the field is required, but no value, return false **/
	if(this.required && !this.value)
	{
		return false;
	}

	/** Check the length against the maxLength attribute **/
	if(this.value && (this.minLength && this.value.length < this.minLength))
	{
		return false;
	}

	/** Check the length against the maxLength attribute **/
	if(this.value && (this.maxLength && this.value.length > this.maxLength))
	{
		return false;
	}

	/** Check the regular expression (function, built-in, or expression) **/
	if(this.regex)
	{
		if(!_checkRegExp(this.regex, this.value))
		{
			/** Return false unless it's not required, and there's no value **/
			if(!this.required && this.value)
			{
				return false;
			}
			if(this.required)
			{
				return false;
			}
		}
	}

	/** Check the equals attribute **/
	if(this.equals)
	{
		/** For each element in the form **/
		for(var i = 0; i < arrFields.length; i++)
		{
			var field = arrFields[i];
			/** If the element name or id is the same as what we're trying to match values with **/
			if(field.element.name == this.equals || field.element.id == this.equals)
			{
				/** If the values are not the same, return false **/
				if(field.element.value != this.value)
				{
					return false;
				}
				break;
			}
		}
	}

	if(this.required)
	{
		/** If minValue or maxValue, but not a number, return false **/
		var fValue=parseFloat(this.value);
		if((this.minValue || this.maxValue) && isNaN(fValue))
		{
			return false;
		}

		/** Check value against minValue **/
		if(this.minValue && fValue < this.minValue)
		{
			return false;
		}

		/** Check value against maxValue **/
		if(this.maxValue && fValue > this.maxValue)
		{
			return false
		}
	}

	//Conditional check on null if ifID is a certain value, or just filled in
	if (this.ifID)
	{
		if (this.ifVal)
		{
			if (document.getElementById(this.ifID).value == this.ifVal && this.value == "")
			{
				return false;
			}
		}
		else
		{
			if (document.getElementById(this.ifID).value != "" && this.value == "")
			{
				return false;
			}
		}
	}

	/** If no errors, return true **/
	return true;
}

/*********************************************************************************
*	Field.prototype._ValidateGroup (arrFields)
*	@param array arrFields
*	@returns bool
*
*	Checks value against group field (checkbox, select - group, etc.) parameters -
*	min, max, required
*
*	If all of the tests pass, return true.
*	If any of the tests fail, return false.
*
**********************************************************************************/
Field.prototype._ValidateGroup = function(arrFields)
{
	/** If required, and no value, return false **/
	if(this.required && this.values.length == 0)
	{
		return false;
	}

	/** Make sure there are at least min number of selected value **/
	if(this.required && this.min && this.min > this.values.length)
	{
		return false;
	}

	/** Make sure there are at most max number of selected value **/
	if(this.required && this.max && this.max < this.values.length)
	{
		return false;
	}

	//Conditional check on null if ifID is a certain value, or just filled in
	if (this.ifID && document.getElementById(this.ifID))
	{
		if (this.ifVal)
		{
			if (document.getElementById(this.ifID).value == this.ifVal && this.value == "")
			{
				return false;
			}
		}
		else
		{
			if (document.getElementById(this.ifID).value != "" && this.element.value == "")
			{
				return false;
			}
		}
	}

	/** If no errors, return true; **/
	return true;
}

/********************************************************************************
*	function _handleError(field, strErrorMessage)
*	@param Field field
*	@param string strErrorMessage
*	@returns string
*
*	Appends the field's display name / id / name to the end of strErrorMessage
*********************************************************************************/
function _handleError(field, strErrorMessage)
{
	var obj = field.element;
	strNewMessage = strErrorMessage + (field.displayname ? field.displayname : (obj.id ? obj.id : obj.name)) + "\n";
	return strNewMessage;
}

function _throwError(field)
{
	var obj = field.element;

	switch(field.type)
	{
		case "text":
		case "password":
		case "textarea":
		case "file":
			try { obj.focus(); } catch(ignore){}
			alert(_getError(field, "err_enter"));
			break;

		case "select-one":
		case "select-multiple":
		case "radio":
		case "checkbox":
			try { obj.focus(); } catch(ignore){}
			alert(_getError(field, "err_select"));
			break;
	}
}

/**************************************************************************
*	function _getError (field, str)
*	@param Field field
*	@param string str
*	@returns string
*
*	If the field has an err attribute, that will be returned.
*
*	Otherwise, returns a specific error message based on 'str',
*	and the displayname / id / name
**************************************************************************/
function _getError(field, str)
{
	var obj = field.element;
	strErrorTemp = (field.err) ? field.err : _getLanguageText(str);

	/** Replace newlines if any exist **/
	idx = strErrorTemp.indexOf("\\n");
	while(idx > -1)
	{
		strErrorTemp = strErrorTemp.replace("\\n","\n");
		idx = strErrorTemp.indexOf("\\n");
	}

	/** Replace the string '%FIELDNAME%' with either the displayname, id, or name of the element **/
	return strErrorTemp.replace("%FIELDNAME%", (field.displayname) ? field.displayname : ((obj.id) ? obj.id : obj.name));
}

/**************************************************************************
*	function _parseBoolean(value)
*	@param value
*	@returns bool
*
*	Parses the value as a boolean, and returns the result.
***************************************************************************/
function _parseBoolean(value)
{
	return !(!value || value == 0 || value == "0" || value == "false" );
}

/**************************************************************************
*	function _checkRegExp(regx, value)
*	@param string regx
*	@param string value
*	@returns bool
*
*	Tests both built-in and entered regular expressions. Returns the value
***************************************************************************/
function _checkRegExp(regx, value)
{
	switch(regx)
	{
		case 'RX_DECIMAL':
			value = 0 + value + 0; //for a leading 0 - decimal positioning
			return (/^[0-9]*\.?[0-9]*$/).test(value);
			break;

		case 'RX_NUMERIC':
			return (/^[0-9]*$/).test(value);
			break;

		case 'RX_EMAIL':
			return ((/^[a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$/).test(value));
			break;

		case 'RX_PHONE':
			return (/^1?[\-]?\(?\d{3}\)?\s?[\-]?\d{3}[\-]?\d{4}$/).test(value);
			break;

		case 'RX_ZIP':
			if(value.length == 6 || value.length == 7)
			{
				return((/^[a-zA-Z]\d[a-zA-Z] ?\d[a-zA-Z]\d$/).test(value));
			}
			if(value.length==5||value.length==10)
			{
				return((/^\d{5}(\-\d{4})?$/).test(value));
			}
			return false;
			break;

		case 'RX_CHAR':
			return (/^[a-zA-Z\s]*$/).test(value);
			break;

		case 'RX_ALPHA_NUMERIC':
			return (/^[\w\s]*$/).test(value);
			break;

		case 'RX_URL':
			return (/^[a-zA-Z]{3,4}:\/\/[a-zA-Z0-9][\d\w\.]*[a-zA-Z0-9](\/[\w\d]+)*/).test(value);
			break;

		case 'RX_CREDIT_CARD':
			value = value.replace(/[^\d]/, "");
			return (/^[\d]{13,19}$/).test(value);
			break;

		default:
			return(regx.test(value));
	}
};