var InputValidator = new Class({
	initialize: function(className, options){
		this.setOptions({
			errorMsg: 'Validation failed.',
			test: function(field){return true}
		}, options);
		this.className = className;
	},
	test: function(field){
		if($(field)) return this.options.test($(field), this.getProps(field));
		else return false;
	},
	getError: function(field){
		var err = this.options.errorMsg;
		if($type(err) == "function") err = err($(field), this.getProps(field));
		return err;
	},
	getProps: function(field)
	{
		if($(field))
		{
			var result = field.className.match( /\([^)]+\)/);
			if( result)
			{
     			result = result[0].substring( 1, result[0].length-1);
                try 
                {
				    return Json.evaluate( '{'+result+'}');
			    }
			    catch(e)
			    {
			    	return {}
			    }
		    } 
		    else 
		    {
			    return {}
		    }
	}
	}
});
InputValidator.implement(new Options);
var FormValidator = new Class({
	options: {
		fieldSelectors:"input, select, textarea",
		useTitles:false,
		evaluateOnSubmit:true,
		evaluateFieldsOnBlur: true,
		evaluateFieldsOnChange: true,
		serial: true,
		warningPrefix: "",
		errorPrefix: "",
		onFormValidate: function(isValid, form){},
		onElementValidate: function(isValid, field){}
	},
	initialize: function(form, options){
		this.setOptions(options);
		try {
			this.form = $(form);
			if(this.options.evaluateOnSubmit) this.form.addEvent('submit', this.onSubmit.bind(this));
			if(this.options.evaluateFieldsOnBlur) this.watchFields();
		}catch(e){//console.log('error: %s', e);
		}
	},
	getFields: function(){
		return this.fields = this.form.getElementsBySelector(this.options.fieldSelectors)
	},
	watchFields: function(){
		try{
			this.getFields().each(function(el){
					el.addEvent('blur', this.validateField.pass([el, false], this));
				if(this.options.evaluateFieldsOnChange)
					el.addEvent('change', this.validateField.pass([el, true], this));
			}, this);
		}catch(e){//console.log('error: %s', e);
		}
	},
	onSubmit: function(event){
		if(!this.validate()) new Event(event).stop();
		else {
			this.stop();
			this.reset();
		}
	},
	reset: function() {
		this.getFields().each(this.resetField, this);
	}, 
	validate : function() {
		var result = this.getFields().map(function(field) { return this.validateField(field, true); }, this);
		result = result.every(function(val){
			return val;
		});
		this.fireEvent('onFormValidate', [result, this.form]);
		return result;
	},
	validateField: function(field, force){
		if(this.paused) return true;
		field = $(field);
		var result = true;
		var failed = this.form.getElement('.validation-failed');
		var warned = this.form.getElement('.warning');
		//if the field is defined
		//if there aren't any failed
		//or if there are failed and it's not serial
		//or force
		//then validate
		if(field && (!failed || force || (failed && !this.options.serial))){
			var validators = field.className.split(" ").some(function(cn){
				return this.getValidator(cn);
			}, this);
			result = field.className.split(" ").map(function(className){
				return this.test(className,field);
			}, this);
			result = result.every(function(val){
				return val;
			});
			if (validators && !field.hasClass('warnOnly')){
			    field=$(field);
				if(result) 
				{
				    field.addClass('validation-passed');
				    field.removeClass('validation-failed');
				}
				else 
				{
				    field.addClass('validation-failed');
				    field.removeClass('validation-passed');
				}
			}
			if(!warned || force || (warned && !this.options.serial)) {
				var warnings = field.className.split(" ").some(function(cn){
					if(cn.test('^warn-') || field.hasClass('warnOnly')) return this.getValidator(cn.replace(/^warn-/,""));
					return null;
				}, this);
				field.removeClass('warning');
				var warnResult = field.className.split(" ").map(function(cn){
					if(cn.test('^warn-') || field.hasClass('warnOnly')) return this.test(cn.replace(/^warn-/,""), field, true);
					return null;
				}, this);
			}
		}
		return result;
	},
	getPropName: function(className){
		return '__advice'+className;
	},
	test: function(className, field, warn){
		if(field.hasClass('ignoreValidation')) return true;
		warn = $pick(warn, false);
		if(field.hasClass('warnOnly')) warn = true;
		field = $(field);
		var isValid = true;
		if(field) {
			var validator = this.getValidator(className);
			if(validator && this.isVisible(field)) {
				isValid = validator.test(field);
				//if the element is visible and it failes to validate
				if(!isValid && validator.getError(field)){
					if(warn) field.addClass('warning');
					var advice = this.makeAdvice(className, field, validator.getError(field), warn);
					this.insertAdvice(advice, field);
					this.showAdvice(className, field);
				} else this.hideAdvice(className, field);
				this.fireEvent('onElementValidate', [isValid, field]);
			}
		}
		if(warn) return true;
		return isValid;
	},
	showAdvice: function(className, field){
		var advice = this.getAdvice(className, field);
		if(advice && !field[this.getPropName(className)] && (advice.getStyle('display') == "none" || advice.getStyle('visiblity') == "hidden" || advice.getStyle('opacity')==0)){
			field[this.getPropName(className)] = true;
			//if element.cnet.js is present, transition the advice in
			if(advice.smoothShow) advice.smoothShow();
			else advice.setStyle('display','block');
		}
	},
	hideAdvice: function(className, field){
		var advice = this.getAdvice(className, field);
		if(advice && field[this.getPropName(className)]) {
			field[this.getPropName(className)] = false;
			//if element.cnet.js is present, transition the advice out
			if(advice.smoothHide) advice.smoothHide();
			else advice.setStyle('display','none');
		}
	},
	isVisible : function(field) {
		while(field.tagName != 'BODY') {
			if($(field).getStyle('display') == "none") return false;
			field = field.parentNode;
		}
		return true;
	},
	getAdvice: function(className, field) {
		return $('advice-' + className + '-' + this.getFieldId(field))
	},
	makeAdvice: function(className, field, error, warn){
		var errorMsg = (warn)?this.options.warningPrefix:this.options.errorPrefix;
				errorMsg += (this.options.useTitles) ? $pick(field.title, error):error;
		var advice = this.getAdvice(className, field);
		if(!advice){
			var cssClass = (warn)?'warning-advice':'validation-advice';
			advice = new Element('div').addClass(cssClass).setProperty(
				'id','advice-'+className+'-'+this.getFieldId(field)).setStyle('display','none').appendText(errorMsg);
		} else{
			advice.setHTML(errorMsg);
		}
		return advice;
	},
	insertAdvice: function(advice, field){
		switch (field.type.toLowerCase()) {
			case 'radio':
				var p = $(field.parentNode);
				if(p) {
					p.adopt(advice);
					break;
				}
			default: advice.injectAfter($(field));
	  };
	},
	getFieldId : function(field) {
		return field.id ? field.id : field.id = "input_"+field.name;
	},
	resetField: function(field) {
		field = $(field);
		if(field) {
			var cn = field.className.split(" ");
			cn.each(function(className) {
				if(className.test('^warn-')) className = className.replace(/^warn-/,"");
				var prop = this.getPropName(className);
				if(field[prop]) this.hideAdvice(className, field);
				field.removeClass('validation-failed');
				field.removeClass('warning');
				field.removeClass('validation-passed');
			}, this);
		}
	},
	stop: function(){
		this.paused = true;
	},
	start: function(){
		this.paused = false;
	},
	ignoreField: function(field, warn){
		field = $(field);
		if(field){
			this.enforceField(field);
			if(warn) field.addClass('warnOnly');
			else field.addClass('ignoreValidation');
		}
	},
	enforceField: function(field){
		field = $(field);
		if(field){
			field.removeClass('warnOnly');
			field.removeClass('ignoreValidation');
		}
	}
});
FormValidator.implement(new Options);
FormValidator.implement(new Events);

