var debug = 3; // Defines the debug level (0 - none, 1 - Errors, 2 - Warnings and Errors, 3 - Info, Warnings and Errors

// Esta é a função principal
// Form - Objecto DOM com o formulário a ser processado
// strErrorClass - CSS class a atribuir aos inputs que contêm erros.
//								Se esta class for vazia ou nula é usad
// showMultipleErrors - Se igual a false, mostra apenas 1 erro de cada vez, caso contrário (ou se for nulo) mostra todos os erros
// objError 	- O nome do objecto HTML, onde o sumário do erro será apresentado.
//						Este objecto deve estar por defeito escondido "style="display:none;" e é mostrado se existirem erros;
//						Este objecto tem que suportar o método innerHtml.
//						Se este objecto for nulo ou não existir é usado o comando Alert para mostrar os erros do formulário.
// languageCode - O código do idioma a usar para apresentar as mensagens de erro
function validateForm(form, strErrorClass, showMultipleErrors, objError, languageCode){
	//trace( "validateForm('" + form.name + ", " + strErrorClass + ", " + showMultipleErrors + ", " + objError + ")", 3);
	var strErrorMessage = "";
	var objFirstError = null;
	
	setTranslationStrings( languageCode );
	
	if (objError && objError != ""){ // By default, hide the error message.
		try{
			document.getElementById(objError).style.display = "none";
		} catch (ignore){
		}
	}
	
	if (showMultipleErrors == false){
		strErrorMessage = "";
	} else {
		showMultipleErrors = 1;
		strErrorMessage = txtTitleErrorSummary + "\n";
	};

	var fields = _GenerateFormFields(form);
	for (var i = 0; i < fields.length; ++i){
		var field = fields[i];
		if (!field.IsValid(fields)){
			field.SetClass(strErrorClass);
			
			if (showMultipleErrors == 0) {
				strErrorMessage += _getErrorMessage(field) + "\n";
				break; // To exit the cicle
			} else {
				strErrorMessage += " - " + _getErrorMessage(field) + "\n";
				bError = true;
			}
			
			if (objFirstError == null){
				objFirstError = field;
			}
		} else { // Voltamos a colocar a class anterior
			field.ResetClass();
		}
	};
	if (objFirstError != null) { // There is an error
		showError(strErrorMessage, objError);
		
		try { // Lets try to set the focus to the first field with error
			objFirstError.element.focus();
		} catch (ignore) {}
		return false;
	} else {
		return true;
	};	
};


 function _GenerateFormFields(form){
	var arr = new Array();
	for (var i = 0; i < form.length; ++i){
		var element = form.elements[i];
		var index = _getElementIndex(arr,element);
		//if it doesn't already exist, add it to our array, else merge the change
		if (index == -1){
			arr[arr.length] = new Field(element, form);
		} else {
			arr[index].Merge(element)
		};
	};
	//printArray( arr);
	return arr;
};

/***************************************************************
** Standard Translation (PT)
*****************************************************************/
var txtTitleErrorSummary, txtRequiredField, txtInvalidField;
function setTranslationStrings( languageCode ){
	if ( languageCode && (languageCode.toLowerCase() == "en" || languageCode.toLowerCase() == "en-gb" || languageCode.toLowerCase() == "en-us")) {
		txtTitleErrorSummary = "O formulário contém o(s) seguinte(s) erro(s):";
		txtRequiredField = "O campo \"%FIELDNAME%\" é de preenchimento obrigatório.";
		txtInvalidField = "O campo \"%FIELDNAME%\" não é válido.";
	} else {
		txtTitleErrorSummary = "O formulário contém o(s) seguinte(s) erro(s):";
		txtRequiredField = "O campo \"%FIELDNAME%\" é de preenchimento obrigatório.";
		txtInvalidField = "O campo \"%FIELDNAME%\" não é válido.";
	};
}; 

