var arrValidators=new Array();

function formValidator() {
	//Properties
	this.name=arguments[0]; //The name of the form
	this.errors=new Array(); //Array with all errors found
	this.rules=new Array(); //This array contains all the rules to apply
	this.fieldToFocus=null; //If an error is found, its field will be focused
	this.debugMode=(arguments[1]) ? arguments[1] : 0;
	//Methods
	this.handleFormValidatorError=function() { //Used to handle errors in the code - only for debug
		if (this.debugMode==1) {
			alert("formValidator error: " + arguments[0]);
			return(false);
		}
		else return(false); //For debug mode 0 (standard), don't show error messages
	};
	this.handleError=function() {
		if (!this.fieldToFocus && objField.focus) this.fieldToFocus=arguments[0];
		this.errors[this.errors.length]=new error(arguments[0],arguments[1]);
	};
	this.initialize=function() {
		if (document.forms[this.name])
			document.forms[this.name].onsubmit=function() {
				return(eval("objVld_" + this.name + ".execute()"));
			}
		else
			return(this.handleFormValidatorError("The form '" + this.name + "' isn't present or it has not been loaded yet"));
	};
	this.addRule=function() { //Feeds rules array
		if (arguments[1]) {
			if (isArray(arguments[1]))
				arrRules=arguments[1];
			else
				arrRules=[arguments[1]];
		}
		else arrRules=["required"];
		for (intRule=0;intRule<arrRules.length;intRule++)
			this.rules[this.rules.length]=new rule(arguments[0],arrRules[intRule],(arguments[2]) ? arguments[2] : null,(arguments[3]) ? arguments[3] : null);
	};
	this.execute=function() {
		objForm=document.forms[this.name];
		for (intRule=0;intRule<this.rules.length;intRule++) {
			objRule=this.rules[intRule];
			if (objForm[objRule.fieldName] || objRule.ruleType=="externalFunction") {
				objField=objForm[objRule.fieldName];
				switch (objRule.ruleType) {
					case "externalFunction": //Evaluates an external function
						extFunction=eval(objRule.fieldName);
						if (typeof(extFunction)=="function" || typeof(extFunction)=="object") {
							if (!extFunction(objForm)) this.handleError(null,(objRule.errorMessage) ? objRule.errorMessage : "");
						}
						break;
					case "date": //Pattern: dd/mm/yyyy
						if (!isEmpty(getFieldValue(objField)) && !verifyDate(getFieldValue(objField))) {
							this.handleError(objField,(objRule.errorMessage) ? objRule.errorMessage : "Data inválida");
						}
						break;
					case "cep": //Pattern: 99999-999 / 99999999
						if (!isEmpty(getFieldValue(objField)) && !verifyCEP(getFieldValue(objField))) {
							this.handleError(objField,(objRule.errorMessage) ? objRule.errorMessage : "CEP inválido");
						}
						break;
					case "email": //a@b.c, c'est claire
						if (!isEmpty(getFieldValue(objField)) && !verifyMail(getFieldValue(objField))) {
							this.handleError(objField,(objRule.errorMessage) ? objRule.errorMessage : "Email inválido");
						}
						break;
					case "required": //The field cannot be empty, that's all
						if (isEmpty(getFieldValue(objField))) {
							this.handleError(objField,(objRule.errorMessage) ? objRule.errorMessage : "O campo " + ((objRule.customFieldName) ? objRule.customFieldName : objField.name) + " deve ser preenchido");
						}
						break;
				}
			}
			//field not found
		}
		if (this.errors.length) { //Show errors and stop the process
			strErrorMessage="Os seguintes erros foram encontrados no preenchimento do formulário:\n\n";
			arrUsedFields=new Array(); //A list of the fields with errors, used to avoid more than one message related to the same field
			for (intError=0;intError<this.errors.length;intError++) {
				if (!inArray(this.errors[intError].field,arrUsedFields)) {
					arrUsedFields[arrUsedFields.length]=this.errors[intError].field;
					strErrorMessage+="- " + this.errors[intError].message + "\n";
				}
			}
			alert(strErrorMessage);
			if (this.fieldToFocus) this.fieldToFocus.focus();
			this.errors=new Array(); //Cleaning...
			this.fieldToFocus=null; //Cleaning...
			return(false);
		}
		else return(true);
	};
	//Add the object to arrValidators Array
	arrValidators[arrValidators.length]=this;
}
//>>>>END of the constructor

function error(objField,strErrorMessage) { //error class
	this.field=objField;
	this.message=strErrorMessage;
}

function rule(strFieldName,strRuleType,strCustomFieldName,strErrorMessage) { //rule class
	this.fieldName=strFieldName;
	this.ruleType=strRuleType;
	this.customFieldName=strCustomFieldName;
	this.errorMessage=strErrorMessage;
}

onload=function() {
	for (intValidator=0;intValidator<arrValidators.length;intValidator++) {
		arrValidators[intValidator].initialize();
	}
}

