/**
 *	@arthor Vladimir Shushkov
 *	@use Prototype 1.6
 */

if (typeof console === 'undefined') {
	// если нет firebug'а
	console = {}; console.log = console.error = console.debug = function(obj) {alert(obj); return true;};
}

if (typeof cm === 'undefined') {
	var cm = {};
}

var prn = function(content) {
	console.log(content);
};



cm.Validate = Class.create({
	
	/**
	 * @param mixed form		ID или элемент формы
	 * @param object fields		Поля формы, подлежащие проверке
	 * @param object options		Общие свойства проверки
	 * @throws Error
	 * @return void
	 */
	initialize: function(form, fields, options) {
		
		this.form 			= form;
		this.fields 		= fields;
		this.commonOptions	= options || null;
		
		this.initForm(form);
		this.initFields();
	},
	
	/**
	 * 
	 */
	initForm: function(form) {
		if (!(this.form = $(form))) {
			throw new Error('форма "'+ form +'" не найдена');
		}
		var _this = this;

		if (typeof this.form.onsubmit === 'function') {
			this._oldOnsubmit = this.form.onsubmit;
		}
		this.form.onsubmit = function() {
			var result = _this.validateForm();
			if (result && _this._oldOnsubmit) {
				result = _this._oldOnsubmit.call(this);
			}
			return result;
		}
	},
	
	/**
	 *	Добавляет обработчик события blur на каждый из заданых элементов формы
	 *	
	 *	@throws Error
	 *	@return void
	 */
	initFields: function() {
		Object.keys(this.fields).each(function(field) {
			
			var element = this.form[field];
			var self = this;
			if (!element) {
				/**
				 *	либо этот элемент не является элементом формы (т.е. не input и не select, и тд),
				 *	либо он не является элементом НАШЕЙ формы,
				 *	либо его вообще не существует
				*/
				element = $(field);
				if (!element) {
					throw new Error('элемента "'+ field +'" не существует.');
				}
				
				if (!element.descendantOf(this.form)) {
					throw new Error('элемент "'+ field +'" в форме "'+ this.form.id +'" не найден');
				}
				
				this.form[field] = element;
				
				$A(element.getElementsByClassName('validate-field')).each(function(field) {
					Event.observe(field, 'blur', function() {
						element.validate.validateField(element);
					}, false);
				}.bind(this));
				
			} else {
				Event.observe(element, 'blur', function() {
					this.validate.validateField(this);
				}, false);
			}
			
			element.validate = this;
			element.validators = [];
			
			$A(this.fields[element.id]).each(function(i) {
				var validator = i;
				if (!(validator instanceof cm.Validator.Base)) {
					return;
				}
				if (this.commonOptions !== null) {
					validator.setOptions(this.commonOptions);
				}
				element.validators.push(validator);
			}.bind(this));
			
		}.bind(this));
	},
	
	/**
	 *	Проверяет поле
	 *	
	 *	@param Element		Общие свойства проверки
	 *	@return boolean
	 */
	validateField: function(field) {
		var result = true, i = 0;
		while (typeof field.validators[i] !== 'undefined' && (result = field.validators[i].validate()) === true) {
			field.validators[i].removeError();
			i++;
		}
		if (result !== true) {
			field.validators[i].removeError();
			field.validators[i].errorBox = field.validators[i].showError(result);
			return false;
		}
		return true;
	},
	
	/**
	 *	Проверяет каждое заданное поле формы
	 *	
	 *	@return boolean
	 */
	validateForm: function(e) {
		var success = true, focus = false;
		Object.keys(this.fields).each(function(field) {
			if (this.form[field].validate.validateField(this.form[field]) !== true) {
				success = false;
				if (focus === false) {
					focus = this.form[field];
				}
			}
		}.bind(this));
		if (focus !== false) {
			focus.focus();
		}
		
		return success;
	}
});

