var Validation = {
	addMethod : function(name, testFunction){
		this.methods[name] = testFunction;
	}
};
Validation.methods = {
	digits : function() {return !(/[^\d]/.test(this)); },
	pattern : function(opt) {return opt.test(this);},
	notBlank : function(){return !this.blank();},
	notEmpty : function(){return !this.empty();},
	minLength : function(opt) {return this.length >= opt;},
	maxLength : function(opt) {return this.length <= opt;},
	min : function(opt) {return this >= parseFloat(opt);}, 
	max : function(opt) {return this <= parseFloat(opt);},
	notOneOf : function(opt) {return $A(opt).all(function(value) {
		return this != value;
	});},
	oneOf : function(opt) {return $A(opt).any(function(value) {
		return this == value;
	});},
	is : function(opt) {return this == opt;},
	isNot : function(opt) {return this != opt;},
	equalToField : function(opt) {return this == $F(opt);},
	notEqualToField : function(opt) {return this != $F(opt);},
	email : function () {
        var regex = /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i; // /^((\"[^\"\f\n\r\t\v\b]+\")|([\w\!\#\$\%\&'\*\+\-\~\/\^\`\|\{\}]+(\.[\w\!\#\$\%\&'\*\+\-\~\/\^\`\|\{\}]+)*))@((\[(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))\])|(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))|((([A-Za-z0-9\-])+\.)+[A-Za-z\-]+))$/;
        return  regex.test(this);
      },
	checked : function(){return document.getElementById(this).checked; /*$ doesn't work here*/}
};

