var LiveValidation = function(element, optionsObj){
this.initialize(element, optionsObj);
}
LiveValidation.VERSION = '1.3 standalone';
LiveValidation.TEXTAREA 		= 1;
LiveValidation.TEXT 			    = 2;
LiveValidation.PASSWORD 		= 3;
LiveValidation.CHECKBOX 		= 4;
LiveValidation.SELECT = 5;
LiveValidation.FILE = 6;
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("LiveValidation::initialize - No element reference or element id has been provided!");
this.element = element.nodeName ? element : document.getElementById(element);
if(!this.element) throw new Error("LiveValidation::initialize - No element with reference or id of '" + element + "' exists!");
this.validations = [];
this.elementType = this.getElementType();
this.form = this.element.form;
var options = optionsObj || {};
this.validMessage = options.validMessage || 'Thankyou!';
var node = options.insertAfterWhatNode || this.element;
this.insertAfterWhatNode = node.nodeType ? node : document.getElementById(node);
this.onValid = options.onValid || function(){ this.insertMessage(this.createMessageSpan(options)); this.addFieldClass(); };
this.onInvalid = options.onInvalid || function(){ this.insertMessage(this.createMessageSpan(options)); 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.oldOnFocus = this.element.onfocus || function(){};
this.oldOnBlur = this.element.onblur || function(){};
this.oldOnClick = this.element.onclick || function(){};
this.oldOnChange = this.element.onchange || function(){};
this.oldOnKeyup = this.element.onkeyup || function(){};
this.element.onfocus = function(e){ self.doOnFocus(e); return self.oldOnFocus.call(this, e); }
if(!this.onlyOnSubmit){
switch(this.elementType){
case LiveValidation.CHECKBOX:
this.element.onclick = function(e){ self.validate(); return self.oldOnClick.call(this, e); }
case LiveValidation.SELECT:
case LiveValidation.FILE:
this.element.onchange = function(e){ self.validate(); return self.oldOnChange.call(this, e); }
break;
default:
if(!this.onlyOnBlur) this.element.onkeyup = function(e){ self.deferValidation(); return self.oldOnKeyup.call(this, e); }
this.element.onblur = function(e){ self.doOnBlur(e); return self.oldOnBlur.call(this, e); }
}
}
},
destroy: function(){
if(this.formObj){
this.formObj.removeField(this);
this.formObj.destroy();
}
this.element.onfocus = this.oldOnFocus;
if(!this.onlyOnSubmit){
switch(this.elementType){
case LiveValidation.CHECKBOX:
this.element.onclick = this.oldOnClick;
case LiveValidation.SELECT:
case LiveValidation.FILE:
this.element.onchange = this.oldOnChange;
break;
default:
if(!this.onlyOnBlur) this.element.onkeyup = this.oldOnKeyup;
this.element.onblur = this.oldOnBlur;
}
}
this.validations = [];
this.removeMessageAndFieldClass();
},
add: function(validationFunction, validationParamsObj){
this.validations.push( {type: validationFunction, params: validationParamsObj || {} } );
return this;
},
remove: function(validationFunction, validationParamsObj){
var found = false;
for( var i = 0, len = this.validations.length; i < len; i++ ){
if( this.validations[i].type == validationFunction ){
if (this.validations[i].params == validationParamsObj) {
found = true;
break;
}
}
}
if(found) this.validations.splice(i,1);
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.toUpperCase() == 'TEXTAREA'):
return LiveValidation.TEXTAREA;
case (this.element.nodeName.toUpperCase() == 'INPUT' && this.element.type.toUpperCase() == 'TEXT'):
return LiveValidation.TEXT;
case (this.element.nodeName.toUpperCase() == 'INPUT' && this.element.type.toUpperCase() == 'PASSWORD'):
return LiveValidation.PASSWORD;
case (this.element.nodeName.toUpperCase() == 'INPUT' && this.element.type.toUpperCase() == 'CHECKBOX'):
return LiveValidation.CHECKBOX;
case (this.element.nodeName.toUpperCase() == 'INPUT' && this.element.type.toUpperCase() == 'FILE'):
return LiveValidation.FILE;
case (this.element.nodeName.toUpperCase() == 'SELECT'):
return LiveValidation.SELECT;
case (this.element.nodeName.toUpperCase() == 'INPUT'):
throw new Error('LiveValidation::getElementType - Cannot use LiveValidation on an ' + this.element.type + ' input!');
default:
throw new Error('LiveValidation::getElementType - Element must be an input, select, or 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('LiveValidation::validateElement - Element to validate acceptance must be a 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(){
if(!this.element.disabled){
var isValid = this.doValidations();
if(isValid){
this.onValid();
return true;
}else {
this.onInvalid();
return false;
}
}else{
return true;
}
},
enable: function(){
this.element.disabled = false;
return this;
},
disable: function(){
this.element.disabled = true;
this.removeMessageAndFieldClass();
return this;
},
createMessageSpan: function(options){
  
  var span = document.createElement('span');
  var imgNode = document.createElement('img');

  if(options == "user_email"){
    imgNode.id = "status_image_email";
  } else{
      imgNode.id = "status_image";
  }

  
  if(this.message == "Thankyou!"){
    imgNode.src = "/images/animatedgif.gif";
    imgNode.style.width="15px";
    imgNode.style.height="15px";
    imgNode.style.verticalAlign = "top";
    span.appendChild(imgNode);
    if(options == "user_login"){
      checked_new_user = true;
      new Ajax.Request('/usersname/exists', {asynchronous:true, evalScripts:true, parameters:'new_user=' + document.getElementById(options).value });
    }
    else if(options == "user_email"){
      checked_new_email = true;
      new Ajax.Request('/email/exists', {asynchronous:true, evalScripts:true, parameters:'new_user_email=' + document.getElementById(options).value });
    }
    else {
      imgNode.src = "/images/icon_confirm_green.png";
      // imgNode.setAttribute('style', 'float:right;');
      span.appendChild(imgNode);
    }
  }
  else{		
    if(options == "true"){
     	var textNode = document.createTextNode(this.message);
      span.setAttribute("style", "position:relative;border:0px solid green;font-weight:bold;font-size:11px;color:red;float:left;margin-left:53px;");
      var br = document.createElement("div");
      span.appendChild(textNode);      
    }
    else{
      if(options == "user_email"){
        imgNode.style.paddingTop = "10px";
        if(document.getElementById('email_confirmation_icon'))
          document.getElementById('email_confirmation_icon').style.display ='none'
      }
      imgNode.src = "/images/icon_wrong.png";		
      imgNode.style.verticalAlign = "top";
      span.appendChild(imgNode);	
    }
  }
  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.instances = {};
LiveValidationForm.getInstance = function(element){
var rand = Math.random() * Math.random();
if(!element.id) element.id = 'formId_' + rand.toString().replace(/\./, '') + new Date().valueOf();
if(!LiveValidationForm.instances[element.id]) LiveValidationForm.instances[element.id] = new LiveValidationForm(element);
return LiveValidationForm.instances[element.id];
}

LiveValidationForm.prototype = {
initialize: function(element){
this.name = element.id;
this.element = element;
this.fields = [];
// preserve the old onsubmit event
this.oldOnSubmit = this.element.onsubmit || function(){};
var self = this;
this.element.onsubmit = function(e){
return (LiveValidation.massValidate(self.fields)) ? self.oldOnSubmit.call(this, e || window.event) !== false : false;
}
},
addField: function(newField){
this.fields.push(newField);
},
removeField: function(victim){
var victimless = [];
for( var i = 0, len = this.fields.length; i < len; i++){
if(this.fields[i] !== victim) victimless.push(this.fields[i]);
}
this.fields = victimless;
},
destroy: function(force){
if (this.fields.length != 0 && !force) return false;
this.element.onsubmit = this.oldOnSubmit;
LiveValidationForm.instances[this.name] = null;
return true;
}
}
var Validate = {
Presence: function(value, paramsObj){
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Can't be empty!";
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 || "Must be a number!";
var notAnIntegerMessage = paramsObj.notAnIntegerMessage || "Must be an integer!";
var wrongNumberMessage = paramsObj.wrongNumberMessage || "Must be " + is + "!";
var tooLowMessage = paramsObj.tooLowMessage || "Must not be less than " + minimum + "!";
var tooHighMessage = paramsObj.tooHighMessage || "Must not be more than " + 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 || "Not valid!";
var pattern = paramsObj.pattern || /./;
var negate = paramsObj.negate || false;
if(!negate && !pattern.test(value)) Validate.fail(message); // normal
if(negate && pattern.test(value)) Validate.fail(message); // negated
return true;
},
Email: function(value, paramsObj){
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Must be a valid email address!";
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 || "Must be " + is + " characters long!";
var tooShortMessage = paramsObj.tooShortMessage || "Must not be less than " + minimum + " characters long!";
var tooLongMessage = paramsObj.tooLongMessage || "Must not be more than " + maximum + " characters long!";
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("Validate::Length - Length(s) to validate against must be provided!");
}
return true;
},
Inclusion: function(value, paramsObj){
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Must be included in the list!";
var caseSensitive = (paramsObj.caseSensitive === false) ? false : true;
if(paramsObj.allowNull && value == null) return true;
if(!paramsObj.allowNull && value == null) Validate.fail(message);
var within = paramsObj.within || [];
if(!caseSensitive){ 
var lowerWithin = [];
for(var j = 0, length = within.length; j < length; ++j){
var item = within[j];
if(typeof item == 'string') item = item.toLowerCase();
lowerWithin.push(item);
}
within = lowerWithin;
if(typeof value == 'string') value = value.toLowerCase();
}
var found = false;
for(var i = 0, length = within.length; i < length; ++i){
if(within[i] == value) found = true;
if(paramsObj.partialMatch){ 
if(value.indexOf(within[i]) != -1) found = true;
}
}
if( (!paramsObj.negate && !found) || (paramsObj.negate && found) ) Validate.fail(message);
return true;
},
Exclusion: function(value, paramsObj){
var paramsObj = paramsObj || {};
paramsObj.failureMessage = paramsObj.failureMessage || "Must not be included in the list!";
paramsObj.negate = true;
Validate.Inclusion(value, paramsObj);
return true;
},
Confirmation: function(value, paramsObj){
if(!paramsObj.match) throw new Error("Validate::Confirmation - Error validating confirmation: Id of element to match must be provided!");
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Does not match!";
var match = paramsObj.match.nodeName ? paramsObj.match : document.getElementById(paramsObj.match);
if(!match) throw new Error("Validate::Confirmation - There is no reference with name of, or element with id of '" + paramsObj.match + "'!");
if(value != match.value){ 
Validate.fail(message);
}
return true;
},
Acceptance: function(value, paramsObj){
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Must be accepted!";
if(!value){ 
Validate.fail(message);
}
return true;
},
Custom: function(value, paramsObj){
var paramsObj = paramsObj || {};
var against = paramsObj.against || function(){ return true; };
var args = paramsObj.args || {};
var message = paramsObj.failureMessage || "Not valid!";
if(!against(value, args)) Validate.fail(message);
return true;
},
now: function(validationFunction, value, validationParamsObj){
if(!validationFunction) throw new Error("Validate::now - Validation function must be provided!");
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';
}
}
