var LiveValidation = function(element, optionsObj){
this.initialize(element, optionsObj);
}
LiveValidation.TEXTAREA     = 1;
LiveValidation.TEXT 		    = 2;
LiveValidation.PASSWORD  = 3;
LiveValidation.CHECKBOX   = 4;
LiveValidation.SELECT         = 5;

LiveValidation.massValidate = function(validations){
var returnValue = true;
for(var i = 0, len = validations.length; i < len; ++i ){
var valid = validations[i].validate();
if(returnValue) returnValue = valid;
}
return returnValue;
}
LiveValidation.prototype = {
validClass: 'LV_valid',
invalidClass: 'LV_invalid',
messageClass: 'LV_validation_message',
validFieldClass: 'LV_valid_field',
invalidFieldClass: 'LV_invalid_field',
initialize: function(element, optionsObj){
var self = this;
if(!element) throw new Error("Verificare Elemento ID");
this.element = element.nodeName ? element : document.getElementById(element);
if(!this.element) throw new Error("Elemento mancante o ID di '" + element + "' esiste");
this.validations = [];
this.elementType = this.getElementType();
this.form = this.element.form;
var options = optionsObj || {};
this.validMessage = options.validMessage || 'Grazie';
this.insertAfterWhatNode = options.insertAfterWhatNode || this.element;
this.onValid = options.onValid || function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); };
this.onInvalid = options.onInvalid || function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); };	
this.onlyOnBlur =  options.onlyOnBlur || false;
this.wait = options.wait || 0;
this.onlyOnSubmit = options.onlyOnSubmit || false;
if(this.form){
this.formObj = LiveValidationForm.getInstance(this.form);
this.formObj.addField(this);
}
this.element.onfocus = function(e){ self.doOnFocus(); }
if(!this.onlyOnSubmit){
switch(this.elementType){
case LiveValidation.CHECKBOX:
this.element.onclick = function(e){ self.validate(); }
case LiveValidation.SELECT:
this.element.onchange = function(e){ self.validate(); }
break;
default:
if(!this.onlyOnBlur) this.element.onkeyup = function(e){ self.deferValidation(); }
this.element.onblur = function(e){ self.doOnBlur(); }
}
}
},
add: function(validationFunction, validationParamsObj){
this.validations.push( {type: validationFunction, params: validationParamsObj || {} } );
return this;
},
deferValidation: function(e){
if(this.wait >= 300) this.removeMessageAndFieldClass();
var self = this;
if(this.timeout) clearTimeout(self.timeout);
this.timeout = setTimeout( function(){ self.validate() }, self.wait); 
},
doOnBlur: function(e){
this.focused = false;
this.validate(e);
},
doOnFocus: function(e){
this.focused = true;
this.removeMessageAndFieldClass()
},
getElementType: function(){
switch(true){
case (this.element.nodeName == 'TEXTAREA'):
return LiveValidation.TEXTAREA;
case (this.element.nodeName == 'INPUT' && this.element.type == 'text'):
return LiveValidation.TEXT;
case (this.element.nodeName == 'INPUT' && this.element.type == 'password'):
return LiveValidation.PASSWORD;
case (this.element.nodeName == 'INPUT' && this.element.type == 'checkbox'):
return LiveValidation.CHECKBOX;
case (this.element.nodeName == 'SELECT'):
return LiveValidation.SELECT;
case (this.element.nodeName == 'INPUT'):
throw new Error('Non puoi usareil programma in un ' + this.element.type + ' input');
default:
throw new Error('Questo Elemento deve essere un input, select, o textarea');
}
},
doValidations: function(){
this.validationFailed = false;
for(var i = 0, len = this.validations.length; i < len; ++i){
var validation = this.validations[i];
switch(validation.type){
case Validate.Presence:
case Validate.Confirmation:
case Validate.Acceptance:
this.displayMessageWhenEmpty = true;
this.validationFailed = !this.validateElement(validation.type, validation.params); 
break;
default:
this.validationFailed = !this.validateElement(validation.type, validation.params);
break;
}
if(this.validationFailed) return false;	
}
this.message = this.validMessage;
return true;
},
validateElement: function(validationFunction, validationParamsObj){
var value = (this.elementType == LiveValidation.SELECT) ? this.element.options[this.element.selectedIndex].value : this.element.value;     
if(validationFunction == Validate.Acceptance){
if(this.elementType != LiveValidation.CHECKBOX) throw new Error('Elemento non valido: deve essere un checkbox!');
value = this.element.checked;
}
var isValid = true;
try{    
validationFunction(value, validationParamsObj);
} catch(error) {
if(error instanceof Validate.Error){
if( value !== '' || (value === '' && this.displayMessageWhenEmpty) ){
this.validationFailed = true;
this.message = error.message;
isValid = false;
}
}else{
throw error;
}
}finally{
return isValid;
}
},
validate: function(){
var isValid = this.doValidations();
if(isValid){
this.onValid();
return true;
}else{
this.onInvalid();
return false;
}
},
createMessageSpan: function(){
var span = document.createElement('span');
var textNode = document.createTextNode(this.message);
span.appendChild(textNode);
return span;
},
insertMessage: function(elementToInsert){
this.removeMessage();
if( (this.displayMessageWhenEmpty && (this.elementType == LiveValidation.CHECKBOX || this.element.value == ''))
|| this.element.value != '' ){
var className = this.validationFailed ? this.invalidClass : this.validClass;
elementToInsert.className += ' ' + this.messageClass + ' ' + className;
if(this.insertAfterWhatNode.nextSibling){
this.insertAfterWhatNode.parentNode.insertBefore(elementToInsert, this.insertAfterWhatNode.nextSibling);
}else{
this.insertAfterWhatNode.parentNode.appendChild(elementToInsert);
}
}
},
addFieldClass: function(){
this.removeFieldClass();
if(!this.validationFailed){
if(this.displayMessageWhenEmpty || this.element.value != ''){
if(this.element.className.indexOf(this.validFieldClass) == -1) this.element.className += ' ' + this.validFieldClass;
}
}else{
if(this.element.className.indexOf(this.invalidFieldClass) == -1) this.element.className += ' ' + this.invalidFieldClass;
}
},
removeMessage: function(){
var nextEl;
var el = this.insertAfterWhatNode;
while(el.nextSibling){
if(el.nextSibling.nodeType === 1){
nextEl = el.nextSibling;
break;
}
el = el.nextSibling;
}
if(nextEl && nextEl.className.indexOf(this.messageClass) != -1) this.insertAfterWhatNode.parentNode.removeChild(nextEl);
},
removeFieldClass: function(){
if(this.element.className.indexOf(this.invalidFieldClass) != -1) this.element.className = this.element.className.split(this.invalidFieldClass).join('');
if(this.element.className.indexOf(this.validFieldClass) != -1) this.element.className = this.element.className.split(this.validFieldClass).join(' ');
},
removeMessageAndFieldClass: function(){
this.removeMessage();
this.removeFieldClass();
}
}
var LiveValidationForm = function(element){
this.initialize(element);
}
LiveValidationForm.getInstance = function(element){
if(!element.id) element.id = 'formId_' + new Date().valueOf();
if(!window['LiveValidationForm_' + element.id]) window['LiveValidationForm_' + element.id] = new LiveValidationForm(element);
return window['LiveValidationForm_' + element.id];
}
LiveValidationForm.prototype = {
initialize: function(element){
this.element = element;
this.fields = [];
var self = this;
this.element.onsubmit = function(){
return LiveValidation.massValidate(self.fields);
}
},
addField: function(newField){
this.fields.push(newField);
}
}
var Validate = {
Presence: function(value, paramsObj){
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Obbligatorio";
if(value === '' || value === null || value === undefined){ 
Validate.fail(message);
}
return true;
},
Numericality: function(value, paramsObj){
var suppliedValue = value;
var value = Number(value);
var paramsObj = paramsObj || {};
var minimum = ((paramsObj.minimum) || (paramsObj.minimum == 0)) ? paramsObj.minimum : null;;
var maximum = ((paramsObj.maximum) || (paramsObj.maximum == 0)) ? paramsObj.maximum : null;
var is = ((paramsObj.is) || (paramsObj.is == 0)) ? paramsObj.is : null;
var notANumberMessage = paramsObj.notANumberMessage || "Deve essere un Numero";
var notAnIntegerMessage = paramsObj.notAnIntegerMessage || "Deve essere un Intero";
var wrongNumberMessage = paramsObj.wrongNumberMessage || "Deve essere  " + is + "!";
var tooLowMessage = paramsObj.tooLowMessage || "Deve essere inferiorre a " + minimum + "!";
var tooHighMessage = paramsObj.tooHighMessage || "Non deve essere oltre " + maximum + "!";
if (!isFinite(value)) Validate.fail(notANumberMessage);
if (paramsObj.onlyInteger && (/\.0+$|\.$/.test(String(suppliedValue))  || value != parseInt(value)) ) Validate.fail(notAnIntegerMessage);
switch(true){
case (is !== null):
if( value != Number(is) ) Validate.fail(wrongNumberMessage);
break;
case (minimum !== null && maximum !== null):
Validate.Numericality(value, {tooLowMessage: tooLowMessage, minimum: minimum});
Validate.Numericality(value, {tooHighMessage: tooHighMessage, maximum: maximum});
break;
case (minimum !== null):
if( value < Number(minimum) ) Validate.fail(tooLowMessage);
break;
case (maximum !== null):
if( value > Number(maximum) ) Validate.fail(tooHighMessage);
break;
}
return true;
},
Format: function(value, paramsObj){
var value = String(value);
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Non valido!";
var pattern = paramsObj.pattern || /./;
if(!pattern.test(value) /* && value != ''*/ ){ 
Validate.fail(message);
}
return true;
},
Email: function(value, paramsObj){
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Inserire un Indirizzo Email valido";
Validate.Format(value, { failureMessage: message, pattern: /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i } );
return true;
},
Length: function(value, paramsObj){
var value = String(value);
var paramsObj = paramsObj || {};
var minimum = ((paramsObj.minimum) || (paramsObj.minimum == 0)) ? paramsObj.minimum : null;
var maximum = ((paramsObj.maximum) || (paramsObj.maximum == 0)) ? paramsObj.maximum : null;
var is = ((paramsObj.is) || (paramsObj.is == 0)) ? paramsObj.is : null;
var wrongLengthMessage = paramsObj.wrongLengthMessage || "Deve essere " + is + " caratteri";
var tooShortMessage = paramsObj.tooShortMessage || " !!" + minimum + " caratteri minimo";
var tooLongMessage = paramsObj.tooLongMessage || " !!" + maximum + " caratteri massimo";
switch(true){
case (is !== null):
if( value.length != Number(is) ) Validate.fail(wrongLengthMessage);
break;
case (minimum !== null && maximum !== null):
Validate.Length(value, {tooShortMessage: tooShortMessage, minimum: minimum});
Validate.Length(value, {tooLongMessage: tooLongMessage, maximum: maximum});
break;
case (minimum !== null):
if( value.length < Number(minimum) ) Validate.fail(tooShortMessage);
break;
case (maximum !== null):
if( value.length > Number(maximum) ) Validate.fail(tooLongMessage);
break;
default:
throw new Error("Provvedere a validare la Lunghezza");
}
return true;
},
Inclusion: function(value, paramsObj){
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Deve essere incluso nella Lista";
if(paramsObj.allowNull && value == null) return true;
if(!paramsObj.allowNull && value == null) Validate.fail(message)
var list = paramsObj.within || [];
var found = false;
for(var i = 0, length = list.length; i < length; ++i){
if(list[i] == value) found = true;
if(paramsObj.partialMatch){ 
if(value.indexOf(list[i]) != -1) found = true;
}
}
if( (!paramsObj.exclusion && !found) || (paramsObj.exclusion && found) ) Validate.fail(message);
return true;
},
Exclusion: function(value, paramsObj){
var paramsObj = paramsObj || {};
paramsObj.failureMessage = paramsObj.failureMessage || "Non deve essere incluso nella Lista";
paramsObj.exclusion = true;
Validate.Inclusion(value, paramsObj);
return true;
},
Confirmation: function(value, paramsObj){
if(!paramsObj.match) throw new Error("Errore di conferma Validazione Elemento ID");
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Inseistente";
var match = paramsObj.match.nodeName ? paramsObj.match : document.getElementById(paramsObj.match);
if(!match) throw new Error("Conferma Validazione: Errore nome di riferimento, o elemento con ID '" + paramsObj.match + "'!");
if(value != match.value){ 
Validate.fail(message);
}
return true;
},
Acceptance: function(value, paramsObj){
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Deve essere Accettato";
if(!value){ 
Validate.fail(message);
}
return true;
},
now: function(validationFunction, value, validationParamsObj){
if(!validationFunction) throw new Error("La funzione Validazione deve essere Accettata");
var isValid = true;
try{
validationFunction(value, validationParamsObj || {});
} catch(error) {
if(error instanceof Validate.Error){
isValid =  false;
}else{
throw error;
}
}finally{ 
return isValid 
}
},
fail: function(errorMessage){
throw new Validate.Error(errorMessage);
},
Error: function(errorMessage){
this.message = errorMessage;
this.name = 'ValidationError';
}
}