function getValidator(strFormName) {
	eval('objVld_' + strFormName + '=new formValidator(\'' + strFormName + '\');');
	return(eval('objVld_' + strFormName));
}

// Generic functions
function getFieldValue(objField) { //Get the value of a form field - lacks checkbox and radio, I've made it elsewhere already
	strReturnValue='';
	if (objField[0]) {
		if (objField[0].type=="radio") {
			for (intRadio=0;intRadio<objField.length;intRadio++) {
				if (objField[intRadio].checked) strReturnValue=objField[intRadio].value;
			}
		}
		else if (objField.type=="select-one")
			strReturnValue=objField.options[objField.selectedIndex].value;
	}
	else
		strReturnValue=objField.value;
	return(strReturnValue);
}

function isArray(objAnyArray) { //returns a boolean indicating whether the argument is an array
	return((objAnyArray.sort) ? 1 : 0);
}

function inArray(value, arrAnyArray) { //returns a boolean indicating whether the first argument is present in the second, which must be an array
	for (intPos=0;intPos<arrAnyArray.length;intPos++)
		if (arrAnyArray[intPos]==value) return true;
	return(false);
}

function explode(arrAnyArray,strSeparator) {
	var strReturnValue="";
	for (intPos=0;intPos<arrAnyArray.length;intPos++)
		strReturnValue+=arrAnyArray[intPos] + strSeparator;
	strReturnValue=strReturnValue.substring(0,strReturnValue.length-strSeparator.length);
	return(strReturnValue);
}

function isEmpty(value) {
	if (isArray(value)) return(value.length==0);
	else return(value==null || trim(value)=="");
}

function trim(str) {
	var intChar,strChar,intStartIndex,intEndIndex;
	for (intChar=0;intChar<str.length;intChar++) {
		strChar=str.substr(intChar,1);
		if (strChar!=" " && strChar!="\n" && strChar!="\r" && strChar!="\t") {
			intStartIndex=intChar;
			break;
		}
	}
	for (intChar=str.length-1;intChar>=0;intChar--) {
		strChar=str.substr(intChar,1);
		if (strChar!=" " && strChar!="\n" && strChar!="\r" && strChar!="\t") {
			intEndIndex=intChar;
			break;
		}
	}
	return(str.substring(intStartIndex,intEndIndex+1));
}

function verifyCEP(strCEP) {
	return(!(strCEP.search("[0-9]{8}")==-1 && strCEP.search("[0-9]{5}-[0-9]{3}")==-1));
}

function verifyMail(strEmail) {
	strValidChars="@abcdefghijklmnopqrstuvxywzABCDEFGHIJKLMNOPQRSTUVXYWZ0123456789-_."; //Characters that an email can contain. (i'm not completely sure about it, but I didn't found any specification that could help)
	arrBoundaryChars=new Array();
	for (intPos=0;intPos<strEmail.length;intPos++) { //Check if all characters are valid
		strThisChar=strEmail.substr(intPos,1);
		if (strValidChars.indexOf(strThisChar)==-1) {
			return(false);
		}
		if (strThisChar=="@" || strThisChar==".") { //Feed the BoundaryChars array
			arrBoundaryChars[arrBoundaryChars.length]=parseInt(intPos);
		}		
	}
	for (intPos=0;intPos<arrBoundaryChars.length;intPos++) { //Check if dots and ats are not in conflict
		intThisItem=arrBoundaryChars[intPos]
		intNextItem=(intPos==arrBoundaryChars.length) ? 0 : arrBoundaryChars[intPos+1]
		if (intThisItem==0 || intThisItem==strEmail.length-1) {
			return(false);
		}
		if (intThisItem+1==intNextItem) {
			return(false);
		}
	}
		
	intFirstAtIndex=strEmail.indexOf("@");
	intLastAtIndex=strEmail.lastIndexOf("@");
	intFirstDotIndex=strEmail.indexOf(".");
	intLastDotIndex=strEmail.lastIndexOf(".");
	
	if (intFirstAtIndex!=intLastAtIndex || intFirstAtIndex==-1 || intFirstDotIndex==-1 || intLastDotIndex<intFirstDotIndex) {
		return(false);
	}
	return(true); //If it got here, it's all ok (I hope)
}

function verifyDate(strDate) {
	strDate=trim(strDate);
	if (strDate.search("[0-9]{2}\/[0-9]{2}\/[0-9]{4}")==-1) //Out of pattern dd/mm/yyyy
		return(false);
	else {
		intDay=strDate.split("\/")[0];
		intMonth=strDate.split("\/")[1];
		intYear=strDate.split("\/")[2];
		if (intDay<1 || intMonth<1 || intMonth>12 || intYear<1900 || intYear>2100) return(false);
		arrLastMonthDay=[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
		if (((intYear % 4 == 0) && intYear % 100 != 0) || intYear % 400 == 0) //Year with 366 days
			arrLastMonthDay[1]=29;
		if (intDay>arrLastMonthDay[-1]) return(false);
	}
	return(true);
}