Validator = Class.create({
	initialize: function(frm, checkClass) {
		this.frm = $(frm);
		this.func = new Hash;
		this.handler = new Hash;
		this.checkClass = checkClass;
		this.frm.observe('submit', this.validate.bindAsEventListener(this));
		new Tip(this.frm, 'Обратите внимание на поля, отмеченные знаком <img src="/res/ajax/error.png" alt="Заполнено не верно" class="png" /> (кроме того, они подсвечены красным). Эти поля не заполнены либо заполнены некорректно.<br />Знак <img src="/res/ajax/error.png" alt="Заполнено не верно" class="png" /> имеет всплывающую подсказку, которая поможет вам заполнить соответствующее поле правильно.<br />Внесите необходимые изменения и повторите попытку.', {style: 'error-submit', title: 'Не все поля заполнены верно'});
		this.addFunc('regex', function(val, options) {
			if (Object.isUndefined(options.pattern)) {
				return false;
			}
			return options.pattern.test(val);
		});
		this.addFunc('email', function(val) {
			return /^[a-zA-Z0-9_\-\.]+\@([a-z0-9\-]+\.)+[a-z]{2,4}$/.test(val);
		});
		this.addFunc('url', function(val) {
			return /^https?:\/\/([a-z0-9\-]+\.)+[a-z]{2,4}(\/\S*)?$/.test(val);
		});
		this.addFunc('range', function(val, options) {
			if (!Object.isUndefined(options.min) && (Number(val) < options.min)) {
				return false;
			}
			if (!Object.isUndefined(options.max) && (Number(val) > options.max)) {
				return false;
			}
			return true;
		});
		this.addFunc('length', function(val, options) {
			if (!Object.isUndefined(options.min) && (val.length < options.min)) {
				return false;
			}
			if (!Object.isUndefined(options.max) && (val.length > options.max)) {
				return false;
			}
			return true;
		});
		this.addFunc('required', function(val) {
			return val != '';
		});
		this.addFunc('uniq', function(val, options) {
			return val == $(options.compare).value;
		});
	},
	addFunc: function(name, func) {
		if (Object.isFunction(func)) {
			this.func.set(name, func);
		}
	},
	getHandler: function(element) {
		var hdl = this.handler.get(element);
		if (Object.isUndefined(hdl)) {
			hdl = new Validator.Handler(this, element);
			this.handler.set(element, hdl);
		}
		return hdl;
	},
	clear: function() {
		var hdl = this.handler;
		this.handler.each(function(pair) {
			if (Object.isUndefined(pair.value.getObject())) {
				hdl.unset(pair.key);
			}
		});		
	},
	validate: function(e) {
		var valid = true;
		this.clear();
		this.handler.each(function(pair) {
			if (!pair.value.react()) {
				valid = false;
			}
		});
		if (!valid) {
			this.frm.prototip.show();
			e.stop();
		}
	}
});
Validator.Handler = Class.create({
	initialize: function(validator, element) {
		this.validator = validator;
		this.element = element;
		this.data = new Array;
		this.reactFunc = this.react.bindAsEventListener(this);
	},
	getObject: function() {
		return $(this.validator.frm.elements[this.element]);
	},
	bind: function(funcName, options) {
		var func = this.validator.func.get(funcName);
		if (Object.isFunction(func)) {
			this.data.push(new this.validator.checkClass(this, this.getObject(), func, options));
		}
		this.getObject().observe('x:change', this.reactFunc);
		this.getObject().observe('blur', this.reactFunc);
		return this;
	},
	clear: function() {
		this.getObject().stopObserving('x:change', this.reactFunc);
		this.getObject().stopObserving('blur', this.reactFunc);
		this.data.each(function(item) {
			item.markValid();
		});
		this.data.clear();
		this.markValid();
		return this;
	},
	react: function() {
		if (!Element.visibleEx(this.getObject())) {
			return true;
		}
		var res = true;
		this.data.each(function(item) {
			item.markValid();
		});
		this.data.each(function(item) {
			if (!item.react()) {
				res = false;
				if (item.options.skipNext) {
					throw $break;
				}
			}
		});
		if (res) {
			this.markValid();
		} else {
			this.markInvalid();
		}
		return res;
	},
	markInvalid: function() {
		this.getObject().addClassName('err');
	},
	markValid: function() {
		this.getObject().removeClassName('err');
	}
});
Validator.Check = Class.create({
	initialize: function(handler, object, func, options) {
		this.handler = handler;
		this.object = object;
		this.func = func;
		this.options = {};
		Object.extend(this.options, options);
		this.sign = {};
	},
	react: function() {
		if (this.options.asynchronous) {
			this.markLoading();
			return this.func.call(this, this.object.value, this.options, this.process.bind(this));
		}
		var res = this.func.call(this, this.object.value, this.options);
		if (this.options.allowEmpty && (this.object.value == '')) {
			res = true;
		}
		this.process(res);
		return res;
	},
	process: function(res) {
		if (res) {
			this.markValid();
		} else {
			this.markInvalid();
			this.handler.markInvalid();
		}
		this.markLoaded();
	},
	markLoading: function() {
		if (!this.sign.loading) {
			var load = new Element('img', {src: '/res/ajax/loader.gif', 'class': 'sign'});
			this.bindSign(load);
			this.sign.loading = load;
		}
	},
	markLoaded: function() {
		if (this.sign.loading) {
			this.sign.loading.remove();
			this.sign.loading = null;
		}
	},
	markInvalid: function() {
		if (!this.sign.error) {
			var err = new Element('span');
			err.insert(new Element('img', {src: '/res/ajax/error.png', 'class': 'sign png'}));
			this.bindSign(err);
			this.sign.error = err;
			if (this.options.hint) {
				new Tip(err, this.options.hint, {style: 'error-hint'});
			}
		}
	},
	markValid: function() {
		if (this.sign.error) {
			if (this.sign.error.prototip) {
				this.sign.error.prototip.remove();
			}
			this.sign.error.remove();
			this.sign.error = null;
		}
	},
	bindSign: function(sign) {
	}
});
Validator.Check.Index = Class.create(Validator.Check, {
	bindSign: function(sign) {
		this.object.previous('.label').insert(sign);
	}
});
Validator.Check.Other = Class.create(Validator.Check, {
	bindSign: function(sign) {
		this.object.ancestors()[0].insert(sign);
	}
});
Element.addMethods({
	visibleEx: function(element) {
		res = true;
		$(element).ancestors().each(function(el) {
			if (!el.visible()) {
				res = false;
				throw $break;
			}
		});
		return res;
	}
});