var errorTemplate = "";
function setTemplate( template ){
	errorTemplate = template;
}
/***************************************************************
** Field Class
*****************************************************************/
function Field(element, form){
   this.type = element.type;
   this.element = element;
   this.exclude = element.exclude || element.getAttribute('exclude');
   this.errorMessage = element.errorMessage || element.getAttribute('errorMessage');
   this.required = _parseBoolean(element.required || element.getAttribute('required'));
   this.realname = element.realname || element.getAttribute('realname');
	 
	 this.regexp = element.regexp || element.getAttribute('regexp');
	 
	 this.errorType = ""; // Identifies the type of error ( required, inValid, other);
	 
   this.elements = new Array();
	 
	 this.id = element.id;
	 this.name = element.name;
   
   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.regexp = this._getRegEx();
         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');
         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 && (!this.exclude || element.options[i].value != this.exclude)){
               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');
         break;
      case "checkbox":
         this.min = element.min || element.getAttribute('min');
         this.max = element.max || element.getAttribute('max');
         //no break, let it fall through to radio
      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 = function(element){
   //never negate a require field
   var required = _parseBoolean(element.getAttribute('required'));
   if (required){
      this.required = true;
   };
   //all other cases (except required) we only add if there isn't already a value (first come first served)
   if (!this.errorMessage){
      this.errorMessage = element.getAttribute('errorMessage');
   };
   if (!this.equals){
   	  this.equals = element.getAttribute('equals');
   };
   if (!this.callback){
   	  this.callback = element.getAttribute('callback');
   };
   if (!this.realname){
      this.realname = element.getAttribute('realname');
   };
   if (!this.max){
      this.max = element.getAttribute('max');
   };
   if (!this.min){
      this.min = element.getAttribute('min');
   };
   if (!this.regexp){
      this.regexp = this._getRegEx();
   };
   if (element.checked){
      this.values[this.values.length] = element.value;
   };
   this.elements[this.elements.length] = element;
};

Field.prototype.IsValid = function(arrFields){
   switch (this.type){
      case "textarea":
      case "password":
      case "text":
      case "file":
         return this._ValidateTextField(arrFields);
      case "select-one":
      case "select-multiple":
      case "radio":
      case "checkbox":
         return this._ValidateGroup(arrFields);
      default:
         return true;
   };
};

// Adiciona a class newClassName ao input field
Field.prototype.SetClass = function(newClassName){
   if ( (newClassName) && (newClassName != "") ) {
       if ( (this.elements) && (this.elements.length > 0)) {
          for (var i = 0; i < this.elements.length; ++i){
						this.elements[i].SetClass(newClassName);
          }
       } else {
       	  if(this.element.className != newClassName){
            this.element.oldClassName = this.element.className;
            this.element.className = this.element.className + " " + newClassName;
          }
       };
   }
};

// Elimina a class de erro aplicada ao elemento
Field.prototype.ResetClass = function(){
	if ( (this.type != "button") && (this.type != "submit") && (this.type != "reset") ) { // Ignora estes elementos do form
   		if ( (this.elements) && (this.elements.length > 0)) {
      		for (var i = 0; i < this.elements.length; ++i){
      			//trace( "1. " + this.elements[i].name + ".oldClassName=" +  this.elements[i].oldClassName, 2 );
		   			if(this.elements[i].oldClassName){
		    	  	 this.element[i].className = this.element[i].oldClassName;
		    	  }
						//this.elements[i].ResetClass();
      		}
   		} else {
				//trace( "2. " + this.element.name + ".oldClassName=" +  this.element.oldClassName, 2 );
   			if( this.element.oldClassName ){
    	  	 this.element.className = this.element.oldClassName;
    	  }
   		};
	};
};

Field.prototype._getRegEx = function(){
	var regex = this.regexp || this.element.getAttribute('regexp');
	if (regex == null)
		return null;
	retype = typeof(regex).toUpperCase();
	if (retype == "FUNCTION") {
  	return regex;
  } else if ((retype == "STRING") && !(regex == "EMAIL") && !(regex == "TEL") &&
  	!(regex == "PC") &&
  	!(regex == "ZIP") &&
  	!(regex == "RX_MONEY") &&
  	!(regex == "CREDITCARD") &&
  	!(regex == "POSTALZIP")) {
  	var nBegin = 0;
  	var nEnd = regex.length;
  	if (regex.charAt(0) == "/") {
	 		nBegin = 1;
	  }
  	if (regex.charAt(regex.length - 1) == "/") {
			nEnd = regex.length-1;
		}
  	
  	return new RegExp(regex.slice(nBegin, nEnd));
  }	else {
  	return regex;
  };
};

Field.prototype._ValidateTextField = function(arrFields){
	if ( (this.required) && (this.callback) ) {
		var nCurId = this.element.id ? this.element.id : "";
		var nCurName = this.element.name ? this.element.name : "";
		
		// Executa, caso exista, a função de callback definida
		eval("bResult = "+this.callback+"('"+nCurId+"', '"+nCurName+"', '"+this.value+"');"); 
		if (bResult == false) {
			return false;
		};
	} else {	
		//required value is empty
		if (this.required && !this.value){
	 		this.errorType = "required";
	 		return false;
	 	} else {
	 		this.errorType = "invalid";
	 	}
	 	//value less than minlength
	 	if (this.value && (this.minLength && this.value.length < this.minLength)){
	 		return false;
	 	};
	 	//value is more than maxlength
	 	if (this.value && (this.maxLength && this.value.length > this.maxLength)){
	  	return false;
	 	};
	 	//value fails regular expression
	 	if (this.regexp){
	 		if (!_checkRegExp(this.regexp, this.value)){
	 	  	//the field isn't required, but there is a value
	    	if (!this.required && this.value){
	      	return false;
	    	}
	      if (this.required){
	      	return false;
	      }
	 	  } else {
	 	  	return true;
	 	  };
	 };
	 
	 //check equality
	 if (this.equals){
	 	   for (var i = 0; i < arrFields.length; ++i){
	     	   var field = arrFields[i];
	     	   if ( (field.element.name == this.equals) || (field.element.id == this.equals) ) {
	     	   	  return (field.element.value == this.value);
	     	   	  break;
	     	   };
	     };
	 };
	 
	 //check against minvalue and maxvalue
	 if (this.minValue && this.maxValue){
	 		try{
				var fValue = parseFloat(this.value);
	      if ((this.minValue || this.maxValue) && isNaN(fValue)){
	         return false;
	      };
	      if ( (this.minValue) && (fValue < this.minValue) ) {
	         return false;
	      };
	      if ( (this.maxValue) && (fValue > this.maxValue) ) {
	         return false
	      };
			} catch(ignore){ // If an error occurs we consider the condition to be success
				return true;
			}
	    
	 };
	}
	return true;
};

Field.prototype._ValidateGroup = function(arrFields){
	if (this.required && this.values.length == 0){
		this.errorType = "required";
		return false;
	} else {
		this.errorType = "invalid";
	}
	if (this.required && this.min && this.min > this.values.length){
		return false;
	};
	if (this.required && this.max && this.max < this.values.length){
		return false;
	};
	return true;
};

function showError(strErrorMessage, objError) {
	if (objError && objError != ""){
		try{
			var strHTMLMessage;
			if (errorTemplate && errorTemplate != "") {
	  		strHTMLMessage = errorTemplate.replace("{errorMessage}", convertToHtml(strErrorMessage));
			} else {
				strHTMLMessage = convertToHtml(strErrorMessage);
			}
			
			document.getElementById(objError).style.display = "";
			document.getElementById(objError).innerHTML = strHTMLMessage;
		} catch (ignore){ // If an error occurs, use the alert method
			alert(strErrorMessage);
		}
	} else {
		alert(strErrorMessage);
	};
};

function _getErrorMessage(field){
	trace( "_getErrorMessage(" + field.name + ")    field.errorMessage=" + field.errorMessage + "   field.errorType=" + field.errorType);
	var obj = field.element;
	if ( field.errorMessage && field.errorMessage != ""){
		return field.errorMessage;
	} else if (field.errorType == "required") {
		return txtRequiredField.replace("%FIELDNAME%", (field.realname)? field.realname : ((obj.id) ? obj.id : obj.name));
	} else {
		return txtInvalidField.replace("%FIELDNAME%", (field.realname)? field.realname : ((obj.id) ? obj.id : obj.name));
	}
};

function _checkRegExp(regx, value){
  switch (regx.toUpperCase()){
  case "EMAIL":
    return ((/^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/).test(value));
  case "TEL":
    return ((/^(\(?\+?[0-9]*\)?)?[0-9_\- \(\)]*$/).test(value));
  case "POSTALCODE_PT":
    return ((/^\d{4}(\-\d{3})?$/).test(value));
  case "ZIP":
    return ((/^\d{5}$/).test(value));
  case "MONEY":
    return ((/^\d+([\.]\d\d)?$/).test(value));
	case "TIME":
    return ((/^([0-1][0-9]|[2][0-3]):([0-5][0-9])$/).test(value));
	case "MM_DD_YYYY":
	case "DD_MM_YYYY":
    try{
			var validformat=/^\d{2}\/\d{2}\/\d{4}$/ //Basic check for format validity
			if (validformat.test(value)){
				var monthfield=(regx.toUpperCase()=="DD_MM_YYYY")?value.split("/")[1]:value.split("/")[0];
				var dayfield=(regx.toUpperCase()=="DD_MM_YYYY")?value.split("/")[0]:value.split("/")[1];
				var yearfield=value.split("/")[2];
				var dayobj = new Date(yearfield, monthfield-1, dayfield)
				if ((dayobj.getMonth()+1!=monthfield)||(dayobj.getDate()!=dayfield)||(dayobj.getFullYear()!=yearfield)){
					return false;
				} else {
					return true;
				};
			} else {
				return false;
			};
		} catch (ex){
			return false;
		};
	case "DATE_TIME":
    return ((/(?=\d)^(?:(?!(?:10\D(?:0?[5-9]|1[0-4])\D(?:1582))|(?:0?9\D(?:0?[3-9]|1[0-3])\D(?:1752)))((?:0?[13578]|1[02])|(?:0?[469]|11)(?!\/31)(?!-31)(?!\.31)|(?:0?2(?=.?(?:(?:29.(?!000[04]|(?:(?:1[^0-6]|[2468][^048]|[3579][^26])00))(?:(?:(?:\d\d)(?:[02468][048]|[13579][26])(?!\x20BC))|(?:00(?:42|3[0369]|2[147]|1[258]|09)\x20BC))))))|(?:0?2(?=.(?:(?:\d\D)|(?:[01]\d)|(?:2[0-8])))))([-.\/])(0?[1-9]|[12]\d|3[01])\2(?!0000)((?=(?:00(?:4[0-5]|[0-3]?\d)\x20BC)|(?:\d{4}(?!\x20BC)))\d{4}(?:\x20BC)?)(?:$|(?=\x20\d)\x20))?((?:(?:0?[1-9]|1[012])(?::[0-5]\d){0,2}(?:\x20[aApP][mM]))|(?:[01]\d|2[0-3])(?::[0-5]\d){1,2})?$/).test(value));
	case "STRONG_PASSWORD":
		return ((/(?=^.{6,12}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).*$/).test(value));
  case "CREDITCARD":
    return (!isNaN(value));
  case "POSTALZIP":
    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));
    break;
  default:
    return (regx.test(value));
  };
};

