/**
 * @author ricky
 * 
 * Объект проверки правильности заполнения полей формы
 * 
 */
var FormValidator = {

	/**
	 * @property
	 * @private
	 * @description Объект, содержащий: ключи - идентификаторы таблицы, значения -
	 *              массивы с объектами для провекри
	 * @type Object
	 */
	_fields : {},

	/**
	 * @property
	 * @private
	 * @description Регулярные выражения для определенных условий
	 * @type Object
	 */
	_regexps : {
		'integer' : /^[0-9]+$/,
		'float' : /^[-+]?[0-9]+(\.[0-9]+)?$/,
		'phone' : /^(\+\d)*\s*(\(\d{3}\)\s*)*\d{3}(-{0,1}|\s{0,1})\d{2}(-{0,1}|\s{0,1})\d{2}$/,
		'russian_mobile_phone' : /^\+?\d\s*\-*\s*((\(\d{3}\))|(\d{3}))\s*\d{3}((\s*)|(\s*\-\s*))\d{2}((\s*)|(\s*\-\s*))\d{2}$/,
		'time' : /^([0-1][0-9]|[2][0-3])(:([0-5][0-9])){1,2}$/,
		'url' : /^(http[s]?:\/\/|ftp:\/\/)?(www\.)?[a-zA-Z0-9-\.]+\.(aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)$/i,
		'mail' : /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i,
		'alpha' : /^[a-zA-Zа-яА-Я\s]*$/,
		'alnum' : /^[a-zA-Zа-яА-Я0-9Z\s]*$/,
		'notempty' : /^.+$/
	},

	/**
	 * @property
	 * @private
	 * @description Типы полей для проверки
	 * @type String[]
	 */
	_types : ['text', 'textarea', 'file', 'radio', 'select', 'checkbox',
			'hidden'],

	/**
	 * @property
	 * @private
	 * @description Булево значение. если есть ошибки при проверке формы, то
	 *              тру, иначе фолс
	 * @type Boolean
	 */
	_form_valid : true,

	/**
	 * @method
	 * @private
	 * @description Если предыдущая проверка не прошла, то появляются элементы с
	 *              описанием ошибок, либо сами элементы управления формой
	 *              окрасились в другой цвет. При последующих проверках перед
	 *              самой проверкой нужно вернуть всю форму в первоначальный
	 *              вид. Для этого и существует метод _reset()
	 * @param {String}
	 *            Идентификатор формы
	 */
	_reset : function(frm_id) {
		// Проверяем, есть ли что очищать
		if (!this._has_fields(frm_id))
			throw new Error('No elements to check in form with id "' + frm_id
					+ '"');

		// Проходим по массиву объектов и очищаем
		$each(this._fields[frm_id]['elements'], function(obj) {
					if (typeof obj['alert'] != 'undefined') {
						obj['alert'].hide();
					}
					if (typeof obj['element_class'] != 'undefined'
							&& obj['element'].hasClass(obj['element_class'])) {
						obj['element'].removeClass(obj['element_class']);
					}
					if (typeof obj['error_div'] != 'undefined'
							&& $type.element(obj['error_div'])) {
						obj['error_div'].hide();
					}
				});

		// сбрасываем значение ошибки в форме.
		this._form_valid = true;
	},

	/**
	 * @method
	 * @private
	 * @description Функция, проверяющее наличие элементов для проверки
	 * @param {String}
	 *            Идентификатор формы
	 * @return {Boolean}
	 */
	_has_fields : function(frm_id) {
		// Если есть поля для проверки, возвращаем правду =)
		if ($chk(this._fields[frm_id])
				&& this._fields[frm_id]['elements'].length) {
			return true;
		}
		return false;
	},

	/**
	 * @method
	 * @private
	 * @description Отображает ошибку определенного типа для элемента
	 * @param {Object}
	 *            obj Объект элемента, для которого нужно отобразить ошибку
	 */
	_display_error : function(obj) {
		if (obj['alert']) {
			obj.alert.show();
		}
		if (obj['element_class']) {
			obj.element.addClass(obj.element_class);
		}
		if (obj['error_div']) {
			obj.error_div.show();
		}
		this._form_valid = false;
	},

	/**
	 * @method
	 * @private
	 * @description Метод для проверки входных данных
	 * @param {Element}
	 *            form Элемент формы
	 * @param {Array}
	 *            elements Массив объектов для проверки
	 * @return {Boolean}
	 */
	_prepare_fields : function(form, elements) {
		var elements = $A(elements);

		// Проверка на тип входных данных. должен быть массив
		if (!$type.array(elements))
			throw new Error('Argument elements for FormValidator._prepare_fields'
					+ ' method should be an array');

		// Проверка на длину массива
		if (!elements.length)
			throw new Error('Argument elements for FormValidator._prepare_fields'
					+ ' method is empty');

		// проверка в одном цикле
		for (var i = 0, len = elements.length; i < len; i++) {
			if (!$type.object(elements[i]))
				throw new Error('Argument elements for FormValidator._prepare_fields'
						+ ' method should consist of objects only');

			// Проверка на наличие ссылки на элемент (element) в массиве
			// объектов
			if (!$chk(elements[i]['element']))
				throw new Error('No element found in config'
						+ ' for FormValidator._prepare_fields');

			// Проверка на наличие типа элемента (type) в массиве объектов
			if (!$chk(elements[i]['type']))
				throw new Error('No type found in config'
						+ ' for FormValidator._prepare_fields');

			// если такого типа поля нет
			if (this._types.indexOf(elements[i]['type']) == -1)
				throw new Error('No type "' + elements[i]['type']
						+ '" found in config'
						+ ' for FormValidator._prepare_fields');

			// если это одинарный элемент, то находим его идентификатор
			if (elements[i]['type'] == 'text'
					|| elements[i]['type'] == 'select'
					|| elements[i]['type'] == 'textarea'
					|| elements[i]['type'] == 'file'
					|| elements[i]['type'] == 'hidden') {
				elements[i]['element'] = form.getElement('#'
						+ elements[i]['element']) || false;
			}
			// если это множественный элемент
			if (elements[i]['type'] == 'radio'
					|| elements[i]['type'] == 'checkbox') {
				var elms = form.getElements('.' + elements[i]['element']
						+ ' > input')
				elements[i]['element'] = (elms.length) ? elms : false;
			}
			// если есть алерт
			if (typeof elements[i]['alert'] != 'undefined') {
				elements[i]['alert'] = form.getElement('#'
						+ elements[i]['alert']) || false;
			}
			if (typeof elements[i]['element_class'] != 'undefined'
					&& typeof elements[i]['element_class']) {
				elements[i]['element_class'] = 'wrong';
			}
			// если есть условие и оно текстовое, то преобразуем его в массив с
			// одни элементом.
			if ($chk(elements[i]['condition'])
					&& $type.string(elements[i]['condition'])) {
				elements[i]['condition'] = [elements[i]['condition']];
			}
			// если нужно показать ошибку, которая размещается в диве под
			// контролами
			if ($chk(elements[i]['error_div'])
					&& $type.bool(elements[i]['error_div'])
					&& elements[i]['error_div']) {
				// пока только, если элемент одиночный
				if ($type.element(elements[i].element)) {
					elements[i].error_div = elements[i].element.getParent()
							.getNext('div.errors');
				}
			}
			// если для элемента существует условие, что в него могут вбиваться
			// только цифры, делаем соответствующий обработчик событий
			if (elements[i]['element'] && elements[i]['type'] == 'text'
					&& $chk(elements[i]['condition'])
					&& elements[i]['condition'].contains('integer')
					&& $chk(window.justNumber)) {
				elements[i]['element'].addEvent('keydown', justNumber);
			}
		}

		return elements;
	},

	/**
	 * @method
	 * @private
	 * @description Находит форму, или возвращает ложь
	 * @param {String|Element}
	 *            form Ссылка на идентификатор формы
	 */
	_prepare_form : function(form) {
		if (!$type.string(form) && !$type.element(form))
			throw new Error('Form is neither string nor Element');

		// если у формы нет айдишника, то ставим ему уникальный
		if ($type.element(form)
				&& (!$chk(form.getProperty('id')) || !form.getProperty('id')
						.trim().length))
			form.setProperty('id', $time());

		if ($type.element(form))
			return form;

		if ($type.string(form)) {
			var form_el = $(form);
			if (!$chk(form_el))
				throw new Error('Couldn not find form with id "' + form + '"');

			return form_el;
		}
	},

	/**
	 * @method
	 * @public
	 * @description Сеттер для полей и их атрибутов
	 * @param {String|Element}
	 *            form идентификатор формы, в которой находятся объекты, или сам
	 *            элемент формы
	 * @param {Array}
	 *            elements массив с объектами для каждого из проверяемых полей
	 * @param {Bool}
	 *            update если true - добавляем элементы, если false -
	 *            переписываем новыми
	 * @return {Object} This
	 */
	set : function(form, elements, update) {
		// если недостаточно аргументов
		if (arguments.length < 2)
			throw new Error('No parameters supplied for FormValidator object');

		// смотрим, есть ли такая форма
		var form = this._prepare_form(form);

		// проверяем, соответствуют ли входные данные правде
		var elements = this._prepare_fields(form, elements);

		var frm_id = form.getProperty('id');

		// Смотрим, что делать с входными данными
		if ($chk(update) && update) {
			// добавляем элементы к уже существующим
			var add_obj = {}
			add_obj[frm_id] = {};
			add_obj[frm_id]['form_link'] = form;
			add_obj[frm_id]['elements'] = elements;
			$extend(this._fields, add_obj);
		} else {
			// обновляем все элементы
			this._fields = {};
			this._fields[frm_id]['form_link'] = form;
			this._fields[frm_id]['elements'] = elements;
		}
		return this;
	},

	/**
	 * @method
	 * @public
	 * @description Проверяет форму
	 * @param {String}
	 *            frm_id Строчка с идентификатором таблицы, поля которой нужно
	 *            проверить, или сам элемент формы
	 */
	validate : function(frm_id) {
		if (!arguments.length)
			throw new Error('No arguments supplied for'
					+ ' FormValidator.validate method. Should be one.');

		if (!$type.string(frm_id))
			throw new Error('Argument frm_id for'
					+ ' FormValidator.validate method should be a string');

		// проверяем, есть ли поля для проверки
		if (!this._has_fields(frm_id))
			throw new Error('No elements to validate in form with id "'
					+ frm_id + '"');

		// ресетаем все алерты
		this._reset(frm_id);

		// Проверяем
		$each(this._fields[frm_id]['elements'], function(obj) {
			// смотрим какой у нас элемент
			if ((obj.type == 'text' || obj.type == 'textarea'
					|| obj.type == 'file' || obj.type == 'hidden')
					&& obj.element) {

				// проверка на тайнимце
				if ($chk(window.tinyMCE) && obj.type == 'textarea'
						&& $chk(tinyMCE.editors[obj.element.get('id')])) {
					obj.element.value = tinyMCE.editors[obj.element.get('id')]
							.getContent();
				}

				// если текстовый, то возможно применение условий проверки
				// есть ли условия
				if (typeof(obj['condition']) != 'undefined'
						&& $type.array(obj['condition'])) {
					// если есть условия
					// смотрим, сколько условий имеется
					for (var i = 0, len = obj['condition'].length, cond = obj['condition']; i < len; i++) {
						// регулярное выражение
						if (typeof this._regexps[cond[i]] != 'undefined') {
							// если условие есть в списке регулярных
							// выражений
							if (!this._regexps[cond[i]].test(obj.element.value
									.trim())) {
								this._display_error(obj);
								continue;
							}
						}
						// больше или меньше определенного числа символов
						var c;
						if (c = cond[i].match(/^(<|>)([0-9]+)$/)) {
							var comparison = c[1];
							var number = c[2];
							if (comparison == '>') {
								if (obj.element.value.trim().length < number) {
									this._display_error(obj);
									continue;
								}
							}
							if (comparison == '<') {
								if (obj.element.value.trim().length > number) {
									this._display_error(obj);
									continue;
								}
							}
						}
						// формат даты
						var d;
						if (d = cond[i].match(/date\(([^\)]+)\)/)) {
							d = new RegExp(d[1].escapeRegExp().replace(
									/[a-zA-Z]/g, '[0-9]'));
							if (!d.test(obj.element.value.trim())) {
								this._display_error(obj);
								continue;
							}
						}
						// больше или меньше определенного числа
						var digit;
						if (digit = cond[i].match(/(less|greater)\-(.+)/)) {
							var comp_digit = digit[1].toFloat();
							// проверка на то, что значение вообще число
							if (!this._regexps['float'].test(obj.element.value
									.trim())) {
								this._display_error(obj);
								continue;
							}
							// если значение прошло проверку на то, что оно
							// все-таки число
							switch (digit[1]) {
								case 'less' :
									if (obj.element.value.toFloat() > comp_digit) {
										this._display_error(obj);
										continue;
									}
									break;
								case 'greater' :
									if (obj.element.value.toFloat() < comp_digit) {
										this._display_error(obj);
										continue;
									}
									break;
							}
						}
					}
				} else {
					// если условий нет, то проверяем просто длину строки
					if (!obj.element.value.trim().length) {
						this._display_error(obj);
					}
				}
			}
			if ((obj.type == 'checkbox' || obj.type == 'radio') && obj.element) {
				if (!obj.element.getChecked().length) {
					this._display_error(obj);
				}
			}
			if (obj.type == 'select' && obj.element) {
				var selected_items = obj.element.getSelected();
				if (!selected_items.length) {
					this._display_error(obj);
				} else if (selected_items.length == 1) {
					if (!selected_items[0].value.trim().length) {
						this._display_error(obj);
					}
				} else {
					var valid = false;
					for (var i = 0, len = selected_items.length; i < len; i++) {
						if (selected_items[i].value.trim().length) {
							valid = true;
							break;
						}
					}
					if (!valid) {
						this._display_error(obj);
					}
				}
			}
		}, this);

		return this._form_valid;
	}
}