cm.Validator = {};
cm.Validator.Base = Class.create({
	
	/**
	 *	@param mixed		имя или элемент поля
	 *	@param object		свойства валидатора
	 *	@return void
	 */
	initialize: function(field, options) {
		this.field			= $(field);
		this.errorBox		= null;
		// this.options		= options || {};
		this.options		= Object.extend({
			/**
			 * callback-функции для управления выводом
			 * ошибки по умолчанию
			 */
			showError: function(el, text) {
				var errorBox = document.createElement('div');
				errorBox.className = 'error-box';
				errorBox.innerHTML = text;
				if (el === el.parentNode.lastChild) {
					el.parentNode.appendChild(errorBox);
				} else {
					el.parentNode.insertBefore(errorBox, el.nextSibling);
				}
				$(el.parentNode).addClassName('error');
				return errorBox;
			},
			removeError: function(errorBox) {
				$(errorBox.parentNode).removeClassName('error');
				errorBox.parentNode.removeChild(errorBox);
			}
		}, this.defaultOptions);
		
		this.setOptions(options);
	},
	
	/**
	 * 
	 */
	setOptions: function(options) {
		this.options = Object.extend(this.options, options);
	},
	
	defaultOptions: {},
	
	/**
	 * Вызывает callback-функцию вывода ошибки
	 */
	showError: function(result) {
		return this.options.showError(this.field, result);
	},
	
	/**
	 * Вызывает callback-функцию удаления ошибки
	 */
	removeError: function() {
		if (this.errorBox === null) {
			return;
		}
		this.options.removeError(this.errorBox);
		this.errorBox = null;
	},
	
	/**
	 * Выводит текст сообщения об ошибке.
	 * Используется класс Template (http://prototypejs.org/api/template/)
	 *
	 * @param string		строка-шаблон ошибки
	 * @param object		данные для вставки в шаблон
	 * @return string
	 */
	getMessage: function(tpl, data) {
		var tpl = new Template(tpl);
		return tpl.evaluate(data)
	},
	
	/**
	 * Пытается получить название поля, ориентируясь на 
	 * соответствующий ему label, если такого label'а нет,
	 * возвращается имя поля
	 *
	 * @return string
	 */
	tryToGetName: function(field) {
		var field = field || this.field;
		if (typeof field.labelName !== 'undefined' || field.labelName !== false) {
			field.labelName = false;
			$A(field.validate.form.getElementsByTagName('label')).each(function(el) {
				if ((el.getAttribute('for') || el.htmlFor) == this.id) {
					this.labelName = el.innerHTML;
				}
			}.bind(field));
		}
		return field.labelName || field.name;
	}
});

/**
 * Классы-наследники класса cmValidator.Base
 */
cm.Validator.NonEmpty = Class.create(cm.Validator.Base, {
	validate: function() {
		if (!this.field.value) {
			return this.getMessage(this.options.message, {name: this.tryToGetName()});
		}
		return true;
	},
	defaultOptions: {
		message: 'поле &laquo;#{name}&raquo; обязательно'
	}
});
cm.Validator.NonEmptyPhone = Class.create(cm.Validator.Base, {
	validate: function() {
		
		var element = this.field.getElementsByClassName('f-phone-code-input')[0];
		if (!element.value) {
			return 'поле &laquo;Код&raquo; обязательно';
		}
		if (!/\d+/.test(element.value)) {
			return 'поле &laquo;Код&raquo; должно состоять из цифр';
		}
		
		element = this.field.getElementsByClassName('f-phone-number-input')[0];
		if (!element.value) {
			return 'поле &laquo;Номер&raquo; обязательно';
		}
		if (!/[\d\-]+/.test(element.value)) {
			return 'поле &laquo;Номер&raquo; должно состоять из цифр';
		}
		
		return true;
	}
});

cm.Validator.CheckLength = Class.create(cm.Validator.Base, {
	validate: function() {
		if (this.field.value.length > this.options.maxLength) {
			return this.getMessage(this.options.message, {length: this.options.maxLength});
		}
		return true;
	},
	defaultOptions: {
		maxLength: 1000,
		message: 'длина поля не должна превышать #{length} символов'
	}
});

cm.Validator.IsEmail = Class.create(cm.Validator.Base, {
	validate: function() {
		if (!(/^[A-Za-z0-9\.\-_]+@[A-Za-z0-9\.\-_]+\.[A-Za-z0-9]{2,5}$/.test(this.field.value))) {
			return this.getMessage(this.options.message, {name: this.tryToGetName()});
		}
		return true;
	},
	defaultOptions: {
		message: 'неверный формат эл. почты'
	}
});