function _hasInvalidCharacters( value ){
	var regExSource = new RegExp( /([<>])|(script)|(&lt;)|(&gt;)|(%3c)|(%3e)|(&amp;lt;)|(&amp;gt;)/ );
	if ( regExSource.test( value ) ){
		return false;
	} else {
		return true;
	}
}

// **************** Utility Methods ************************
function convertToHtml(str){
	var htmlStr = "";
	try { // Vamos usar os UL e LIs para formatar o código do sumário de erro
		htmlStr = str.substr(0, str.indexOf("\n"));
		str = str.substr(str.indexOf("\n")+1);
		htmlStr += "<ul>";
		str = str.replaceAll(" - ", "<li>");
		str = str.replaceAll("\n", "</li>");
		htmlStr += str + "</ul>";
	} catch (ignore) {
		htmlStr = str.replaceAll("\n", "<br/>");
	}
	return htmlStr;
}

String.prototype.replaceAll = function (oldStr, newStr){
	var str = this.valueOf();
	var pos = str.indexOf( oldStr );
	var limit = 100;
	while (pos >= 0){
		limit--;
		str = str.replace(oldStr, newStr);
		pos = str.indexOf( oldStr );
		if (limit <= 0) { break;} // Avoids loop cicles
	}
	return str;
}

function printArray( arr ){
	var tmp="num \t Name \t\t\t Type \n";
	for(var i=0;i<arr.length;i++){
		tmp += i + ":\t" + arr[i].name + "\t\t\t" + arr[i].type + "\n";
	};
	trace( tmp,3);
};

function _getElementIndex(arr, element){
	if (element.name) {
		for (var i = 0; i < arr.length; ++i){
			if (arr[i].element.name) { 
				if (arr[i].element.name.toLowerCase() == element.name.toLowerCase()){
					return i;
				}
			};
		};
	}
	return -1;
};

function trace( txt, errorLevel ){
	if (debug >= errorLevel){
		alert( txt );
	};
};

function _parseBoolean(value){
   return !(!value || value == 0 || value == "0" || value == "false");
};