var Validator = Class.create();
Validator.prototype = {
  initialize: function(conditions, options) {
		this.conditions = conditions;
		this.observers = {};
		this.form = null;
		this.options = Object.extend({
			observe : true,
			beforeValidate : null,
			afterValidate : null,
			beforeElementValidate : null,
			afterElementValidate : null
		}, options || {});
		if(this.options.observe){
			this.initConditions(this.conditions);
		}
  },
	initConditions: function(conditions){ 
		$H(conditions).each(function(item){ 
			var elementId = item.key; 
			var events = item.value;
			$H(events).each(function(event){ 
				var eventType = event.key;
				var parameters = $A(event.value);
				parameters.each(function(condition){ 
					this.addObserver(elementId, eventType, condition);
				}.bind(this));
			}.bind(this));
		}.bind(this));
	},
	
	addObserver: function(elementId, eventType, condition){
		var element = $(elementId);
		if(!(element && condition)){ return false; }
		if(!this.observers[elementId]){
			this.observers[elementId] = {};
		}
		if(!this.observers[elementId][eventType]){
			this.observers[elementId][eventType] = true;
			if(eventType=="submit"){
				var form = element.up("form");
				if(!this.form){
					this.form = form;
					this.form.observe("submit", this.checkForm.bindAsEventListener(this, this.form), false);
				}
			}else{
				element.observe(eventType, this.checkElementOnEvent.bindAsEventListener(this, elementId), false);
			}
		}
	},
	_beforeValidateInternal: function(form){
		try{form.getInputs("submit")[0].disable();}catch(e){}
	},
	validateAll: function(callback){ 
	// check all conditions of all elements of all event types
	},
	validateAllByType: function(type, callback){ 
	// check conditions of all elements by event type
		var errors = [];
		var elements = $H(this.conditions).keys();
		elements.each(function(elementId){
			if(!this.conditions[elementId][type]){return false;}
			for (var index = 0; index < this.conditions[elementId][type].length; ++index) {
				var condition = this.conditions[elementId][type][index];
				var test = this.checkCondition(elementId, condition);
				if(!test){
					var result = {element: elementId, eventType: type, condition: condition};
					errors.push(result);
				}
			}
		}.bind(this));
		if(callback){callback(errors);}
		return errors;
	},
	validateElement: function(elementId, callback){
	// check element's all types conditions
		var errors = [];
		var types = $H(this.conditions[elementId]).keys();
		types.each(function(type){
			if(!this.conditions[elementId][type]){return false;}
			for (var index = 0; index < this.conditions[elementId][type].length; ++index) {
				var condition = this.conditions[elementId][type][index];
				var test = this.checkCondition(elementId, condition);
				if(!test){
					var result = {element: elementId, eventType: type, condition: condition};
					errors.push(result);
				}
			}
		}.bind(this));
		return errors;
	},
	validateElementByType: function(elementId, type, callback){
	// check element's conditions of given type
		var errors = [];
		for (var index = 0; index < this.conditions[elementId][type].length; ++index) {
			var condition = this.conditions[elementId][type][index];
			var test = this.checkCondition(elementId, condition);
			if(!test){
				var result = {element: elementId, eventType: type, condition: condition};
				errors.push(result);
			}
		}
		return errors;
	},
	checkCondition: function(elementId, condition){
		var methodName = condition.split("(", 1)[0].strip();
		var params = condition.sub(methodName, '') || "()";
		var thisPointer = methodName=="checked" ? "elementId" : "$F(elementId)";
		var method = "Validation.methods."+methodName+".bind("+thisPointer+")"+params+";";
		eval("var test = " + method);
		return test;		
	},
	checkForm: function(event, form){
		this._beforeValidateInternal(form);
		if(this.options.beforeValidate){this.options.beforeValidate(form);}
		var formErrors = [];
		var elementsOnSubmit = $H(this.conditions).inject([], function(array, item) {
			if(item.value.submit && $(item.key) && $(item.key).childOf(this.form)){array.push(item.key);}
			return array;
		}.bind(this));
		elementsOnSubmit.each(function(elementId){
			var elementErrors = this.checkElementOnEvent(event, elementId);
			if(elementErrors && elementErrors.size()){formErrors = formErrors.concat(elementErrors);}
		}.bind(this));
		if(formErrors.size() && !this.options.afterValidate){Event.stop(event);}
		this._afterValidateInternal(form, formErrors);
		if(this.options.afterValidate){this.options.afterValidate(formErrors, event);}
	},
	_afterValidateInternal: function(form, formErrors){
		//try{if(formErrors.size()){form.getInputs("submit")[0].enable();}}catch(e){}
    try{form.getInputs("submit")[0].enable();}catch(e){}
	},
	checkElementOnEvent: function(event, elementId){
		if(this.options.beforeElementValidate){this.options.beforeElementValidate(elementId);}
		var errors = this.validateElementByType(elementId, event.type);
		if(this.options.afterElementValidate){this.options.afterElementValidate(event, elementId, errors);}
		return errors;
		
	}
};
function commonSubmitCallback (errors, event){
	if(!errors.size()){return true;}
	if(event){Event.stop(event);}
	var elementsWithErrors = errors.pluck("element").uniq();
	if(elementsWithErrors && elementsWithErrors.size()){
		var firstElement = $(elementsWithErrors.first()); 
		firstElement.activate();
		try {new Effect.Shake(firstElement.up("fieldset"));} catch(e){}
		elementsWithErrors.each(function(element){
			try{new Effect.Highlight(element, {queue: {position:'end', scope: "sc"+element, limit:1}, duration: 0.4, startcolor: "#EB26A3"});} catch(e){}
		});
	}
	return false;
}
function commonElementCallback (event, elementId, errors){
	if(event && event.type.toLowerCase()=="submit"){return true;}
	if(!errors.size()){return true;}
	if($F(elementId).empty() && !errors.any(function(error){return error.condition=="notEmpty";})){
		return true;
	}
	if($(elementId) && $(elementId).tooltip && $(elementId).tooltip.focus){
		$(elementId).tooltip.focus.show();
	}
	try {new Effect.Shake(elementId);} catch(e){}
	try{new Effect.Highlight(elementId, {
							queue: {position:'end', scope: "sc"+elementId, limit:1}, 
							duration: 0.4,
							startcolor: "#EB26A3"
			});} catch(e){}
	return false;
}

function required(){
	var ids = $A(arguments);
	var conditions = {};
	var notBlank = {submit: ["notBlank"]};
	ids.each(function(elementId){
		conditions[elementId] = notBlank;
	});
	return conditions;
}