FormValidator.adders = {
	validators:{},
	add : function(className, options) {
		this.validators[className] = new InputValidator(className, options);
		//if this is a class
		//extend these validators into it
		if(!this.initialize){
			this.implement({
				validators: this.validators
			});
		}
	},
	addAllThese : function(validators) {
		$A(validators).each(function(validator) {
			this.add(validator[0], validator[1]);
		}, this);
	},
	getValidator: function(className){
		return this.validators[className];
	},
	isEmpty:function(element) { 
		if(element.type == "select-one"||element.type == "select")
			return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != "");
		else
			return ((element.getValue() == null) || (element.getValue().length == 0));
	}
	
};
Object.extend(FormValidator, FormValidator.adders);
FormValidator.implement(FormValidator.adders);

FormValidator.addAllThese([
	['required', {
		errorMsg: function(element){return 'обязательное поле'}, 
		test: function(element) { 
			return !FormValidator.isEmpty(element); 
		}
	}],
	['minLength', {
		errorMsg: function(element, props){
			if($type(props.minLength))
				return 'пожалуйста, введите минимум ' + props.minLength + ' символа';
			else return '';
		}, 
		test: function(element, props) {
	        if(FormValidator.isEmpty(element)) return true;
			if($type(props.minLength)) return (element.getValue().length >= $pick(props.minLength, 0));
			else return true;
		}
	}],
	['maxLength', {
		errorMsg: function(element, props){
			if($type(props.maxLength))
				return 'максимальный размер поля - ' + props.maxLength + ' символов';
			else return '';
		}, 
		test: function(element, props) {
	        if(FormValidator.isEmpty(element)) return true;
			return (element.getValue().length <= $pick(props.maxLength, 10000));
		}
	}],
	['validate-number', {
		errorMsg: 'пожалуйста, введите число',
		test: function(element) 
		{
				return FormValidator.isEmpty(element) || !/[^\d+$]/.test(element.getValue());
		}
	}],
	['validate-double', {
		errorMsg: 'пожалуйста, введите число. Например 3.14',
		test: function(element) 
		{
				return FormValidator.isEmpty(element) || !/[^\d.+$]/.test(element.getValue());
		}
	}],
	['validate-login', {
		errorMsg: 'разрешены только латинские символы, цифры и знак подчеркивания', 
		test: function(element) {
			return FormValidator.isEmpty(element) || /^[a-zA-Z0-9_]*$/.test(element.getValue())
		}
	}],
	['validate-date', {
		errorMsg: 'Формат даты: дд.мм.гггг. Например, 10.11.2007 для 10 ноября 2007',
		test: function(element) {
			if(FormValidator.isEmpty(element)) return true;
	    var regex = /^(\d{2})\.(\d{2})\.(\d{4})$/;
	    if(!regex.test(element.getValue())) return false;
	    var d = new Date(element.getValue().replace(regex, '$2/$1/$3'));
	    var result = ( (d.getDate()<10?'0'+d.getDate():d.getDate())+"."+((1+d.getMonth())<10?'0'+(d.getMonth()+1):(d.getMonth()+1))+"."+d.getFullYear())==element.getValue();
        return result;
		}
	}],
	['validate-email', {
		errorMsg: 'Пожалуйста, введите верный email адрес', 
		test: function (element) {
			return FormValidator.isEmpty(element) || /\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/.test(element.getValue());
		}
	}],
	['validate-pass-confirm', {
		errorMsg: 'пароль и подтверждение пароля не совпадают',  
		test: function (el, props) 
		{
			if(FormValidator.isEmpty(el)) return true;			
			if( props.confirm)
			{
				if( el.value == $(props.confirm).value)
				    return true;
			}
			return false;
		}
	}],
	['validate-by-ajax', {
		errorMsg: 'ошибка проверки',  
		test: function (el, props) 
		{
			if(FormValidator.isEmpty(el)) return true;
			var url=props.url+'?data='+el.value;
			var result = true;
			var jSonRequest = new Json.Remote(url,{async:false, onComplete: function(response){
			    result = response.result;
			    this.errorMsg = response.message;
			}.bind( this)}).send();
			return result;
		}
	}]
]);

window.addEvent( 'domready', function(){
	var els = $$('form.validate');
	els.each( function( el, i) { new FormValidator( el)});
});