cm.Validator.RegExp = Class.create(cm.Validator.Base, {
	validate: function() {
		if (this.statement === null || !this.field.value) {
			return true;
		}
		var re = new RegExp(this.options.statement, this.options.flags);
		if (!re.test(this.field.value)) {
			return this.options.message;
		}
		return true;
	},
	defaultOptions: {
		statement: null,
		flags: '',
		message: ''
	}
});

cm.Validator.IsDate = Class.create(cm.Validator.Base, {
	validate: function() {
		if (!this.field.value) {
			return true;
		}
		var matches = /^(\d{4})-(\d{2})-(\d{2})$/.exec(this.field.value);
		if (matches === null) {
			return this.options.msgWrongFormat;
		}
		var date = new Date(matches[1], matches[2]-1, matches[3]);
		if (date.getFullYear() != matches[1] ||
			date.getMonth() != (parseInt(matches[2])-1) ||
			date.getDate() != parseInt(matches[3])) {
			return this.options.msgNotValid;
		}
		return true;
	},
	defaultOptions: {
		msgWrongFormat: 'неверный формат даты',
		msgNotValid: 'неверная дата'
	}
});

cm.Validator.MustEqual = Class.create(cm.Validator.Base, {
	validate: function() {
		if (this.options.equalField == null) {
			return true;
		}
		if (!this.field.validate.form[this.options.equalField]) {
			throw new Error('элемент "'+ this.options.equalField +'" в форме "'+ this.validateObj.form.id +'" не найден');
			return true;
		}
		
		if (this.field.value != this.field.validate.form[this.options.equalField].value) {
			return this.getMessage(this.options.message, {
				field1: this.tryToGetName(this.field),
				field2: this.tryToGetName(this.field.validate.form[this.options.equalField])
			});
		}
		return true;
	},
	defaultOptions: {
		equalField: null,
		message: 'значение поля &laquo;#{field1}&raquo; должно совпадать с значением поля &laquo;#{field2}&raquo;'
	}
});

cm.Validator.MinLength = Class.create(cm.Validator.Base, {
	validate: function() {
		
		var value1 = parseFloat(this.field.value);
		var value2 = parseFloat(this.options.value);
		
		if (value1 <= value2) {
			return this.getMessage(this.options.message, {
				value1: this.tryToGetName(this.field),
				value2: value2
			});
		}
		return true;
	},
	defaultOptions: {
		value: 0,
		message: 'значение поля &laquo;#{value1}&raquo; должно быть больше чем &laquo;#{value2}&raquo;'
	}
});

cm.Validator.CaptchaVerify = Class.create(cm.Validator.Base, {
	validate: function() {
		
		if (this.field.verified === true) {
			return true;
		}

		if (!this.field.value) {
			return false;
		}

		var self = this;
		var url = this.options.url +'call~'+ this.options.tag +'?isValidCode='+ this.field.value;

		this.field.verified = false;
		
		new Ajax.Request(url, {asynchronous: false, onSuccess: function(transport) {
			var result = (transport.responseText == 'true');
			self.field.verified = result;
			if (!result) {
				self.field.parentNode.firstChild.src = self.field.parentNode.firstChild.src +'?'+ Math.random();
			}
		}});
		
		return this.field.verified;
	},
	defaultOptions: {
		url: '',
		tag: ''
	}
});


cm.Validator.ValueCompare = Class.create(cm.Validator.Base, {
	validate: function() {
		if (this.options.field == null) {
			return true;
		}
		
		var compareField = this.field.validate.form[this.options.field];
		
		if (!compareField) {
			throw new Error('элемент "'+ this.options.field +'" в форме "'+ this.validateObj.form.id +'" не найден');
			return true;
		}
		
		var value1 = parseFloat(this.field.value.replace(',', '.')), 
			value2 = parseFloat(compareField.value.replace(',', '.'));
		
		if (
			(this.options.type == 'lt'  && value1 < value2) ||
			(this.options.type == 'lte' && value1 <= value2) ||
			(this.options.type == 'gt'  && value1 > value2) ||
			(this.options.type == 'gte' && value1 >= value2)
		) {
			return true;
		}
		
		return this.options.message;
	},
	defaultOptions: {
		field: null,
		type: 'le', // lt | lte | gt | gte
		message: null
	}
});
